[카테고리:] 생활코딩

  • 안드로이드 앱 개발 일지, 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();
            }
    
  • 스토리지의 모든 경우를 python permutation으로 구현

    스토리지의 모든 경우를 python으로 출력하기

    스토리지의 모든 경우를 python으로 구현하여, 모든 경우에 대한 행동할 값을 테이블로 작성이 가능하다. 이후 중복된 데이터를 엑셀로 정리가 가능하다. 각 경우에 대한 값을 입력으로 보고, 어떻게 행동할 지를 출력으로 임의로 작성하면 X->Y의 관계를 가르칠 수 있다.

    스토리지의 상황

    어느셀에서대차를뺄것인가

    라인 프로그램을 작성하다 보면, 모든 경우에서 어는 부분을 처리해주는 부분이 없어 중단되는 상황이 있다. 셔틀과 스토리지로 구성된 라인이 있다고 할 때, 마지막 공정이 대차를 아래와 같이 세가지 경우 중 하나로 처리할 수 있다.

    1. 생산 데이터와 마지막 공정 대차가 일치하여 시작 공정으로 다시 돌림
    2. 생산 데이터와 마지막 공정 대차가 같지 않아, 스토리지에서 새로운 대차를 빼고, 마지막 대차는 빈 셀로 넣기
      • 간단하게 만들기 위해서, 스토리지에 생산 데이터의 대차가 있어도 라인에 있는 대차를 먼저 사용

    3. 생산 데이터와 마지막 공정 대차가 같지 않아, 다음 턴에서 맞을 것으로 보고 마지막 대차를 시작 공정으로 다시 돌림

    Python Permutation으로 구현하기

    모든 경우에 대한 데이터를 파악하고, 사용자가 엑셀에서 함수를 데이터를 입력하다면 쉽게 입력/출력 테이블을 만들 수 있다. 모든 경우를 엑셀로 출력하는 vba가 있으나, 사용하기 복잡해 보인다. stack overflow 인터넷의 누군가 python으로 간단하게 구현할 수 있는 코드를 작성하여 참조할 수 있도록 정리했다. 왜 pyhon을 많은 사람들을 쓰는지 이해가 된다.

    import itertools
    
    #all cell are full
    mylist8 = ["UM","UM","UM","UM","UM","UM","UM"]
    mylist1 = ["QF","UM","UM","UM","UM","UM","UM"]
    mylist2 = ["QF","QF","UM","UM","UM","UM","UM"]
    mylist3 = ["QF","QF","QF","UM","UM","UM","UM"]
    mylist4 = ["QF","QF","QF","QF","UM","UM","UM"]
    mylist5 = ["QF","QF","QF","QF","QF","UM","UM"]
    mylist6 = ["QF","QF","QF","QF","QF","QF","UM"]
    mylist7 = ["QF","QF","QF","QF","QF","QF","QF"]
    
    #1 cell is empty
    mylist9 = ["UM","UM","UM","UM","UM","UM","Vacant"]
    mylist10 = ["QF","UM","UM","UM","UM","UM","Vacant"]
    mylist11 = ["QF","QF","UM","UM","UM","UM","Vacant"]
    mylist12 = ["QF","QF","QF","UM","UM","UM","Vacant"]
    mylist13 = ["QF","QF","QF","QF","UM","UM","Vacant"]
    mylist14 = ["QF","QF","QF","QF","QF","UM","Vacant"]
    mylist15 = ["QF","QF","QF","QF","QF","QF","Vacant"]
    
    #2 cell are empty
    mylist16 = ["UM","UM","UM","UM","UM","Vacant","Vacant"]
    mylist17 = ["QF","UM","UM","UM","UM","Vacant","Vacant"]
    mylist18 = ["QF","QF","UM","UM","UM","Vacant","Vacant"]
    mylist19 = ["QF","QF","QF","UM","UM","Vacant","Vacant"]
    mylist20 = ["QF","QF","QF","QF","UM","Vacant","Vacant"]
    mylist21 = ["QF","QF","QF","QF","QF","Vacant","Vacant"]
    
    #3 cell are empty
    mylist22 = ["UM","UM","UM","UM","Vacant","Vacant","Vacant"]
    mylist23 = ["QF","UM","UM","UM","Vacant","Vacant","Vacant"]
    mylist24 = ["QF","QF","UM","UM","Vacant","Vacant","Vacant"]
    mylist25 = ["QF","QF","QF","UM","Vacant","Vacant","Vacant"]
    mylist26 = ["QF","QF","QF","QF","Vacant","Vacant","Vacant"]
    
    #4 cell are empty
    mylist27 = ["UM","UM","UM","Vacant","Vacant","Vacant","Vacant"]
    mylist28 = ["QF","UM","UM","Vacant","Vacant","Vacant","Vacant"]
    mylist29 = ["QF","QF","UM","Vacant","Vacant","Vacant","Vacant"]
    mylist30 = ["QF","QF","QF","Vacant","Vacant","Vacant","Vacant"]
    
    #5 cell are empty
    mylist31 = ["UM","UM","Vacant","Vacant","Vacant","Vacant","Vacant"]
    mylist32 = ["QF","UM","Vacant","Vacant","Vacant","Vacant","Vacant"]
    mylist33 = ["QF","QF","Vacant","Vacant","Vacant","Vacant","Vacant"]
    
    #6 cell are empty
    mylist34 = ["UM","Vacant","Vacant","Vacant","Vacant","Vacant","Vacant"]
    mylist35 = ["QF","Vacant","Vacant","Vacant","Vacant","Vacant","Vacant"]
    
    
    myPermutation1 = itertools.permutations(mylist1)
    myPermutation2 = itertools.permutations(mylist2)
    myPermutation3 = itertools.permutations(mylist3)
    myPermutation4 = itertools.permutations(mylist4)
    myPermutation5 = itertools.permutations(mylist5)
    myPermutation6 = itertools.permutations(mylist6)
    myPermutation7 = itertools.permutations(mylist7)
    myPermutation8 = itertools.permutations(mylist8)
    myPermutation9 = itertools.permutations(mylist9)
    myPermutation10 = itertools.permutations(mylist10)
    myPermutation11 = itertools.permutations(mylist11)
    myPermutation12 = itertools.permutations(mylist12)
    myPermutation13 = itertools.permutations(mylist13)
    myPermutation14 = itertools.permutations(mylist14)
    myPermutation15 = itertools.permutations(mylist15)
    myPermutation16 = itertools.permutations(mylist16)
    myPermutation17 = itertools.permutations(mylist17)
    myPermutation18 = itertools.permutations(mylist18)
    myPermutation19 = itertools.permutations(mylist19)
    myPermutation20 = itertools.permutations(mylist20)
    myPermutation21 = itertools.permutations(mylist21)
    myPermutation22 = itertools.permutations(mylist22)
    myPermutation23 = itertools.permutations(mylist23)
    myPermutation24 = itertools.permutations(mylist24)
    myPermutation25 = itertools.permutations(mylist25)
    myPermutation26 = itertools.permutations(mylist26)
    myPermutation27 = itertools.permutations(mylist27)
    myPermutation28 = itertools.permutations(mylist28)
    myPermutation29 = itertools.permutations(mylist29)
    myPermutation30 = itertools.permutations(mylist30)
    myPermutation31 = itertools.permutations(mylist31)
    myPermutation32 = itertools.permutations(mylist32)
    myPermutation33 = itertools.permutations(mylist33)
    myPermutation34 = itertools.permutations(mylist34)
    myPermutation35 = itertools.permutations(mylist35)
    
    #myCombination = itertools.combinations(mylist, 2)
    
    for i in myPermutation1:
        print i
    for i in myPermutation2:
        print i
    for i in myPermutation3:
        print i
    for i in myPermutation4:
        print i
    for i in myPermutation5:
        print i
    for i in myPermutation6:
        print i
    for i in myPermutation7:
        print i
    for i in myPermutation8:
        print i
    for i in myPermutation9:
        print i
    for i in myPermutation10:
        print i
    for i in myPermutation11:
        print i
    for i in myPermutation12:
        print i
    for i in myPermutation13:
        print i
    for i in myPermutation14:
        print i
    for i in myPermutation15:
        print i
    for i in myPermutation16:
        print i
    for i in myPermutation17:
        print i
    for i in myPermutation18:
        print i
    for i in myPermutation19:
        print i
    for i in myPermutation20:
        print i
    for i in myPermutation21:
        print i
    for i in myPermutation22:
        print i
    for i in myPermutation23:
        print i
    for i in myPermutation24:
        print i
    for i in myPermutation25:
        print i
    for i in myPermutation26:
        print i
    for i in myPermutation27:
        print i
    for i in myPermutation28:
        print i
    for i in myPermutation29:
        print i
    for i in myPermutation30:
        print i
    for i in myPermutation31:
        print i
    for i in myPermutation32:
        print i
    for i in myPermutation33:
        print i
    for i in myPermutation34:
        print i
    for i in myPermutation35:
        print i

    python에 익숙하면 더 세련된 형태로 할 수 있겠으나, 일회성으로 하기 때문에 그냥 노가다로 해결했다.

    엑셀로 중복 데이터 제거

    출력 결과는 중복되는 데이터들이 많은데, 간단한 함수로 중복을 제거할 수 있다. 이를 그냥 입력으로도 사용 가능하나, 중복되는 데이터는 정확도가 떨어지므로, 정리해 주는게 맞아 보인다.
    아래는 정리한 테이블의 데이터 셋이다.
    170217datasetv4

  • 가속도 센서로 pitch, roll 구하기

    가속도 센서로 pitch, roll 구하기

    가속도 센서로 pitch, roll 알아내기
    생각을 정리하기 위해서 기록으로 남긴다..

    처음에 3축 가속도 센서가 있으면 pitch, roll, yaw를 알 수 있다고 생각했다. 그러나 이는 불가능하다..
    아래 문서를 읽어보니 pitch와 roll 2개의 값만 알 수 있다. 나머지 yaw는 compass 센서로 확인이 가능하다.
    AN3461

    위 문서를 읽다 보면, 아래와 같은 식이 어떻게 나왔는지 파악하는데 좀 걸렸다.


    x축으로 돌렸을 경우(roll), y,z 평면으로 보면 위와 같은 그림이 된다. 나머지는 안해봤는데, 아마도 이런 식으로 하면 되지 않을까 한다. roll, pitch, yaw 적용하는 순서에 따라 6개의 방정식이 나오는데, 4개는 못쓰는 식이다. 경우의 수가 너무 많아서 그런 듯 싶다. 나머지 2개는 사용할 수 있는데, 두 개의 식이 다른 결과를 갖는다.
    tan(pi)는 cos(theta)sin(pi)/cos(theta)cos(pi)로 간단하게 구할 수 있는데, tan(theta)는 3개의 식을 G_px, G_py, G_pz에 대해서 풀어써야 된다.

    암튼 내가 가진 gyro+accelemeter는 배치가 아래와 같이 되어있다.

    x에서 y로 갈 경우, z가 나와야 되는데, 들어가니 left handed coordinate system인듯 한데..
    X표시가 들어가는 방향. ●가 나오는 방향이다.
    left handed, right handed coordinate

    left handed
    right handed

    배치된 그림의 z축을 흔들리게 설치를 했다. z축이 회전이라 yaw를 알아야 한다고 처음에 판단 했으나, z축을 x축으로 변환을 하고, roll을 구하는 방법으로 해야 겠다. 난 한개의 값만 필요하니..
    몇번의 삽질로..아래 식을 참조하여…대략 구할 수 있었다.

    가속도 센서로 각도를 구하는 공식..

    accelerationX = (signed int)(((signed int)rawData_X) * 3.9);
    accelerationY = (signed int)(((signed int)rawData_Y) * 3.9);
    accelerationZ = (signed int)(((signed int)rawData_Z) * 3.9);
    pitch = 180 * atan (accelerationX/sqrt(accelerationY*accelerationY + accelerationZ*accelerationZ))/M_PI;

    roll = 180 * atan (accelerationY/sqrt(accelerationX*accelerationX + accelerationZ*accelerationZ))/M_PI;
    roll = 180 * atan (-accelerationY/accelerationZ) / M_PI;

    yaw = 180 * atan (accelerationZ/sqrt(accelerationX*accelerationX + accelerationZ*accelerationZ))/M_PI;

    위 식이 틀린 부분도 있어 보인다.
    MMA7455L 가속도 센서를 사용하는데, 첨부로 data sheet를 넣어둔다..
    MMA7455L
    여기서 더 나가면, 센서 드라이버를 어떻게 구현했는지도 찾아봐야 할 것도 같다..

  • Lego NXT에 dimu 사용하기

    https://nxttime.wordpress.com/2011/11/22/imu-sensor-software/

    By request I will publish the Lejos software for the dIMU sensor.

    The software contains of drivers for the Dexter Industries IMU sensor, programs to calibrate the sensor and the IMU filter that I wrote about on this blog before. The filter can be used for other IMU sensors as well. Also included is the sample program that utilizes this filter. It is the very same program I used in the video’s published in a previous post. There is no formal support and no warranty. If you are going to use it you will have to rely on your own wit, the javadoc and on this blog.
    Download and install

    You can download the software from this link. After downloading you will have to extract it keeping the directory structure intact. Make sure yor compiler can find the code.
    Using the IMU drivers

    The dIMU consists of two sensors, a gyroscope and a accelerometer, each has it’s own driver. The gyroscope driver is called L3G4200D and the driver for the accelerometer is MMA7455L. (I use the technical names of the sensors a driver names.) On top of these drivers I made user interfaces that allowes you to examine, configure and calibrate the sensors. The user interfaces are called L3G4200D_E and MMA7455L_E respectively. You can use these programs as driver as well. It gives your programs access to the additional functionality but it needs the NXT screen and might result in larger programs. There is a sample program included that gives access to the user interfaces, it is called testDIMU.

    This is how you enable the drivers in your code,

    SensorPort.S4.i2cEnable(I2CPort.HIGH_SPEED);
    MMA7455L accel = new MMA7455L(SensorPort.S4);
    L3G4200D gyro = new L3G4200D(SensorPort.S4);

     

    The first line instructs Lejos to use high speed I2C. The sensors support this.

    This is how you get data (rate, acceleration and tilt) from the sensors.

    float[] rate = new float[3];
    gyro.fetchAllRate(rate);
    
    float[] accel = new float[3];
    accel.fetchAllAccel(accel);
    
    float[] tilt = new float[3];
    accel.fetchAllTilt(tilt);

    As you can see the drivers return data from all three axis is one call. If you just need data from one axis you can get it from the arrays. The order is X, Y, Z. The gyro returns degrees per second by default. The accelerometer returns Milli-G for acceleration and Degrees for tilt, also by default. If you want to use other units in your programs you can change the default values like this.

    gyro.setRateUnit(RateUnits.RPS);
    accel.setAccelUnit(AccelUnits.MS2);
    accel.setTiltUnit(TiltUnits.RADIANS);

    This changes the default units to radians per second, meter per second^2 and radians respectively. Other units are available, check the javaDoc. The default unit can be overridden per data request by specifying the desired unit as second parameter in the FetchAll… methods.
    Configuring the sensors

    The gyro can be configured for dynamic range and sample rate using the setRange and setSampleRate methods. As a rule one should select the lowest value that your application allows for. This gives data of best possible quality.

    The accelerometer cannot be configured. I found this of little use.
    Calibrating the sensors

    The gyro of my dIMU doesn’t really need calibrating. however there is the possibility to do so. Calibration is started b calling gyro.calculateOffset(). During calibration the sensor should remain motionless. Calibration settings of the gyro are not stored, so they are lost when your program terminates. (Storing calibration settings of gyro sensors is of no use as the calibration values depend on environmental factors and are not stable over time.)

    The accelerometer needs calibration. The user interface driver provides functionality to calibrate the sensor and to store the calibration settings. The (base) driver looks for stored calibration settings upon initialization and loads these automatically when they are available. Calibration settings of the accelerometer are stable over time so you’ll need to do this just once. Each of the three axes has to be calibrated separately. To calibrate an axis one has to point it straight up first and hold still while the calibration routine collects data samples. Then the axis has to be pointed straight down and held still for some time. Follow the on screen instructions and do not forget to save the settings after calibration.
    Using the IMU filter

    The IMU filter can be used with any three-axis gyro and any three-axis accelerometer as long as their drivers implement the RataData and TiltData interfaces. This is how you initialise the filter

    NLCFilter attitude = new NLCFilter(gyro, accel);
    attitude.start();

    The two parameters to the filter constructor are the gyro driver and accelerometer driver. One could leave out the accelerometer, the filter will work but values will drift over time. The second line of code starts the filter. The filter needs 2 to 5 seconds to settle at start up, therefore you need to keep the sensor motionless and more or less level for a few seconds. You can find out if the filter is ready settling with the Initializing() method.

    The IMU filter keeps track of the attitude, or pose, of your sensor/robot. You can query the roll, pitch and yaw angles this way

    attitude.setTiltUnit(TiltUnits.RADIANS);
    float roll=attitude.getRoll();
    float pitch=attitude.getPitch();
    float yaw=attitude.getYaw();

    or

    float[] tilt = new float[3];
    attitude.fetchAllTilt(tilt);

    By default these methods return angles in radians. You can change this by setting a different unit with the setTiltUnit() method.

    You can also use the filter to transform data from a body frame to a world frame. This is useful if another sensor returns data that needs to be corrected for the robots current attitude. the next example transforms a distance returned by a UltraSonic sensor to world coordinates. The example assumes the us and IMU sensors are aligned and that the US sensor measures along the X-axis.

    Matrix usBody=new Matrix(1,3);
    Matrix usWorld=null;
    us = new UltrasonicSensor(SensorPort.S1);
    usBody.set(0,0,us.getDistance());
    usWorld=attitude.transformToWorld(usBody);

    The matrix usWorld now contains the distance from sensor to the detected object over the X, Y and Z axis.
    Configuring and tuning the IMU filter

    By default the IMU filter updates as often as possible. It’s update frequency is over 100 Hertz. To free computer resources one can lower the update frequency by using the setFrequency() method. The filter will try to match the specified frequency. A parameter value of 0 will run the filter at maximum speed.

    The filter can be tuned to increase the quality of its output. I advise you not to tune the filter until you are familiar with it and understand its inner workings. Tuning consists of altering the P and I values of it’s internal PI controller. The I value takes care of gyro drift cancellation and the P value controls how fast attitude is corrected by use of tilt data from the accelerometer. The values can be modified by using the setKp() and setKi() methods.

    There are two ways the filter can assist you in tuning. It keeps track of the integral of absolute errors, or absI. This is a measure of the total error of the filter over time. The smaller the error (given a fixed period) the better the filter performs. /* The filter also allows you to send data over bluetooth to the pc for examination. For this one has to use the NXTChartingLogger on the pc that is part of the Lejos distribution. You instruct the filter to send its state to the pc by supplying a NXTDataLogger object with the setDataLogger() method. */
    Running the demo program

    The demo program is called testIMU. At startup of this program the sensor must be horizontal and motionless. The sensor is assumed to be aligned ith the NXT brick with the sensor plug facing to the same direction as the sensor ports. Once you see the wire frame you can start moving the sensor.The demo has four display modes:

    Wire frame. Here it shows a wire frame of the sensor on the NXT screen
    Rotation matrix. The screen will show the content of the rotation matrix. In this matrix the current attitude is stored. The matrix is also used to convert body coordinates to world coordinates by matrix multiplication..
    Roll, Pitch, Yaw. The screen shows the Roll, Pitch, Yaw angles of the sensor.
    Update speed. The screen shows the actual update speed of the filter.

    You can browse through the display modes by using the arrow keys. The enter key resets the filter. The clip below shows the demo program in action in wire frame mode.