행복한 가족, 패밀리그램

개발/안드로이드

안드로이드 IOIO - 개발자의 고장난 RC 자동차 장난감 살리기 #3

패밀리그램 2017. 1. 23. 00:17

안드로이드 IOIO - 개발자의 고장난 RC 자동차 장난감 살리기 #1

안드로이드 IOIO - 개발자의 고장난 RC 자동차 장난감 살리기 #2


  이전 포스팅에서 IOIO와 고장난 RC 장난감 자동차를 연결하는 작업을 진행 했었습니다. 이번 포스팅에선 본격적으로 간단한 코딩과 동작완료!

우선 IOIO 관련 Android Java Library가 있습니다.  정확한 레퍼런스는 아래 링크에서 확인 할 수 있습니다.

https://github.com/ytai/ioio/wiki/Building-IOIO-Applications-With-Gradle


개발에 앞서 개발환경은 아래와 같습니다.

조정 단말기 

Nexus5 N (Nougat 7.1) 

 compile SDK version

 25 (2017, 01-22 기준 최신)

 min SDK

 L (Lollipop 5.0)

 Android Studio Gradle

 2.2.3 (현재 최신)

 단말기와 IOIO간 Interface

 Bluetooth 3.0


먼저 안드로이드 프로젝트를 생성합니다. 생성은 안드로이드 개발자라면 충분히 알 듯하기에 생략....

https://github.com/ytai/ioio/wiki/Building-IOIO-Applications-With-Gradle 위 링크에 가이드와 조금 다르게 라이브러리가 구성 되어있습니다. 개별적으로 재구성하여 필요한 라이브러리를 통합하였기 때문입니다. ( 사실 라이브러리 전부를 한 곳에 넣어두었다고 생각하면됩니다. -_-;;;) 

그리고 필요한 관련 Permission들은 전부 라이브러리에 추가되어 있습니다. IOIO Android Library 정식 버전을 확인하고 싶다면 반드시 위 링크를 확인하시길 바랍니다.


프로젝트 최상위 build.gradle 에 아래 코드를 추가 합니다.

1
2
3
4
5
6
allprojects {
    repositories {
        jcenter()
        maven { url "https://jitpack.io" }
    }
}
cs


그리고 프로젝트 application 쪽 build.gradle에 아래 dependency를 추가 합니다.

1
2
3
4
5
6
7
8
9
dependencies {
    compile fileTree(dir: 'libs', include: ['*.jar'])
    androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', {
        exclude group: 'com.android.support', module: 'support-annotations'
    })
    compile 'com.android.support:appcompat-v7:25.1.0'
    compile 'com.github.fivetrue:ioio-android:1.0.0'
    testCompile 'junit:junit:4.12'
}
cs


추가 되었다면 개발 준비가 완료되었습니다.

샘플 코드는 아래 Git 에서 확인 할 수 있습니다.

https://github.com/fivetrue/ioio-broken-car

https://github.com/fivetrue/ioio-broken-car.git


* 위 샘플 코드에 대한 설명은 모델, 컨트롤러 순으로 진행됩니다.

1. 모델 (Output)

IOIO에 사용하는 Pin은 43~46 번 Pin 이었습니다. 해당 Pin 모두 Motor를 제어하기 위한 Pin이기 때문에 1 or 0 인 Digital 신호를 보냅니다. 이 부분을 가독성있게 정의합니다.

각 Pin은 Unique하기 때문에 Singletone으로 정의합니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public enum DefinedPIN implements Runnable{
 
    FRONT_MOTOR_LEFT(43), FRONT_MOTOR_RIGHT(44), REAR_MOTOR_BACKWARD(45), REAR_MOTOR_FORWARD(46);
 
 
    public final int pin;
 
    DefinedPIN(int pin){
        this.pin = pin;
    }
 
    @Override
    public void run() {
 
    }
}
cs


고장난 RC 장난감 자동차 프로젝트를 추가적으로 더 진행 할 진 모르겠지만, 확정성을 위해 Output interface를 정의합니다.

