아두이노 Wifi 스위치 - 클라이언트 소스 리뷰

아두이노 Wifi 스위치의 사용자 인터페이스는 현재 React Native 기반의 앱으로 개발되어 있다. 추후에 웹 인터페이스를 지원 하려고 Express.js 를 셋팅해 놓긴 했는데 언제 할지는 잘 모르겠다. 그래도 React Native(이하 RN)은 아이폰과 안드로이드를 동시에 지원하기 때문에 집안에 아이폰과 안드로이드 사용자가 따로 있어도 앱을 빌드해서 각자의 폰에 깔아 주고 전등이나 선풍기 등등을 끄고 켤 수 있다.

아래 화면엔 두개 밖에 없지만 스위치를 몇개 더 만들고 아이패드에 앱을 깔아서 거실에 거치대를 달아서 아이패드를 장착해 두고 터치로 스위치를 끄고 켜거나 각지의 아이폰에서 끝고 켤 수 있도록 확장을 할 계획이다.

RN(React Native)

RN은 이전에 쓴글 https://bipark.github.io/2 에서도 이야기 했지만 Java Script를 이용하여 코딩하고 Xcode 혹은 Android Studio에서 빌드해서 폰에 설치하면 내부적으로 Native 방식으로 구동되기 때문에 일반적인 Native 빌드방식과 큰 차이없이 앱을 만들 수 있다. 하지만 악명높은 JS의 디펜던시 산맥을 넘고 콜백헬을 지나가야 한다. 물론 최근 ES6의 Promise 패턴 지원으로 쉬워 졌다고 말하고는 있지만 사실은 더 어려워졌다. 돌맞을라.. ㅋ

앱코드 리뷰

앱 코드는 아래 리스트가 전부이다. 그 나머지 들은 다 들러리로 상당히(?) 많은 디펜던시와 셋팅 파일들로 이루어져 있다. 웹페이지 개발에 익숙한 개발자는 JS와 TAG가 혼합된 이런 코드가 익숙 할 수도 있지만 네이티브 개발자들은 아무래도 익숙 할것 같지 않다. 하지만 대충 읽어 보면 라이프싸이클(constructor, componentDidMount) 처럼 보이는 함수가 있고 그냥함수(_getData, _setSwitch) 그리고 그리는 함수(render) 가 있다.

사실 이것만 알면 React 혹은 RN 코드도 대충 이해 할 수 있다. 코맨트에 각 함수들의 용도와 형태를 아래와 같이 정리 했다.

// 상단의 import는 아래 코드에서 사용하고 있는 각종 module을 연결하고 사용할 함수들을 정의한다.
import React, { Component } from 'react';
import { Container, Content, Header, Left, Right, Body, Title, Button, Card, CardItem, Footer, FooterTab, Grid, Col } from 'native-base';
import { Text, FlatList, View, RefreshControl } from 'react-native';
import axios from 'axios';
import { Switch } from 'react-native-switch';
import css from './css';

// 서버 연결 주소 초기화
axios.defaults.baseURL = "http://192.168.0.10:3000";

// 기본으로 사용할 클래스 정의 Component 로부터 상속 받아 app 클래스를 만들었다.
export default class app extends Component {

    // 생성자 - 클래스를 생성하면서 this.state 에 변수를 만들고 초기화 했다.
    // switches는 스위치들의 정도를 담아둘 배열로
    // refreshing은 현재 리프레시중인지 판단하는 BOOL 변수이다.
    // state가 변수라는 말이 딱 일치하는 건이니지만 비슷한거니 대충 그렇게 알면 된다.
    // 간단하게 말하면 클래스의 상태인데 이게 변하면 UI혹은 실행이 변하는 데이터를 보통 state에 넣는다.
    constructor(props) {
        super(props);
        this.state = {
            switches: [],
            refreshing: false,
        };
    };

