Let’s go!

  • 하드디스크에서 나온 옛날 사진

    하드디스크에서 나온 옛날 사진

    Sorry you have no rights to view this post!

  • WordPress로 에버노트 대신하기

    WordPress로 에버노트 대신하기

    워드 프레스 템플릿으로 에버노트 대신하기

    에버노트를 한 5년정도 잘 사용했었다. 작년에 에버노트의 내용을 백업하기 위해 database에 접근했으나, 에버노트 제작사에서 접근을 허용하지 않았다. 관련 프로그램들이 있었으나, 잘 되지 않았고, 데이터가 너무 많은지 파일을 받는데 한계가 있었다. 내가 만든 데이터인데 내가 제대로 소유를 못하고 있다고 생각하니 씁씁했다. 이번 기회에 에버노트를 떠나기로 하고, WordPress로 만든 블로그에 에버노트를 대신할 기능을 추가했다. 기능은 부족하지만, 나름 써보니 괜찮은 듯 하다. 텍스트는 어느 정도 입력, 조회, 수정이 가능했었고, 오늘 몇번의 시행착오 끝에 한개의 사진을 추가 가능하도록 했다.

    페이지로 php를 제공하는 템플릿 만들기

    워드프레스는 기본으로 php를 사용할 수 없다. 다행히? 사용자가 page에서 템플릿을 만든다면 사용할 수 있다. 작업을 조회/수정/입력하는 템플릿을 3개를 만들었다.

    작업 조회하는 php

    이 템플릿은 mysql에 접근하여 완료되지 않은 작업을 표시한다. 사용자가 작업을 추가할 경우, 그룹ID를 할당하는데, 이 그룹ID를 기준으로 작업을 표시한다. mysql의 database는 두 개를 만들었는데 하나는 작업 내용이고, 하나는 이미지 파일의 경로이다. 조회를 눌렀을 때, grpId로 그림의 경로가 있으면 이를 화면에 표시한다.

    wordpress 설치 디렉토리 중 wp-content가 내용을 담당하고 있다. 사용하는 theme의 경로로 이동한 다음 다음 3개의 파일을 만들었다.
    myJob.php, myJobInsert.php, myJobModify.php
    아래 Template Name을 원하는 내용으로 변경하고 page를 만들 때 이를 선택하면 해당 파일의 php 기능을 사용할 수 있다.

    <?php /* Template Name: myJob*/ ?>
    <?php
    get_header(); ?>
    
    <div id="main-content" class="main-content">
    
    <?php
        if ( is_front_page() && twentyfourteen_has_featured_posts() ) { 
            // Include the featured content template.
            get_template_part( 'featured-content' );
    
        }   
    ?>
    

    대략 아래와 같이 작업을 한다.
    1. 로그인이 되어 있는지 확인
    2. mysql user와 password를 시스템에서 읽어옮
    3. 데이터 베이스에 연결
    4. query를 날림..쿼리는 대략 다음과 같이 했다.

    $query="select * from
            (select * from  
            (select* from `workList` order by `id` desc, `업데이트일` desc) as target 
    group by `grpId`)
    as target2 where year(`완료일`) = '0000' order by `마감일` asc";
    

    php에서 template table을 만들 수 있으나, 셰션이 끊기면 임시 테이블도 없어진다. 귀찬고 데이터도 얼마 되지 않아 select를 중첩했다.
    5. query의 결과를 html로 표시해 주는 부분
    6. 이미지가 있으면 이미지도 같이 표시

    작업을 입력하는 php

    이 템플릿은 그룹ID를 할당하여 작업을 입력한다. 오늘 사진도 한장 입력을 할 수 있도록 수정했다. submit 버튼을 누르면 가장 마지막의 GrpId로 오늘 날자를 업데이트 날자로 하여 새로 항목을 만든다. 파일을 업로드 했다면, 파일 경로를 내용에 대한 데이터와 연결한다.

    템플릿은 위와 같은 방법을 만들었다. html에서 form을 설정하여 해당 내용을 $_POST로 데이터 베이스에 변수로 들어가도록 했다.
    1. 로그인 확인 및 데이터베이스에 연결
    2. 가장 마지막의 grpId를 확인
    3. +1을 하여 사용할 grpId로 설정
    4. 오늘 날자, 오늘 날자+14일을 확인하여 업데이트일, 목표일로 자동 설정
    5. form으로 해당하는 내용을 표시
    6. 이미지를 입력할 경우, 파일 이름과 경로를 설정
    7. 사용자가 submit 버튼을 누르면 내용과 이미지의 경로를 데이터베이스에 입력하고, 이미지를 설정한 경로에 업로드

    작업을 수정하는 php

    조회된 작업 중, 수정을 원하는 부분이 있으면 grpId를 확인해야 한다. 작업을 수정한는 페이지에서 Id를 입력하여 내용을 표시한 다음, 내가 원하는 내용으로 업데이트한다. 조회 버튼을 눌렀을 경우, 가장 나중에 작업된 내용이 표시된다.

  • 안드로이드 앱 개발 일지, 끝

    안드로이드 앱 개발 일지, 끝

    안드로이드 6.0 권한 설정

    안드로이드 6.0으로 넘어가면서 구글이 앱을 실행할 때 권한을 요청하도록 정책을 변경했다. 사용자들은 앱이 어떤 권한을 사용하는지 확실히 알지만, 그만큼의 귀찮음이 개발자에게 전가 되었다. 사용자는 많고, 개발자는 적으니 보통 개발자가 불편함을 갖는게 최대 다수의 최대 행복의 기준에 맞아 보인다. 그러나 사용자와 개발자의 숫자가 비슷할 경우는 그냥 일만 늘어난다. 개발자들이 쉽게 사용할 수 있게 구글이 관련 기능을 패키지 형식으로 제공하면 좋았겠지만, 그렇지는 않았다. 위 기준을 적용하면 구글의 갑질이라 볼 수 있다. 인터넷에 안드로이드 6.0 권한으로 쳐보면 많은 불만들이 보인다. 다행히 누군가 인터넷에 쉽게 구현할 수 있도록 코드를 공개했다.
    [안드로이드]유용한 라이브러리 – TedPermission(마시멜로우 권한체크)
    안드로이드 스튜디오에서 gradle에 주소를 넣어주면 소스를 다운받아 컴파일 해주나보다. 몇 줄 추가하여 바로 사용했다. 구현까지 10분도 안걸렸다!!!

    완료 동영상 및 소감


    -를 입력하기 어려워 보이는데, 나중에 좋은 생각이 나면 보완하기로 하고, 일단 여기에서 끝낸다.

    내가 java를 잘 알지 못했지만 끝까지 개발해보니 보람 있다. 직접 쓰고, 인터넷에서 가져다 쓴 코드가 완벽하지 않다. 내가 개념을 제대로 모르고 쓴 부분도 있고, 버그가 생길 느슨한 부분도 있다. 나는 이런 생각으로 무엇인가 새롭게 시도하는데 주저하고, 시간을 오래 사용한다. 그러나, 이런 상황은 소비자가 시장에서 제품을 사도 똑같다.

    자동차에 사용되는 기술을 예를 들어보자. 제조사는 새로운 기술이 적용된 제품을 소비자에게 팔 때, 자기네 기술이 완벽하다고 홍보한다.(뻥을 친다.) 그러나 제조사가 새로운 기술을 구현할 때 그것이 완벽할 수 없다. 제조사가 이런 사실을 공개하지 않지만 다수의 프로그램 회사들이 제품 출시 후 패치를 하는데 이를 보면 확실하다. 제조사는 다른 회사들보다 잘 만들거나, 동등하게 만든다. 제조사가 완전하지 않은 기술로 만든 제품을 소비자에게 팔아 이익을 남긴다고 비난할 수 있으나, 과거 그래왔고 그러면서 기술이 발전했다. 이렇게 생각하니 속편하다.

    내가 전문 개발자는 아니지만 이 개발로 필요한 도구를 어느 정도 만들어 사용할 수 있게 되었다. 특히 휴대폰은 여러 목적사용이 가능하여 많은 이점이 있다. 좋은 시대 태어나 시대가 주는 이점을 잘 받아먹고 있다.

    프로그램을 개발하는데 여러 방법이 있지만, fsm가 가장 합리적으로 보인다. fsm의 신도?로, 믿음을 실천할 수 있어 보람 되다. 정작 프로 개발자들은 fsm을 잘 사용하지 않아 보인다. 퀀텀 프로그램의 창시자? Miro Samek과 C++을 자바로 포팅한 Alexei Krasnopolski가 특히 고맙다. 내 취미생활에 신념을 넣어주신 분들이다. 음성인식을 자유롭게 사용하도록 공개???했고, 장난감을 만들어준 구글도 고맙다. 인터넷의 여러 오픈소스 개발자들에게도 고맙다.

    전체코드

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

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

    안드로이드 앱 개발 일지, 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에서는 권한에 대한 정책이 변경되었다. 이 분을 수정해야 대부분 휴대폰에서 동작이 가능해 보인다. 마지막 배열에 써야 되는데, 가끔 중간에 데이터가 들어간다. 왜그런지 모르겟으나…수정 버튼을 눌러야 겠다..

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

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

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

    버튼을 눌렀을 겨우, 다른 activity 실행

    안드로이드 스튜디오를 실행하면 기본으로 하나의 activity를 가지고 있다. 앱을 실행할 경우, 처음 보여주는 Activity는 번호만 나와있어 사용자가 어느 방향으로 측정할 지 모른다. 버튼을 하나 누르면 번호와 측정 부분을 보여줘야 한다. 이렇게 하기 위해 MainActivity에서 버튼을 하나 만들었고, 사용자가 이를 눌렀을 경우 subActivity가 실행되도록 했다. 여기에서 참조 했다.
    아래는 구현한 코드이다.

    public class MainActivity extends AppCompatActivity {
    
        Intent subActivity;
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
            subActivity = new Intent(this, LayoutActivity.class);
    
            Button BtnLayout = (Button)findViewById(R.id.Layout);
            BtnLayout.setOnClickListener(mLayoutListener);
    
        Button.OnClickListener mLayoutListener = new View.OnClickListener(){
            @Override
            public void onClick(View v) {
                startActivity(subActivity);
            }
        };
    }

    subActivity라고 LayoutActivity.java로 아래와 같이 만들었다. setContentView의 layout_main은 res의 layout의 경로이다.

    public class LayoutActivity extends Activity {
        Intent LayoutIntent;
        @Override
        protected void onCreate(@Nullable Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.layout_main);
        }
    }

    layout을 아래와 같이 새로 만들어 주고, LayoutActivity에 알려 줬다.

    Manifest에 등록하기

    이렇게 하고 버튼을 누를 경우, 빈 화면이 보여야 되는데, 에러가 발생한다. Manifest에 아래와 같이 등록한다.

    <?xml version="1.0" encoding="utf-8"?>
    <manifest xmlns:android="http://schemas.android.com/apk/res/android"
        package="com.example.now0930.myapplication">
    
        <application
            android:allowBackup="true"
            android:icon="@mipmap/ic_launcher"
            android:label="@string/app_name"
            android:supportsRtl="true"
            android:theme="@style/AppTheme">
            <activity
                android:name=".MainActivity"
                android:label="@string/app_name"
                android:theme="@style/AppTheme.NoActionBar">
                <intent-filter>
                    <action android:name="android.intent.action.MAIN" />
    
                    <category android:name="android.intent.category.LAUNCHER" />
                </intent-filter>
            </activity>
            <activity
                android:name=".LayoutActivity">
    
            </activity>
        </application>
    
        <uses-permission android:name="android.permission.INTERNET"/>
        <uses-permission android:name="android.permission.RECORD_AUDIO"/>
        <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
    
    </manifest>

    사용자 바탕화면 설정하기

    새로운 activity에는 바탕화면을 설정하고 싶다. 다음 사이트에서 참조 했다. 결론을 말하면, png 파일로 AndroidStudio 폴더에 넣어주면 된다. layout 설정하는 화면에 design과 text를 설정할 수 있는데, text로 다음과 같이 입력하면 된다. 설정 옵션에 방향을 변경할 수 있는데, 어떻게 하는지 몰라 그림을 90도 돌렸다.

    <?xml version="1.0" encoding="utf-8"?>
    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
                  xmlns:app="http://schemas.android.com/apk/res-auto"
                  android:layout_width="match_parent"
                  android:layout_height="match_parent"
                  android:background="@drawable/carsideview"
                  android:orientation="vertical"
                  android:weightSum="1">
    
    </LinearLayout>

    이 사진에 번호를 붙이고 싶으나, 제공하는 TextView에서는 원하는 위치로 가져갈 수 없다. 사진을 수정하여 넣어야 될듯 보인다.