1
2
3
4
5
6
7
8
public interface IOIOOutput {
 
    void openOutput(IOIO ioio) throws ConnectionLostException;
 
    void closeOutput() throws ConnectionLostException;
 
    void run() throws ConnectionLostException;
}
cs


그리고 Digital 신호를 내보내는 Class를 정의합니다. 물론 해당 Class는 IOIOOutput interface를 구현합니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
public class IOIODigitalOutput implements IOIOOutput {
 
    private static final String TAG = "IOIODigitalOutput";
 
    public final DefinedPIN definedPIN;
    public DigitalOutput digitalOutput;
    public boolean enable;
 
    public IOIODigitalOutput(DefinedPIN definedPIN){
        this.definedPIN = definedPIN;
    }
 
 
    @Override
    public void openOutput(IOIO ioio) throws ConnectionLostException {
        if(LL.D) Log.d(TAG, "openOutput: " + this);
        digitalOutput = ioio.openDigitalOutput(definedPIN.pin);
    }
 
    @Override
    public void closeOutput() throws ConnectionLostException {
        if(digitalOutput != null){
            digitalOutput.write(false);
        }
    }
 
    @Override
    public void run() throws ConnectionLostException {
        if(digitalOutput != null){
            digitalOutput.write(enable);
        }
    }
}
cs

이렇게 모델 구현은 완료 되었습니다.


2. 컨트롤러 (IOIO Looper)

 이제 컨트롤러를 구현 할 차례입니다. 원래 IOIO 기본 코드에서는 Activity 가 Control Looper를 가지고 있습니다. 위 샘플 코드에서는 분리되어있습니다.

IOIO는 하나의 Looper (무한 루프 Thread) 를 이용하여 신호를 반복하여 전달 받습니다. IOIOOutput의 run() method는 IOIO 에서 호출하는 반복 루프에서 Output 동작을 수행합니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
public class ControlLooper extends BaseIOIOLooper {
 
    private static final String TAG = "ControlLooper";
 
    public interface DeviceControlImpl{
        void showVersionInfo(IOIO ioio, String msg);
        void disconnected();
    }
 
    private List<IOIOOutput> mIOIOOutputs;
 
    private IOIO mIOIO = null;
 
    private DeviceControlImpl mControlImpl = null;
 
    public ControlLooper(DeviceControlImpl impl, List<IOIOOutput> outputs){
        mControlImpl = impl;
        mIOIOOutputs = outputs;
    }
 
    /**
     * Called every time a connection with IOIO has been established.
     * Typically used to open pins.
     *
     * @throws ConnectionLostException
     *             When IOIO connection is lost.
     *
     */
 
    @Override
    protected void setup() throws ConnectionLostException {
        mIOIO = ioio_;
        mControlImpl.showVersionInfo(mIOIO, "IOIO connected!");
        for(IOIOOutput output : mIOIOOutputs){
            output.openOutput(mIOIO);
        }
    }
 
    /**
     * Called repetitively while the IOIO is connected.
     *
     * @throws ConnectionLostException
     *             When IOIO connection is lost.
     * @throws InterruptedException
     *                 When the IOIO thread has been interrupted.
     *
     * @see IOIOLooper#loop()
     */
    @Override
    public void loop() throws ConnectionLostException, InterruptedException {
        for(IOIOOutput output : mIOIOOutputs){
            output.run();
        }
        Thread.sleep(10L);
    }
    /**
     * Called when the IOIO is disconnected.
     *
     * @see IOIOLooper#disconnected()
     */
    @Override
    public void disconnected() {
        mControlImpl.disconnected();
        for(IOIOOutput output : mIOIOOutputs){
            try {
                output.closeOutput();
            } catch (ConnectionLostException e) {
                Log.e(TAG, "disconnected: ", e);
            }
        }
    }
 
    /**
     * Called when the IOIO is connected, but has an incompatible firmware version.
     *
     * @see IOIOLooper#incompatible(IOIO)
     */
    @Override
    public void incompatible() {
        mControlImpl.showVersionInfo(mIOIO, "Incompatible firmware version!");
    }
}
cs