    // 클래스가 마운트 되면 _getData 함수를 호출한다. 마운트 될때 한번 호출된다.
    componentDidMount() {
        this._getData();
    };

    // 호출되면 refreshing을 True로 바꾸고 - 꼭 this.setState() 함수를 이용해서 변경 해야 한다. 
    // 리액트의 중요한 특징이다. - 자세한건 찾아보자 state 키워드
    // axios,get()을 이용해 서버에서 Json에디어를 가지고 온다.
    // await는 데이터가 올때까지 기다린다는 뜻이다. 동기 방식으로 데이터를 가지고 온다.
    // await를 사용하기 위해서 _getData() async로 선언되어 있다.
    // 데이터를 가지고 오면 setState()를 이용해서 값을 변수에 저장한다.
    async _getData() {
        this.setState({refreshing:true});
        let data = await axios.get("/sw-lists");
        this.setState({
            switches: data.data.switches,
            refreshing:false
        });
    };

    // render() 함수 내에서 호출되고 사용자 입력에 의해 호출되는 함수이다. 
    // 화면을 눌러 스위치를 On/Off 하면 서버에 전송한다.
    async _setSwitch(item, val) {
        let data = await axios.post("/action",{
            ip:item.ip,
            status:val
        });
    };

    // 아래의 render()의 일부분을 분리해서 별도의 함수로 만들었다.
    // Switch가 선언되어 있고 이벤트가 연결되어 있다.
    // 코드를 보기 좋게 하기 위해 이렇게 구분 하거나 별도의 파일로 분리 하기도 한다.
    _renderItem = ({item}) => (
        <Card>
            <CardItem>
                <Content>
                    <Grid>
                        <Col>
                            <Text>{item.title}</Text>
                            <Text>{item.ip}</Text>
                        </Col>
                        <Col style={css.switch}>
                            <Switch
                                value={(item.status === 1)}
                                onValueChange={(val) => {this._setSwitch(item, val)}}
                            />
                        </Col>
                    </Grid>
                </Content>
            </CardItem>
        </Card>
    );

    // 실제 화면을 그리는 함수이다.
    // 위의 setState()에 의해서 데이터가 변경되면 render() 함수가 호출된다.
    // 다른것도 많지만 이게 가장 중요한 리액트의 특징이다. 

    // 태그들을 그리고 각 대그에 이벤트를 링크 시켜준다.
    // 중요한 부분은 FlatList에서 state.switches의 갯수 만큼 위의 카드를 그린다.
    // Header와 Footer는 그냥 장식이다.
    render() {
        const { state } = this;
        return (
            <Container>
                <Header>
                    <Body>
	                    <Title>Home Controller</Title>
                    </Body>
                </Header>
                <Content refreshControl={
                    <RefreshControl
                        refreshing={this.state.refreshing}
                        onRefresh={this.getData}
                    />
                }>
                    <FlatList
                        keyExtractor={(item, index) => item.id}
                        data={state.switches}
                        renderItem={this._renderItem}
                        style=
                    />
                </Content>
	            <Footer>
		            <Text>Billy's Home Controller</Text>
	            </Footer>
            </Container>

        );
    }


}

UI 화면

위 코드를 이용해서 아래와 같은 화면을 구현 했다. 이 이미지는 아이폰과 안드로이드에서 동일한 형태와 성능으로 실행된다.

이것이 RN의 가장 큰 장점이다.

정리

주말에 코딩을 하고 그걸 정리해서 블로깅을 해보겠다고 마음먹고 시작한 첫번째 프로젝트가 아두이노 와이파이 스위치 였다. 일단 아두이노와 서버 그리고 앱까지 기본 동작이 마무리 하고 돈도 안되는 쓸데없는 짓을 하고 이렇게 뿌듯한 기분을 느껴도 되는 것인지 잘모르겠다. 도움이 되시면 좋겠다.

근데.. 다음엔 뭘하지?

Written on July 9, 2017