[태그:] FSM

  • 안드로이드 앱 개발 일지, 3차

    안드로이드 앱 개발기 3

    파일 입출력

    수집된 데이터를 저장할 경우, 파일에 저장하면 된다. 전에 누가 개발한 코드를 보니 쉽게 사용하던데, android developer에서 보니 설명이 어렵게 되어 있다. stack overflow에서 찾아, 되는 코드를 만들 수 있었다.

    android developer에는 internal storage는 아무 제한이 없이 사용하는데, 코드로 만들면 permission 문제인지 파일과 폴더를 만들 수 없다. internal storage의 기본 위치는 /data/…인데, 안드로이드에서 직접 만들어보니 안되었다. 갤럭시 노트2의 경우 권한으로 막아놓은 듯 하다. 이런 설명이 없으니, 하루 삽질을 하게 되었다.

    결국 외부 저장소로 만들기로 했다. sd카드 슬롯이 없는데도 외부 저장소로 저장이 되었다. 꼭 sd카드일 필요는 없어 보인다. stack overflow에 있는 내용을 요약하면..
    First, get a file object

    You’ll need the storage path. For the internal storage, use:

    File path = context.getFilesDir();

    For the external storage (SD card), use:

    File path = context.getExternalFilesDir(null);

    위 부분을 아래와 같이 사용하면 앱별 폴더 이름을 특성화 할 수 있다.

     File directory = new File(context.getExternalFilesDir(Environment.DIRECTORY_DOCUMENTS), "GapnFlush");
                if (!directory.mkdirs()) {
                    Log.d("file", "Directory was created");
                }

    이렇게 하면 갤럭시 노트의 경우,
    /storage/emulated/0/Android/data/app이름/files/Documents에 GapnFlush란 폴더를 만든다.

    Then create your file object:

    File file = new File(path, "my-file-name.txt");

    Write a string to the file

    FileOutputStream stream = new FileOutputStream(file);
    try {
        stream.write("text-to-write".getBytes());
    } finally {
        stream.close();
    }

    Or with Google Guava

    Files.write("text-to-write", file, "UTF-8");

    앱 실행 전 권한도 설정을 해줘야 된다.

        <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>

    이제 파일은 저장이 되니까, 입맛대로 쓰면 된다..

  • 안드로이드 앱 개발 일지, 2차

    안드로이드 앱 개발기 2

    FSM 모델 수정

    batch 입력에서 에러가 발생하면 초기 상태로 돌아가는데, 그렇게 되면 나중에 데이터를 저장하거나, 초기화 할 경우, 항상 초기 상태에서 전이가 된다. 별로 좋지 않아 보여 아래와 같이 fsm을 수정 했다.

    #stateBatchRec에 한번 들어가면 에러가 발생할 경우, #stateDecision 상태로 들어가게 했다. 그 부분에서 #stateSave나 #stateModi로 전이를 일어나게 할 생각이다. 뒤 부분은 아직 못 그렸지만..

    보완할 사항으로는 #stateBatchRec 상태에서 일정 시간동안 지나면 #stateDecision으로 돌아가야 한다. timer를 쓰려고 보니, 좀 복잡하여, int를 계속 더해서 일정수가 지나면 전이가 일어나게 하려고 했는데, 잘 안된다. 하위 상태인 #stateRec_Gap에서 int를 초기화 시켜야되는데, 하위 state에서 상위 state에 접근할 수 있는 방법이 없어 보인다.

    버튼 두번 입력시 SpeechRecognizer 오류 발생 문데

    인식을 시작 하라고 버튼을 누른 상태에서 다시 버튼을 누르면 에러가 발생한다. 이럴 경우, 적당한 초기화를 해야 하는데, 딱히 없어 보인다. #stateDecision에 들어가면 기존의 SpeechRecognizer를 취소하게금 아래와 같이 했다.

        State stateDecision = new State(s0) {
            @Override
            public State fireInit() {
                Log.d("FSM", "Init>>stateDecision;");
                return null;
            }
    
            @Override
            public void enter() {
                Log.d("FSM", "Entry>>stateDecision;");
                myHandle.sendEmptyMessage(2);
            }
    

    Main Activity는 아래와 같이 handler 처리를 했다.

    public class MainActivity extends AppCompatActivity {
        Intent i;
        SpeechRecognizer mRecognizer;
        int TvIndex = 0;
        TextView[] TvGap = new TextView[10];
        TextView[] TvFlush = new TextView[10];
        //hsm에서 SpeechListener를 제어하기 위해서.
    
        Handler mHandler = new Handler() {
            @Override
            public void handleMessage(Message msg) {
                super.handleMessage(msg);
                //음성인식 시작부분..
                if (msg.what == 1)
                    mRecognizer.startListening(i);
                if (msg.what == 2)
                    mRecognizer.cancel();
            }
        };
    

    thread 사용 문제

    주기적으로 이벤트를 감지하기 위해서 thread를 사용했는데, 딱히 필요해 보이지 않는다. 각 이벤트마다 fsm.dispatch형식으로 이벤트를 새로 정의해서 보내는 경우가 대다수다.

  • 안드로이드 앱 개발 일지, 1차

    안드로이드 앱 개발 일지, 1차

    안드로이드 앱 개발기 1

    구글 음성인식 API를 활용한 기록기

    일하는 주변에 필요한 도구를 만들어 쓰기로 했다. 휴대폰에 사용되는 안드로이드 os가 휴대용 PC인데 그 기능을 잘 활용하면 현장의 많은 삽질을 줄일 수 있다. 나의 코딩 등급 및 JAVA에 대한 이해도는 미미하지만, stack overflow와 구글신을 믿어 해보기로 했다. 이 개발이 끝나면 JAVA 사용 방법, HSM(fsm) 적용, 파일 저장 등의 기술이 늘 것 같다.

    옛날에 fsm을 공부하기 위해 퀀텀 프로그래밍을 사서 읽었다. 이 책에 HSM에 대한 소개가 있었다. fsm을 확장하여 hierarchy로 정의한 fsm인데, 그런 방법으로 개발하면 쉽다고 했다. 문제는 이 사람이 C로 코드를 소개를 했다. 이를 JAVA로 옮겨야 되는데, 내 능력으로는 불가능 했다. 3년전에 인터넷을 열심히 뒤진 결과 Alexei Krasnopolski가 자바로 변경한 결과를 찾았다. fsm을 잘 사용하려면 uml을 정확하게 알아야 되는데, 대충 알고 있다. 나중에 문제되는 부분을 따로 찾아 보기로 했다.

    내가 원하는 기능

    아래의 기능을 갖는 앱을 개발하려고 한다.

    1. 구글의 음성인식을 활용하여, 말하는 내용을 텍스트로 변환
    2. layout에 번호를 할당하여 처음부터 끝까지 연속적인 인식
    3. 사용자 입력에 대한 android 판단은 소리로 표시
    4. 연속 작업 도중 틀린 부분은 버튼을 눌러 수정
    5. 수정 작업이 완료되면 다음 연속 작업으로 다시 복귀
    6. 모든 작업이 끝나면 파일로 저장

    아래의 방향으로 접근 하기로 했다.

    1. 구글 음성인식 API 사용
    2. hsm의 구성 및 설정
    3. event 전달 방법
    4. thread 사용 방법
    5. handler
    6. 파일 저장
    7. android studio 사용

    구글 음성 인식은 speech to text라고 하는데, 과거 text to speech의 역버전이다. 한국의 네이버도 이런 기능을 지원한다. 그러나 안드로이드에서 적절하게 사용이 가능한지 모르겠고, 별도 API를 알아야 된다. 게다가 전세계 개발자들이 구글을 많이 사용하여 내가 원하는 답이 많다. 이런 저런 이유로 구글을 사용했다. 안드로이드 내부에서 바로 구현이 가능하다. 샘플 코드는 인터넷에 구했다.

    전에는 eclipse를 안드로이드 개발이 가능하도록 세팅했는데, 요새 무슨 변화가 있었는지 최신 버전은 지원하지 않았다. 테마 관련 에러도 많이 뜨고..구글이 공식앱 개발툴이라고 밀고 있는 android studio를 사용했다. 인터넷에서 다운로드 하여 사용 하면 된다.

    Hierarchy-finite State Machine 정의

    일단 아래 그림과 같이 접근 하기로 했다.

    이 그림이 정확한지 해보지 않고는 정확한 판단을 할 수 없어, 일단 해보고 나중에 다시 보완하기로 했다.

    package com.example.now0930.myapplication;
    
    /**
     * Created by now0930 on 17. 2. 26.
     */
    
    import android.app.Activity;
    import android.os.Handler;
    import android.os.Message;
    import android.provider.ContactsContract;
    import android.speech.SpeechRecognizer;
    import android.util.Log;
    
    import com.example.now0930.myapplication.hsm.Event;
    import com.example.now0930.myapplication.hsm.Hsm;
    import com.example.now0930.myapplication.hsm.State;
    
    /**
     * This class represents a test HSM implementation.
     *
     * @author Alexei Krasnopolski (krasnop@bellsouth.net)
     */
    public class myHsm extends Hsm {
        int myFoo;
        DataGapFlush myGapFlush;
        Handler myHandle;
    
    
        public myHsm(DataGapFlush myDataInst, Handler handle) {
            this.myGapFlush = myDataInst;
            this.myHandle = handle;
        }
    
        public void init() {
            //System.out.print("Top-INIT;");
            Log.d("FSM", "Top-INIT;");
            myFoo = 0;
            super.init();
        }
    
        ;
    
        public State fireInit() {
            Log.d("FSM", "INIT>>s0;");
            return stateStandby;
        }
    
        ;
    
        public State fireEvent(Event e) {
    
            return getParent();
        }
    
        ;
    
        public void enter() {
            Log.d("FSM", "ENTRY>>s0;");
        }
    
        ;
    
        public void exit() {
            Log.d("FSM", "EXIT<<s0;"); } Hsm s0 = this; // This is just alias of current instance State stateStandby = new State(s0) { @Override public State fireInit() { Log.d("FSM", "INIT>>stateStandby;");
                return null;
            }
    
            @Override
            public void enter() {
                Log.d("FSM", "ENTRY>>stateStandby;");
            }
    
            @Override
            public State fireEvent(Event e) {
                switch (e.getID()) {
                    case 5:
                        printMessage(e, "stateBatchRec");
                        //myHandle.sendMessage(myHandle.obtainMessage());
                        myHandle.sendEmptyMessage(1);
                        myHsm.this.transition(stateBatchRec);
                        return null;
                    default:
                        break;
                }
                return getParent();
            }
    
            @Override
            public void exit() {
                Log.d("FSM", "EXIT<<stateStandby;"); } }; State stateBatchRec = new State(s0) { @Override public State fireInit() { Log.d("FSM", "Init>>stateBatchRec;");
                return stateBatchRec_Gap;
            }
    
            @Override
            public void enter() {
                Log.d("FSM", "Entry>>stateBatchRec;");
            }
    
            @Override
            public State fireEvent(Event e) {
                switch (e.getID()) {
                    case 6:
                        printMessage(e, "stateStandby");
                        myHsm.this.transition(stateStandby);
                        return null;
                    case 11:
                        printMessage(e, "stateModify");
                        myHsm.this.transition(stateModify);
                        return null;
                    default:
                        break;
    
                }
                return getParent();
            }
    
            @Override
            public void exit() {
                Log.d("FSM", "EXIT<<stateBatchRec;"); } }; State stateBatchRec_Gap = new State(stateBatchRec) { @Override public State fireInit() { Log.d("FSM", "Init>>stateBatchRec_Gap;");
                return null;
            }
    
            @Override
            public void enter() {
                Log.d("FSM", "Entry>>stateBatchRec_Gap;");
                //myHandle.sendEmptyMessage(1);
            }
    
            @Override
    
            public State fireEvent(Event e) {
                //아래 e.getID를 실행하면
                //이벤트 정의시 내부 데이터에 의한 기준으로 하면
                //일정 시점 이후로는 그 동작만 계속됨..
                //내부 이벤트, 이부 이벤트로 분리.
                //내부 이벤트는 그 state에서만 실행되도록 정의
                boolean gapFlag = myGapFlush.checkGapFulled();
                if(gapFlag == true){
                    myHsm.this.transition(stateBatchRec_Flush);
                    return null;
                }
    
                switch (e.getID()) {
                    case 1:
                        printMessage(e, "stateBatchRec_Gap");
                        int i = myGapFlush.getGapIndex();
                        myGapFlush.setGapIthwithN(i, myGapFlush.getTmpWord());
                        myGapFlush.emptyTmpWord();
                        myHsm.this.transition(stateBatchRec_Gap);
                        myHandle.sendEmptyMessage(1);
                        return null;
                    /***
                    case 2:
                        myHsm.this.transition(stateBatchRec_Flush);
                        return null;
                     ***/
                    default:
                        break;
                }
                return getParent();
            }
    
            ;
    
            @Override
            public void exit() {
                Log.d("FSM", "Exit<<stateBatchRec_Gap;"); } 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) {
                    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);
                            myHandle.sendEmptyMessage(1);
                            return null;
                        default:
                            break;
                    }
                    return getParent();
    
                }
    
                @Override
                public void exit() {
                    Log.d("FSM", "Exit<<stateBatchRec_Flush;"); } }; }; State stateModify = new State(s0) { @Override public State fireInit() { Log.d("FSM", "Init>>stateModify;");
                return null;
            }
    
            @Override
            public void enter() {
                Log.d("FSM", "Entry>>stateModify;");
            }
    
            @Override
            public State fireEvent(Event e) {
                return null;
            }
    
            @Override
            public void exit() {
                Log.d("FSM", "Exit<<stateModify;"); } }; public static void printMessage(Event e, String stateName) { Log.d("FSM", "Event-" + e.getID() + ">>" + stateName + ";");
        }
    
    };
    

    HSM 내부에 gap과 flush를 같이 관리가 되도록 데이터 형식으로 같이 정의 했다. 핸들러는 MainActivity의 startListener를 제어하기 위한 핸들러이다.
    MainActivity에서 생성자를 만들경우 자동으로 관련 주소를 넘겨오도록 했다.

    State간의 Action은 전이가 있을 때 실행된다. transition과 별개의 action을 실행하려면, state 내부에 넣어주면 될것 같다.

    DataGapFlush Class 정의

    DataGapFlush는 다음과 같이 정의 했다.

    public class DataGapFlush {
        private String[] gap;
        private String[] flush;
        private int gapIndex = 0;
        private int flushIndex = 0;
        private String VIN = "";
        private String tmpSpokenWord = "";
    
        public DataGapFlush() {
            gap = new String[10];
            flush = new String[10];
            //0부터 10까지 "Empty"를 입력..
    
            for (int i = 0; i < 10; i++) {
                gap[i] = "Empty";
                flush[i] = "Empty";
            }
    
            //DEBUG
            for (int i = 0; i < 8; i++) {
                gap[i] = "가";
            }
            //DEBUG
        }
    
        //gap을 0에서 10까지 "Empty"로 들어가 있는지 확인
        //모두 차있으면 true을 return..
        //중간에 비어 있으면 flase를 return
    
        public boolean checkGapFulled() {
            int i;
            for (i = 0; i < 10; i++) {
                if (gap[i] != "Empty") {
                    this.gapIndex = i;
                } else
                    break;
            }
    
            setGapIndex(i);
            if (this.gapIndex == 10)
                return true;
            else
                return false;
        }
    
        public boolean checkFlushFulled() {
            int i;
            for (i = 0; i < 10; i++) { if (flush[i] != "Empty") { this.flushIndex = i; } else break; } setFlushIndex(i); if (this.flushIndex == 10) return true; else return false; } public int getGapIndex() { checkGapFulled(); return this.gapIndex; } public int getFlushIndex() { checkFlushFulled(); return this.flushIndex; } public void setGapIndex(int i) { this.gapIndex = i; } public void setFlushIndex(int i) { this.flushIndex = i; } public boolean setGapIthwithN(int i, String value) { if (i >= 10)
                return false; //failed..
            else {
                gap[i] = value;
            }
            return true; //success
        }
    
        public boolean setFlushIthwithN(int i, String value) {
            if (i >= 10)
                return false; //failed..
            else {
                flush[i] = value;
            }
            return true; //success
        }
    
    
        public void setTmpWord(String words) {
            this.tmpSpokenWord = words;
        }
    
        public String getTmpWord() {
            return this.tmpSpokenWord;
        }
    
        public void emptyTmpWord() {
            this.tmpSpokenWord = "";
        }
    
        public String getData(boolean GAPFLUSH_FLAG, int NthData) {
            String tmp = "";
            if (NthData <= 9) {
                if (GAPFLUSH_FLAG == true) {
                    //GAP을 가져옴..
                    tmp = this.gap[NthData];
                } else
                    //Flush를 가져옴..
                    tmp = this.flush[NthData];
            }
            return tmp;
        }
    
    }
    
    

    구글 음성인식이 숫자로 자동으로 변환되면 좋은데, 텍스트로 입력이 들어온다. 10개까지 저장할 수 있도록 String으로 배열을 잡았다. gap, flush index는 어느 부분에 데이터를 넣을지 결정한는 부분이다. Hsm에서 DataGapFlush를 만들 때, 내부에 Empty로 10개를 만든다. Empty 문자열을 확인 후, 0번부터 입력을 한다.

    Event 정의

    package com.example.now0930.myapplication.hsm;
    
    /**
     * Created by now0930 on 17. 2. 26.
     */
    
    import com.example.now0930.myapplication.DataGapFlush;
    
    import java.util.EventObject;
    import java.util.Objects;
    import java.util.logging.Handler;
    
    public class Event extends EventObject {
        private int id;
        private int gapIndex = 0;
        private int flushIndex = 0;
        private DataGapFlush thisData;
        boolean SPEECHFLAG = false;
    
        private enum ID {JUSTONRESULT, GAPISFULLED, FLUSHISFULLED}
    
        ;
    
        public Event(Object o, DataGapFlush myDataIns, boolean flag) {
            super(o);
            this.thisData = myDataIns;
            this.SPEECHFLAG = flag;   //false이면 음성 입력이 없음..
        }
    
        //버튼을 눌렀을 경우, 이벤트 정의 함수..
        public Event(Object o, DataGapFlush myDataIns, int eventInstance) {
            super(o);
            this.thisData = myDataIns;
            this.id = eventInstance;
        }
    
    
        public Event(Object o, int index) {
            super(o);
            this.id = index;
        }
    
        //event 정의..
        //gap부터 채우고 flush를 채움..
        //gap not full, flush not full,onResult : id 1
        //gap full, flush not full : id 2
        //gap full, flush not full, onResult : id 3
        //gap not full, flush full : id not defined
        //gap full, flush full : id 4
    
    
        //음성인식 버튼을 누를 경우 : id 5
        //startListening 후, 입력이 없을 경우.: id6
        //startListening 후, 에러 발생. : id 6
    
        //버튼 눌름 정의..
        //1번을 눌렀을 때, Gap을 수정할 때. : 11
        //2번을 눌렀을 때, Gap을 수정할 때. : 12
        //3번을 눌렀을 때, Gap을 수정할 때. : 13
        //4번을 눌렀을 때, Gap을 수정할 때. : 14
        //5번을 눌렀을 때, Gap을 수정할 때. : 15
        //6번을 눌렀을 때, Gap을 수정할 때. : 16
        //7번을 눌렀을 때, Gap을 수정할 때. : 17
        //8번을 눌렀을 때, Gap을 수정할 때. : 18
        //9번을 눌렀을 때, Gap을 수정할 때. : 19
        //10번을 눌렀을 때, Gap을 수정할 때. : 20
        //1번을 눌렀을 때, Flush을 수정할 때. : 21
        //2번을 눌렀을 때, Flush을 수정할 때. : 22
        //3번을 눌렀을 때, Flush을 수정할 때. : 23
        //4번을 눌렀을 때, Flush을 수정할 때. : 24
        //5번을 눌렀을 때, Flush을 수정할 때. : 25
        //6번을 눌렀을 때, Flush을 수정할 때. : 26
        //7번을 눌렀을 때, Flush을 수정할 때. : 27
        //8번을 눌렀을 때, Flush을 수정할 때. : 28
        //9번을 눌렀을 때, Flush을 수정할 때. : 29
        //10번을 눌렀을 때, Flush을수정할 때. : 30
    
        public int getID() {
    
            //외부에서 미리 설정한 event id를 아래 부분에서 바꾸지 않도록 설정..
            if(this.id == 0) {
                if (SPEECHFLAG == true) {
                    //gap이 모두 차있음..
                    if (thisData.checkGapFulled() == false && thisData.checkFlushFulled() == false)
                        this.id = 1;
                    else if (thisData.checkGapFulled() == true && thisData.checkFlushFulled() == false)
                        this.id = 3;
                }
    
                /***
                 * 아래 부분은 데이터 기준으로 이벤트가 설정됨..
                else if (SPEECHFLAG == false) {
                    if (thisData.checkGapFulled() == true && thisData.checkFlushFulled() == false)
                        this.id = 2;
                    else if (thisData.checkGapFulled() == true && thisData.checkFlushFulled() == true)
                        this.id = 4;
                }
                 */
            }
            return this.id;
        }
    }
    
    

    이벤트는 위와 같이 했다. 책에서는 이벤트를 판단하는 함수가 간략해야 된다고 했는데, 내 능력으로는 이런 구현이 어려울 듯 하다. 사용할 모든 이벤트를 넣어 버렸다. 이벤트는 Hsm에서 e.getID() 함수를 call할 경우, 판단이 된다.
    내가 원하는 Event 처리는 다음과 같다.

    1. 음성인식 버튼을 눌렀을 경우 : e.id = 5
    2. 음성인식 후, 결과가 제대로 나왔을 경우 : e.id = 1, 3
    3. 음성인식 후, 결과가 에러가 발생할 경우 : e.id = 6
    4. gap의 데이터 10개를 모두 입력했을 경우 : e.id = 2
    5. flush의 데이터 10개를 모두 입력했을 경우 : e.id = 4

    이벤트는 MainActivity에서 thread로 실행을 했다. 위에서 문데되는 부분이 e.id가 2, 4일 경우이다. gap의 모든 데이터를 채워 버리면 다른 이벤트는 e.getID()를 실행할 때, 인식이 되지 않는다. 위 부분을 처리하기 위해서, 특정 state에서 한번만 일어나도록 했다.

    public State fireEvent(Event e) {
                //아래 e.getID를 실행하면
                //이벤트 정의시 내부 데이터에 의한 기준으로 하면
                //일정 시점 이후로는 그 동작만 계속됨..
                //내부 이벤트, 이부 이벤트로 분리.
                //내부 이벤트는 그 state에서만 실행되도록 정의
                boolean gapFlag = myGapFlush.checkGapFulled();
                if(gapFlag == true){
                    myHsm.this.transition(stateBatchRec_Flush);
                    return null;
                }
    
                switch (e.getID()) {
                    case 1:
                        printMessage(e, "stateBatchRec_Gap");
                        int i = myGapFlush.getGapIndex();
                        myGapFlush.setGapIthwithN(i, myGapFlush.getTmpWord());
                        myGapFlush.emptyTmpWord();
                        myHsm.this.transition(stateBatchRec_Gap);
                        myHandle.sendEmptyMessage(1);
                        return null;
                    /***
                    case 2:
                        myHsm.this.transition(stateBatchRec_Flush);
                        return null;
                     ***/
                    default:
                        break;
                }
                return getParent();
            }
    

    HSM 내부의 이벤트와 외부의 이벤트를 구분해야 할 듯 한데, 딱히 어떻게 할지를 모르겠다.

    Handler 정의 및 Message 전송

    JAVA는 어떤지 모르겠는데, android는 MainActivity내부에 handler를 정의하여 외부에서 메세지를 보내는 구조로 되어 있다. HSM에서 일정한 전이가 일어나면 MainActivity에 있는 SpeechRecognizer로 정의 되어잇는 startListening을 실행하도록 했다.

    MainActivity handler 정의

    public class MainActivity extends AppCompatActivity {
        Intent i;
        SpeechRecognizer mRecognizer;
        int TvIndex = 0;
        TextView[] TvGap = new TextView[10];
        TextView[] TvFlush = new TextView[10];
        //hsm에서 SpeechListener를 제어하기 위해서.
    
        Handler mHandler = new Handler() {
            @Override
            public void handleMessage(Message msg) {
                super.handleMessage(msg);
                //음성인식 시작부분..
                if (msg.what == 1)
                    mRecognizer.startListening(i);
            }
        };

    Hsm에서 handler 초기화 및 message 전송

    HSM을 만들 떄, MainActvity에서 만든 handler로 초기화 했다. 메세지는 구조는 잘 모르겠으나, 간단한 메세지를 sendEmptyMessage로 보냈다.

    public class myHsm extends Hsm {
        int myFoo;
        DataGapFlush myGapFlush;
        Handler myHandle;
    
    
        public myHsm(DataGapFlush myDataInst, Handler handle) {
            this.myGapFlush = myDataInst;
            this.myHandle = handle;
        }
    
    
    
    public State fireEvent(Event e) {
                switch (e.getID()) {
                    case 5:
                        printMessage(e, "stateBatchRec");
                        //myHandle.sendMessage(myHandle.obtainMessage());
                        myHandle.sendEmptyMessage(1);
                        myHsm.this.transition(stateBatchRec);
                        return null;
                    default:
                        break;
                }
                return getParent();
            }
    
  • HSM을 활용한 레고 장난감 만들기

    HSM을 활용한 레고 장난감 만들기

    기억하기 위해서 기록해 놓음..

    처음 다이어그램인데..수정.
    12113378_1289445931071053_7620380557359358782_o

    수정 다이어그램은 아래와 같다.
    20160908_225320

    기존 대비 S11을 지워 버렸다. S10에서 S11로 transiton이 발생할 때, 모터 동작이 끝나기 때문에 굳이 S11을 만들 필요가 없다. self transition으로 처리했다. state machine을 내가 제대로 이해하고 있는지 모르겠으나..

    S13에서도 self transition을 추가 했는데, 회전 동작시 장애물을 완전하게 피했는지를 확인하기 위해서이다. S13에서 이벤트를 다시 한번 확인하고, 장애물에 걸리면 전진동작 없이 회피 동작을 하도록 했다.

    가장 고민했던 부분은 S0은 event driven program이고 S1은 data driven program 방식으로 접근해야 할 것 같은데, 모든 ir센서로 읽어 들이는 모든 데이터를 event로 분류하기가 힘들었다. 이벤트로 설정하려면 배타적 관계도 확인해 봐야 하는데, 버튼 눌림여부 * 버퍼의 수치별 이벤트를 한개의 event로 처리하려면 경우의 수가 너무 많았다. 쿨하게 evnet는 아래와 같이 몇가지 경우만 잡았다. 나머지는 S1에 들어갔으므로 대략 무시하기로 했다.

    package hsm;
    
    import lejos.hardware.Button;
    
    public class Event {
    	  
    	  public enum mEvent {EnterPressed,DownPressed,GapIsWide,GapIsNarrow,Nothing};
    	  
    	  //Ir센서를 읽기 위한 부분..
    	  private float irValue;
    	  
    	  public Event(float tmp){
    		  this.irValue = tmp;
    	  }
    	 /* 
    	  public void setEventBuffer(float value){
    		  this.irValue = value;
    	  }
    	  */
    	  
    	  public mEvent getID(){
    		  
    		  mEvent thisEvent = mEvent.Nothing;
    		  
    		  //버퍼에서 데이터를 읽어 들임 
    
    		  if(Button.ENTER.isDown() == true)
    			  return mEvent.EnterPressed;
    
    		  if(Button.DOWN.isDown() == true)
    			  return mEvent.DownPressed;
    		  
    		  if(irValue > 10)
    		  	return mEvent.GapIsWide;
    		  
    		  if(irValue <= 10)
    			  return mEvent.GapIsNarrow;
    		 		  
    		  return thisEvent;
    	  }
    	  
    	  
    
    
    }

    일단 S1에 들어가면, irRead -> ir 버퍼 가공 -> 모터 동작의 방식으로 처리했는데, 이 방법도 좋지 않았다. 각 state별 process가 다른데, thread로 돌리면 이를 구분할 수 없게 된다. 따라서 아래와 같이 수정했다.

    S10,S13에서만 irRead 동작, S10,S12,S13에서는 모터 동작, 각 transition에 따른 버퍼값 수정..

    S0에서는 버튼이 눌림만 감시하기 때문에 event driven programming으로 접근했다.

    hsm의 c++ -> java 이식은 인터넷의 코드를 그대로 사용했다.
    hsm의 구조는 대략 아래와 같다.

    package hsm;
    
    import mypackage.IrBufferv2;
    import mypackage.MotorData;
    
    public class myHsmForLego extends Hsm{
    	private MotorData mMotorData;
    	
    	Runnable myIrRun;
    	Runnable myMotorRun;
    	Runnable myProcessRun;
    
    	
    
    
    	public myHsmForLego(MotorData motor,Runnable irTh, Runnable motorTh, Runnable ProcessTh){
    		this.mMotorData = motor;
    		
    		//thread를 전달하기 위한 부분
    		this.myIrRun = irTh; 
    		this.myMotorRun = motorTh;
    		this.myProcessRun = ProcessTh;
    
    	}
    	
    	@Override
    	public void init() {
    		// TODO Auto-generated method stub
    		System.out.print("Top-INIT;" );
    	    
    	    //초기화시 모터 목표를 0으로 설정
    		super.init();
    	}
    
    	@Override
    	public State fireInit() {
    		// TODO Auto-generated method stub
    	    System.out.print("INIT>>s;" );
    		return s0;
    	}
    
    	@Override
    	public void enter() {
    		// TODO Auto-generated method stub
    	    System.out.print("ENTRY>>s;" );
    
    	}
    	
    	@Override
    	public State fireEvent(Event e) {
    		// TODO Auto-generated method stub
    		return getParent();
    	}//fireEvent
    
    	@Override
    	public void exit() {
    		// TODO Auto-generated method stub
    	    System.out.print("EXIT<<s;" );
    	    System.out.print("\n");
    
    
    	}
    	
    	Hsm s = this;                 // This is just alias of current instance
    
    	
    	
    	State s0 = new State(s){
    		@Override
    		public State fireEvent(Event e) {
    			// TODO Auto-generated method stub
    			switch(e.getID())
    			{
    				case EnterPressed :
    					transition(s1);
    					return null;
    
    				default :
    					  break;
    			}
    			
    			return getParent();
    		}//fireEvent
    		
    		@Override
    		public State fireInit() {
    			// TODO Auto-generated method stub
    		    System.out.print("INIT>>s0;" );
    			return null;
    		}
    
    		@Override
    		public void enter() {
    			// TODO Auto-generated method stub
    		    System.out.print("ENTRY>>s0;" );
    			
    		}
    
    		@Override
    		public void exit() {
    			// TODO Auto-generated method stub
    		    System.out.print("EXIT<<s0;" );
    		    System.out.print("\n");
    		
    		}
    		
    	};//sate s0
    	
    	State s1 = new State(s){
    
    		@Override
    		public State fireInit() {
    			// TODO Auto-generated method stub
    		    System.out.print("INIT>>s1;" );
    			return s10;
    		}
    
    		@Override
    		public void enter() {
    			// TODO Auto-generated method stub
    		    System.out.print("ENTRY>>s1;" );
    	
    		}
    
    		@Override
    		public State fireEvent(Event e) {
    			// TODO Auto-generated method stub
    			switch(e.getID())
    				{
    					case DownPressed :
    						transition(s0);
    						return null;
    
    					default :
    						  break;
    				}
    			return null;
    		}
    
    		@Override
    		public void exit() {
    			// TODO Auto-generated method stub
    		    System.out.print("EXIT<<s1;" );
    		    System.out.print("\n");
    				
    		}
    		
    	};//state s1;
    	
    	
    
    	State s10 = new State(s1){
    
    		@Override
    		public State fireInit() {
    			// TODO Auto-generated method stub
    		    System.out.print("INIT>>s10;" );
    			return null;
    		}
    
    		@Override
    		public void enter() {
    			// TODO Auto-generated method stub
    			// IR 센서값을 버퍼에 저장..
    			// 이벤트를 판단할 수 있게됨..
    			myIrRun.run();
    		    System.out.print("ENTRY>>s10;" );
    	
    		}
    
    		@Override
    		public State fireEvent(Event e) {
    		
    			// TODO Auto-generated method stub
    			switch(e.getID())
    			{
    			case GapIsWide :
    				//모터 이동부분 추가.
    				// 아래 process run은 삭제필요..
    				// thread를 인수로 줄수 없음..
    				//myProcessRun.run();
    				mMotorData.storeTargetDistance(10, 10);
    				myMotorRun.run();
    				
    				//seft transition
    				transition(s10);
    				return null;
    
    			case GapIsNarrow :
    				mMotorData.storeTargetDistance(-50, -50);
    				myMotorRun.run();
    
    				transition(s12);
    				return null;
    
    			default :
    				break;
    			
    			}
    			return getParent();
    		}//fireEvent
    
    		
    		@Override
    		public void exit() {
    			// TODO Auto-generated method stub
    		    System.out.print("EXIT<<s10;" );
    			System.out.print("\n");
    
    		}
    			
    	};//state s10
    
    	
    	/* 아래 부분은 삭제..2016.09.08
    	State s11 = new State(s1){
    
    		@Override
    		public State fireInit() {
    			// TODO Auto-generated method stub
    		    System.out.print("INIT>>s11;" );
    			return null;
    		}
    
    		@Override
    		public void enter() {
    			// TODO Auto-generated method stub
    		    System.out.print("ENTRY>>s11;" );
    			
    		}
    
    		@Override
    		public State fireEvent(Event e) {
    			// TODO Auto-generated method stub
    			switch(e.getID())
    			{
    			case GapIsWide:
    				//seft transition
    				//모터 이동부분 추가.
    				myIrRun.run();
    				myProcessRun.run();
    				myMotorRun.run();
    				transition(s11);
    				//모터 이동부분 추가.
    				return null;
    
    			case GapIsNarrow:
    				//모터 이동부분 추가.
    				mMotorData.storeTargetDistance(-10, -10);
    				myMotorRun.run();
    
    				transition(s12);
    				return null;
    
    			default :
    				break;
    			
    			}
    			
    			return getParent();
    		}//fireEvent
    
    		@Override
    		public void exit() {
    			// TODO Auto-generated method stub
    			System.out.print("EXIT<<s11;" );
    			System.out.print("\n");
    		}
    			
    	};//State s11
    	*/
    	
    	State s12 = new State(s1){
    
    		@Override
    		public State fireInit() {
    			// TODO Auto-generated method stub
    		    System.out.print("INIT>>s12;" );
    
    			return null;
    		}
    
    		@Override
    		public void enter() {
    			// TODO Auto-generated method stub
    		    System.out.print("ENTRY>>s12;" );
    		}
    
    		@Override
    		public State fireEvent(Event e) {
    			// TODO Auto-generated method stub
    			//모터 이동부분 추가.
    			mMotorData.storeTargetDistance(50, -50);
    			myMotorRun.run();
    
    			transition(s13);
    			return getParent();
    
    		}//fireEvent
    
    		@Override
    		public void exit() {
    			// TODO Auto-generated method stub
    			System.out.print("EXIT<<s12;" );
    			System.out.print("\n");
    
    		}
    		
    		
    	};//State s12
    
    	State s13 = new State(s1){
    
    		@Override
    		public State fireInit() {
    			// TODO Auto-generated method stub
    			System.out.print("INIT>>s13;" );
    
    			return null;
    		}
    
    		@Override
    		public void enter() {
    			// TODO Auto-generated method stub
    			// 충분하게 돌았는지 확인.
    			myIrRun.run();
    			System.out.print("ENTRY>>s13;" );
    
    			
    		}
    
    		@Override
    		public State fireEvent(Event e) {
    			// TODO Auto-generated method stub
    			switch(e.getID())
    				{
    					case GapIsWide :
    						//모터 이동부분 추가.
    						// 아래 process run은 삭제필요..
    						// thread를 인수로 줄수 없음..
    						//myProcessRun.run();
    						transition(s10);
    						return null;
    
    					case GapIsNarrow :
    						//충분하게 돌지 못해더 더 돌아야 된단고 판단.
    						mMotorData.storeTargetDistance(50, -50);
    						myMotorRun.run();
    						transition(s10);
    						return null;
    
    					default :
    						break;
    
    			}
    			return getParent();
    		}//fireEvent
    
    		@Override
    		public void exit() {
    			// TODO Auto-generated method stub
    			System.out.print("EXIT<<s13;" );
    			System.out.print("\n");
    
    			
    		}
    
    		
    	};//State 13
    
    
    		
    }//myHsmForLego
    

    다음에는 iR센서에 모터를 달아서 한바퀴 돌려보고 몇도로 움직여야 판단까지 하고 싶으나, 갈길이 멀어 보인다. 일단 이걸 다 뜯을 생각을 하니 머리가 아프다..한번 뜯으면 다시 조립 못할 것 같다..
    20160908_232920