위 코드를 보면 0.01초 단위로 Loop를 돌며 run을 수행합니다. (정확히 표현하자면 loop를 0.01 초 멈추고 다시 진행합니다.)


3. View (Activity)

IOIO를 동작하기 위해서는 IOIOActivity를 상속 받아 구현하여야합니다. 그리고 IOIOActivity에서는 IOIOLooper 객체를 갖고 있어야합니다.

(그래야 IOIO와 연결 시 Loop를 진행 시켜 IOIOLooper에게 동작을 받아오겠죠?)



간단한 전진, 후진, 앞 바퀴 좌,우 동작을 하는 Activity 의 View입니다. 방향 선택 시 IOIO로 신호를 전달하는 핵심적인 코드는 onTouchListener에 있습니다.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
public class MainActivity extends IOIOActivity implements ControlLooper.DeviceControlImpl, View.OnTouchListener, View.OnClickListener{
 
    private ControlLooper mControlLooper;
 
    private final IOIOOutput mFrontMotorLeft = new IOIODigitalOutput(DefinedPIN.FRONT_MOTOR_LEFT);
    private final IOIOOutput mFrontMotorRight = new IOIODigitalOutput(DefinedPIN.FRONT_MOTOR_RIGHT);
    private final IOIOOutput mRearMotorBackward = new IOIODigitalOutput(DefinedPIN.REAR_MOTOR_BACKWARD);
    private final IOIOOutput mRearMotorForward = new IOIODigitalOutput(DefinedPIN.REAR_MOTOR_FORWARD);
 
    private ImageView mFrontRight;
    private ImageView mFrontLeft;
    private ImageView mBackward;
    private ImageView mForward;
    ...
    ...
    private void initData(){
        ArrayList<IOIOOutput> outputs = new ArrayList<>();
        outputs.add(mFrontMotorLeft);
        outputs.add(mFrontMotorRight);
        outputs.add(mRearMotorBackward);
        outputs.add(mRearMotorForward);
        mControlLooper = new ControlLooper(this, outputs);
    }
 
    private void initView(){
        ...
 
        mFrontRight.setTag(mFrontMotorRight);
        mFrontLeft.setTag(mFrontMotorLeft);
        mForward.setTag(mRearMotorForward);
        mBackward.setTag(mRearMotorBackward);
 
        ...
    }
    ...
    @Override
    public boolean onTouch(View view, MotionEvent motionEvent) {
        if(view.getTag() != null && view.getTag() instanceof IOIODigitalOutput){
            switch (motionEvent.getAction()){
                case MotionEvent.ACTION_DOWN :
                    ((IOIODigitalOutput) view.getTag()).enable = true;
                    return false;
 
                case MotionEvent.ACTION_UP :
                    ((IOIODigitalOutput) view.getTag()).enable = false;
                    return false;
 
            }
        }
        return false;
    }
}
 
cs

각 모터를 제어하는 View에 tag로 제어 할 모터 DigitalOuput 객체를 설정합니다. 그 후  onTouch() 에서 버튼이 눌렸을 때 Digital신호를 true로 눌리지 않았을 때 false로 변경하는 로직을 넣습니다.

(onClickListener도 구현되어있는데 이 부분은 View Selector동작을 위해 구현만 되어있는 것입니다.)


이렇게 하면 구현이 완료되었습니다 ! 

우리 아이의 반응은 정말 좋았지만, 아직 가지고 놀기엔 너무 이른 나이인 것 같습니다 ㅠㅠ.. 언제한번 날씨가 좋아지면 모터에 7V이상의 전압을 달아 야외에서 아들과 함께 시연해야 할 것 같습니다. 그럼 고장난 RC 장난감 살리기 프로젝트는 간단히 마무리!

날씨가 점점 추워지는데 감기 조심하세요!


안드로이드 IOIO - 개발자의 고장난 RC 자동차 장난감 살리기 #1

안드로이드 IOIO - 개발자의 고장난 RC 자동차 장난감 살리기 #2