2016년 10월 25일 화요일

RxJS - ReactiveX 인터뷰

A: 왜 RxJS입니까
B: javascript는 참 쉽고 친숙한 언어죠.
A: 별로 그렇게 생각 안합니다만.
B: 그래서 좀 어렵고 있어보이는게 뭘까 싶어서...
A: 네?
B: 함수형이라는게 유행하기도 하고
f(x) 좋쟎습니까? 미스테리~ 미스테리~ 정수정짱짱 으아아

이런 수학선생님이라면 수포자 따윈
A: ...
B: 그리고 반응형이라는 말 뭔가
A: 뭔가?
B: 대충대충해도 막 알아서 할거 같고...
A: 그럴리가요?
B: 안그렇겠죠?
A: 네
B: 네

(잠시만 기다려주세요)

A: 그래도 뭔가 매력이 있으니 이렇게 시간을 내셔서 이것저것 Rx에 대해 글도 쓰고 이야기도 하고 그러시는거 아닌가요?
B: 매력이라.
으음.
제가 팔꿈치 터널 증후근이 좀 있어요.
오른손 세끼손가락, 약지손가락이 저립니다.
A: 무슨 상관이?
B: 그래서 각종 괄호를 쓰는게 너무 힘듭니다.
소중대괄호 만든 사람 죽었으면.
Hello world (ASCII):

A: 이미 옛날에 돌아가셨겠죠.
B: 그렇겠네요.
아무튼 그래서 소중대괄호 의존이 적은 커피스크립트를 쓰는데요.
A: 빨리 본론을 말씀해주시죠.
B: 커피스크립트에서 가로로 80자 이상쓰면 Line exceeds maximum allowed length 라고 경고해요.
A: 그래서요?
B: 근데 Rx를 쓰면 코드를 가늘게 쓸 수가 있더라구요.
A: 호오?
B: 그리고 = 쓰는 것도 너무 힘듭니다.
A: 네?
B: 오른손을 쓰쟎아요.
A: ...
그러니까 정리하면
1. 괄호가 힘들다
2. 커피를 쓴다
3. 커피는 길게 쓰면 경고
4. Rx를 쓰면 코드가 가늘다
5. 대입문을 줄이고 싶다.
B: 네
하지만 5번은 생각보다 별로...
A:
B:

B: 아!
A: ?
B: 코드가 가늘어서 좋은 점이.
A: 네.
B: 핸드폰에서 코드를 보기 좋습니다.
A: ... 왜 팔꿈치 터널 증후근이 안 낫는지 알겠습니다.
B: 도와주세요.
쇠고기 사묵으면 나을 것 같습니다.

고오오기. 고기하면 소오고오기이


A: (모르겠고) 사실 그런건
array function(map, filter, reduce, concat)만 써도 비슷하쟎습니까.
사실 비동기 처리가 중요한거 아닙니까?
B: 비동기 까짓거 promise도 있고 걍 callback해도 되고
A: 여기까지 와서 시비 거시는 겁니까?
B: 뭐 얼마나 대단한 코드를 짠다고 callback 지옥 걱정을 합니까.

하지만 방심하면 금방...

A: 대단한 거 안짜도 빡센데요.
B: object literal 잘 쓰세요.
A: 싫어요.
B: 저도 별로...

A: 아무튼 지적 허영심도 충족시키고 손목 터널도 덜고
폰에서도 보기도 짱짱맨 라이브러리란 거죠?
B: 그리고 한번 배워놓으면 Polyglot이라서 다른 언어에서도 쓸 수 있습니다.
A: 그렇죠? 그건 참 좋은 거 아닌가요. 기껏 배워놨는데 본전 생각도 나고
B: 사실 UniRx(Unity+Rx) 같은 걸 보면 꼭 그런 것만도...
A: 그래도 개념을 이해하면 두루 도움이 되겠죠?
B: Go같은 경우를 보면 Channel이 더 좋고 쉬운데 굳이...
A: ...하지말까요?
B: 도와주십쇼.
A: 네, 협조부탁드립니다.
B: 네. 근데 저도 별로 아는게 없어서.
A: 모르는 거 부탁 드린적 없습니다.
B: 미흡하나마 잘 부탁드리겠습니다.
A: 네, 전혀 겸손처럼 느껴지지 않는군요.
B: 저야 워낙 신실한 사람이라서요.

A: Rx는 어떤 경우에 쓰면 좋을까요?
B: 연속적인 데이터가 예상할 수 없는 지점에 발생할 때 사용하면 좋습니다.
A: 이제까지 중 제일 말같은 말씀이시네요.
B: 저야 워낙 신실한 사람이라서요.
A: 못들은 걸로 하겠습니다.
좀 더 구체적으로 어떤 상황이 있을까요?
B: 키보드 입력, 마우스 처리, AJAX 호출, 파일 읽고 쓰기, 배치작업 스케쥴링, 각종 네트웍 처리 등등이 있겠죠. 이 모든 것들을 다 스트림으로 볼 수 있습니다.

그러하다.

B: 덤으로 전역변수의 사용을 지양하는 효과도 있어서 실수로 인한 외부 변수 오염으로부터 안전한 프로그래밍을 하는데 도움이 됩니다.
A: 튜토리얼은 앞으로 어떤 식으로 진행할 건가요?
B: 개론은 어짜피 reactivex.io 에 한글 문서도 있고 예제나 트레이닝 같이 따라해볼 수 있는 쪽으로 번역해볼까 합니다.
보신 분들은 알겠지만 발번역 오역이 난무하니 상처받지 않게 지도편달(?) 부탁드립니다.
A: 좋은 말씀 감사합니다. 뭔가 주객이 전도된 듯한 느낌이 드는군요.
B: 별말씀을요.

2016년 10월 23일 일요일

RxJS 에서 requestAnimationFrame 을 사용하려면?

http://jsbin.com/qosigic/edit?html,js,output

소스는 이쪽.

게임과 같은 분야는 타이밍 처리가 매우 중요하기 때문에 UI를 갱신하는 무한 루프를 놓고 delta 시간을 얻어와서 처리하는 로직을 많이 쓰는데
이걸 Javascript 에서 할 땐 전통적으로 setTimeout 을 걸어놓은 함수를 재귀하는 식으로 썼는데 문제는 setTimeout(setInterval도 마찬가지)이 굉장히 부정확한 타이밍을 가지고 있었고 이를 보완하기 위해 requestAnimationFrame 이라는 것을 만들어 사용하기 시작했다.
문제는 역시 지원하지 않는 몇몇 브라우저(ex. IE < 10)들 때문에 안타깝게도 RxJS 5.x 의 defaultScheduler에선 setTimeout(https://github.com/Reactive-Extensions/RxJS/blob/6dac0365ad22f87a92197fc4dcee70e72a11ddbb/src/modular/scheduler/defaultscheduler.js#L119)을 쓰고 있는데
물론 권장하는 방식은 https://github.com/Reactive-Extensions/RxJS/tree/8fa95ac884181fb6cbff8ce7c1d669ffb190f5e4/examples/crop 의 예처럼 RequestAnimationFrameScheduler 를 사용하는 것이지만

RxLua 처럼 기본으로 타이머가 없고 외부패키지에 의존하는 환경도 있고해서
기본 RxJS 환경에서 직접 만들어보기로 했다.

Scope 을 위해 일단 function으로 감싸보았다.
(function() {
  var canvas=document.getElementById('canvas1');
  var ctx = canvas.getContext('2d');
  var start = 0;
  var step = function(timestamp) {
    if (!start) start = timestamp;
    var progress = timestamp - start;
    ctx.clearRect(0,0,canvas.width,canvas.height);
    ctx.fillText("legacy: "+progress.toFixed(2), 10,10);
    window.requestAnimationFrame(step);
  }
  window.requestAnimationFrame(step);
})();
이게 아마 보통 requestAnimationFrame 을 사용하는 패턴일 것이다.
그러면 Rx 에서 subscribe 일때 처리해야하는 부분은 어디일까?
실제로 UI를 갱신하는 ctx 부분일 것이다.
(function() {
 var canvas=document.getElementById('canvas1');
 var ctx = canvas.getContext('2d');
 var start = 0;
 var step = function(timestamp) {
   if (!start) start = timestamp;
   var progress = timestamp - start;
   /* 여기부터 */
   ctx.clearRect(0,0,canvas.width,canvas.height);
   ctx.fillText("legacy: "+progress.toFixed(2), 10,10);
   /* 여기까지 */
   window.requestAnimationFrame(step);
 }
 window.requestAnimationFrame(step);
})();
이 부분을 분리해보자.
가장 간단한 방법은 역시 Subject 로 만드는 방법이겠다.
stepSubject.subscribe(progress=>{
  ctx.clearRect(0,0,canvas.width,canvas.height);
  ctx.fillText("Rx Subjeect: "+progress.toFixed(2), 10,10);
});
이렇게 실제로 progess 가 넘어오는 것을 받는 subscribe 를 만들고
stepSubject 를 생성한 뒤 루프 안에서 stepSubject.next 에서 progress 를 건내주도록 하자.
(function() {
  var canvas=document.getElementById('canvas2');
  var ctx = canvas.getContext('2d');
  var stepSubject = new Rx.Subject();
  var start = 0;
  var step = function(timestamp) {
    if (!start) start = timestamp;
    var progress = timestamp - start;
    stepSubject.next(progress);
    window.requestAnimationFrame(step);
  }
  window.requestAnimationFrame(step);
  stepSubject.subscribe(progress=>{
    ctx.clearRect(0,0,canvas.width,canvas.height);
    ctx.fillText("Rx Subjeect: "+progress.toFixed(2), 10,10);
  });
})();
간단하다. 별로 크게 건들지 않고 분리를 했다.

scope을 생각했을 때 반복하는 step function이 Observable 안에 있으면 좋겠다 싶은 생각이 든다.
start 와 step을 Observable.create 안에 넣고 .next 로 쏘아보자. subscribe는 동일하다.
(function() {
  var canvas=document.getElementById('canvas3');
  var ctx = canvas.getContext('2d');
  var stepObservable = Rx.Observable.create(observer=>{
    var start = 0;
    var step = function(timestamp) {
      if (!start) start = timestamp;
      var progress = timestamp - start;
      observer.next(progress);
      window.requestAnimationFrame(step);
    }
    window.requestAnimationFrame(step);
  });
  stepObservable.subscribe(progress=>{
    ctx.clearRect(0,0,canvas.width,canvas.height);
    ctx.fillText("Rx Observable: "+progress.toFixed(2), 10,10);
  });
})();
이렇게 옮기면 Rx.Observable 안에서 requestAnimationFrame 을 제어할 수 있다.
개인적으로는 이런 상황이라면 start 와 step 의 Observable 안에 scope 을 가지고 있는 후자를 더 추천한다.

2016년 10월 22일 토요일

RxJS Basic - 기초

http://reactivex.io/rxjs/manual/tutorial.html 글을 대충 번역해서 쓰는 글입니다.
jsbin/codepen 같은 곳에서 RxJS 라이브러리를 추가한 후 연습해봅시다.
현재 가장 최근 버전은 https://cdnjs.com/libraries/rxjs/5.0.0-rc.1 에서 확인하세요.

  1. Observable 로 변환하기
    1. 하나 혹은 n 개의 값으로부터

      Rx.Observable.of('foo', 'bar');
      =>foo
      =>bar
    2. 배열로부터

      Rx.Observable.from([1,2,3]);
      =>1
      =>2
      =>3
    3. 이벤트로부터

      Rx.Observable.fromEvent(document.querySelector('button'), 'click');
      =>button을 누를 때 마다 event로 변환
    4. Promise로부터

      Rx.Observable.fromPromise(fetch('/users'));
      =>/user 주소의 내용을 fetch한 것을 변환
    5. 마지막 인자가 콜백인 경우 콜백으로부터

      /* node.js */
      var exists = Rx.Observable.bindCallback(fs.exists);
      exists('file.txt').subscribe(exists => console.log('Does file exist?', exists));

      var rename = Rx.Observable.bindNodeCallback(fs.rename);
      rename('file.txt', 'else.txt').subscribe(() => console.log('Renamed!'));
  2. Observable 만들기
    1. 외부로부터 새로운 이벤트를 생성한다.

      var myObservable = Rx.Subject.create();
      myObservable.subscribe(value => console.log(value));
      myObservable.next('foo');
    2. 내부에서 새로운 이벤트를 생성한다.

      var myObservable = Rx.Observable.create(observer => {
        observer.next('foo');
        setTimeout(() => observer.next('bar'), 1000);
      });
      myObservable.subscribe(value => console.log(value));
    • 어떤 것을 선택할지는 시나리오에 따라 선택할 수 있습니다.
      일반적인 Observable은 당신이 시간이 흘러가면서 값을 생성하는 기능을 구현할때 좋습니다.
      일례로 websocket 연결 같은 것을 들 수 있다.
      Subject를 사용하면 어디서나 새로운 이벤트를 작동할 수 있으며 기존의 Observable과 연결할 수 있습니다.
  3. 흐름(Flow)을 제어하기

    // "hello world"라고 쳐봅니다.
    var input = Rx.Observable.fromEvent(document.querySelector('input'), 'keypress');

    // 2글자 이하로는 필터링합니다
    input.filter(event => event.target.value.length > 2)
      .subscribe(value => console.log(value));
    // "hel"

    // 이벤트를 지연시킵니다.
    input.delay(200)
      .subscribe(value => console.log(value));
    // "h" -200ms-> "e" -200ms-> "l" ...

    // 매번 200ms 동안 한번만 받아들입니다.
    input.throttleTime(200)
      .subscribe(value => console.log(value));
    // "h" -200ms-> "w"

    // 마지막 200ms 후에 발생한 것만 통과시킵니다.
    input.debounceTime(200)
      .subscribe(value => console.log(value));
    // "o" -200ms-> "d"

    // 3개의 이벤트만 받습니다.
    input.take(3)
      .subscribe(value => console.log(value));
    // "hel"

    // 다른 옵저버블이 이벤트를 발생시킬 때 통과시킵니다.
    var stopStream = Rx.Observable.fromEvent(document.querySelector('button'), 'click');
    input.takeUntil(stopStream)
      .subscribe(value => console.log(value));
    // "hello" (click)
  4. 값(Value)을 만들기

    // "hello world" 라고 쳐봅니다.
    var input = Rx.Observable.fromEvent(document.querySelector('input'), 'keypress');
    // 새로운 값을 내보냅니다.
    input.map(event => event.target.value)
      .subscribe(value => console.log(value));
    // "h"

    // 값 뽑아내기
    input.pluck('target', 'value')
      .subscribe(value => console.log(value));
    // "h"

    // 값을 쌍으로 뽑아내기
    input.pluck('target', 'value').pairwise()
      .subscribe(value => console.log(value));
    // ["h", "e"]

    // 중복 값 제거하기
    input.pluck('target', 'value').distinct()
      .subscribe(value => console.log(value));
    // "helo wrd"

    // 이전 값과 다를 때만 내보내기
    input.pluck('target', 'value').distinctUntilChanged()
      .subscribe(value => console.log(value));
    // "helo world"
  5. 응용프로그램(Application) 만들기
    RxJS는 코드의 오류를 줄이기에 탁월한 도구입니다.
    이에 순수함수와 상태 없는(stateless) 함수를 사용합니다.
    하지만 응용프로그램들은 상태를 가지고 있습니다(stateful).
    그렇다면 우리는 stateless한 RxJS 와 stateful한 응용프로그램을 어떻게 연결할 수 있을까요?
    0 이라는 값으로부터 단순한 상태 저장(State Store)을 만들어봅시다.
    매번 클릭할 때 마다 우리는 State Store의 값이 증가하길 원합니다.

    var button = document.querySelector('button');
    Rx.Observable.fromEvent(button, 'click')

      // 카운트의 스트림을 합산(scan-reduce)합니다.
      .scan(count => count + 1, 0)
      // 매번 그것이 변할 때 마다 요소(element)의 갯수를 설정합니다.
      .subscribe(count => document.querySelector('#count').innerHTML = count);

    이렇게 RxJS에서 상태를 만들어 보았습니다. 하지만 DOM을 변경하는 것은 마지막 줄에서 일어나는 부작용(Side-effect)입니다.
  6. 상태 저장소(State store)
    응용 프로그램에서는 상태를 유지하기를 위해 State Store를 사용합니다. 이들은 프레임워크마다 store나 reducer, model 등 각각 다른 이름으로 불립니다. 그러나 그것들은 단지 객체(Object)일 뿐입니다.

    var increaseButton = document.querySelector('#increase');
    var increase = Rx.Observable.fromEvent(increaseButton, 'click')

      // 우리는 함수에 대입하여 상태를 변하게 합니다.
      .map(() => state => Object.assign({}, state, {count: state.count + 1}));
    우리가 여기서 하는 것은 클릭 이벤트를 상태 변경 함수에 매핑 하는 것입니다.
    그래서 값을 매핑하는 것 대신 함수를 매핑하였습니다.
    함수는 State Store 의 상태를 변화시킬 것입니다.
    그러면, 실제로 어떻게 변하는지 살펴봅니다.

    var increaseButton = document.querySelector('#increase');
    var increase = Rx.Observable.fromEvent(increaseButton, 'click')
      .map(() => state => Object.assign({}, state, {count: state.count + 1}));

    // 우리는 초기 상태를 갖는 객체를 만들었습니다. 새로운 상태 변화가 있을 때 마다
    이를 불러내고 상태를 전달합니다.
    // 새로운 상태를 반환하고 다음 클릭에 대해 변경할 준비를 합니다.
    var state = increase.scan((state, changeFn) => changeFn(state), {count: 0});
    우리는 이제 같은 State store를 변경할 더 많은 Observables 를 추가할 수 있습니다.

    var increaseButton = document.querySelector('#increase');
    var increase = Rx.Observable.fromEvent(increaseButton, 'click')

      // 카운트를 증가시키는 함수를 매핑합니다.
      .map(() => state => Object.assign({}, state, {count: state.count + 1}));

    var decreaseButton = document.querySelector('#decrease');
    var decrease = Rx.Observable.fromEvent(decreaseButton, 'click')

      // 또한 카운트를 감소시키는 함수도 매핑합니다.
      .map(() => state => Object.assign({}, state, {count: state.count - 1}));

    var inputElement = document.querySelector('#input');
    var input = Rx.Observable.fromEvent(inputElement, 'keypress')
      // keypress 이벤트로 inpupValue 상태를 만듭니다.
      .map(event => state => Object.assign({}, state, {inputValue: event.target.value}));

    // 이 세가지 상태를 생성하는 Observables들을 병합(Merge)합니다.
    var state = Rx.Observable.merge(
      increase,
      decrease,
      input
    ).scan((state, changeFn) => changeFn(state), {
      count: 0,
      inputValue: ''
    });

    // 상태 변화에 대해 subscribe 하고 DOM을 갱신합니다.
    state.subscribe((state) => {
      document.querySelector('#count').innerHTML = state.count;
      document.querySelector('#hello').innerHTML = 'Hello ' + state.inputValue;
    });


    // 실제로 상태가 바뀌었을 때를 확인하여 렌더링을 최적화합니다.
    var prevState = {};
    state.subscribe((state) => {
      if (state.count !== prevState.count) {
        document.querySelector('#count').innerHTML = state.count;
      }
      if (state.inputValue !== prevState.inputValue) {
        document.querySelector('#hello').innerHTML = 'Hello ' + state.inputValue;
      }
      prevState = state;
    });
원문에는 immutable JS 나 React 와 같이 실제로 응용프로그램 차원에 적용하는 법도 있지만 이 글에선 범위 밖이라 다루지 않습니다.
각자 자신이 사용하는 환경에 맞게 적용해봅시다.

2016년 10월 17일 월요일

JavascriptCore in Swift 3 연습

//: Playground - noun: a place where people can play

import UIKit
import JavaScriptCore

/* swift to javascript */
let str = "var double=function(x) { return x*2 };"
let context = JSContext()

context?.evaluateScript(str)
let getDouble = context?.objectForKeyedSubscript("double")
getDouble?.call(withArguments: [1]).toString()

*2를 하는 double이라는 function을 js에서 만들고 swift에서 인지 1을 넣어 2를 반환하는 예.
스위프트에서 자바스크립트 함수를 호출할 때는 
  1. objectForKeyedSubscript를 사용하여 함수명을 지정하고
  2. call(withArguments: [, ...]) 를 사용하여 인자를 지정한다.
  3. 결과값은 JSValue 형이므로 적당히 변환해서 사용한다.

/* javascript to swfit */
let str2 = "postMessage('갔다가 다시 올꺼야')"
let postMessageBlock : @convention(block) (AnyObject?) -> (AnyObject) = { (data) in
    print(data)
    return data!
}
context?.globalObject.setValue(unsafeBitCast(postMessageBlock, to: AnyObject.self), forProperty: "postMessage")

context?.evaluateScript(str2)

반대의 경우, 즉 자바스크립트에서 스위프트의 특정 함수를 호출하고자 할 땐
해당 context의 globalObject에 setValue를 하되 unsafeBitCast를 사용한다.
evaluateScript를 한 후 js의 postMessage는 setValue가 연결한 swift쪽의 postMessageBlock을 호출.
들어온 값은 콘솔에 출력하고 자기 자신을 그대로 반환하도록 하였다.

요정도만 있으면 일단 Javascript기반 script 엔진을 만드는데 문제는 없어보인다.

Android의 경우 https://github.com/ericwlange/AndroidJSCore 를 사용 중인데

아주 좋은 예제가 있으니 이쪽을 참조하자.

* 10/25 추가분 *
Object안에 function이 있을 경우 objectForKeyedSubscript로 접근이 안되는데
context?.objectForKeyedSubscript("objS.s")
가령 이런 건 의도대로 잘 되지 않는다.

context?.evaluateScript("var someObj = { a: 1, b: 2, c: function(v) { return v*v; } }")
context?.evaluateScript("someObj.c").call(withArguments: [10]).toInt32()

이와 같이 해당 key를 evaluateScript해서 직접 call을 사용하면 접근할 수 있다.
그런데 이런 경우 문제는 단지 function(v) { return v*v; } 만 가져오는 것이므로 function 안에서 다른 멤버변수인 a,b를 접근할 수 없다.

* 10/28 추가분 *
만일 자바스크립트 쪽에서 인자로 무기명 함수를 사용할 경우 해당 인자를 JSValue로 받아 evaluateScript 하는 형식으로 사용할 수 있다.
다만 해당 함수는 바로 사용할 수 없으므로 IIFE(https://en.wikipedia.org/wiki/Immediately-invoked_function_expression)로 감싸서 바로 실행할 수 있도록 만들어 줘야한다.
let publishBlock: @convention(block) (AnyObject?, JSValue?) -> (AnyObject) = { (data, callback) in
    print("(\(callback!.toString()!))()")
    return (context?.evaluateScript(callback?.toString()))!
}
context?.globalObject.setValue(unsafeBitCast(publishBlock, to: AnyObject.self), forProperty: "Meteor_publish")
context?.evaluateScript("var q=0; Meteor_publish('something', function() { q= 100; })")
context?.evaluateScript("q")


2016년 10월 14일 금요일

ESP32 DevBoard 개봉기

오늘 드디어 손에 넣었다. ESP32 DevBoard!
Adafruit 에서 15개 한정 재입고 트윗을 보고 광속 결제.
그리고 1주일의 기다림. 사랑해요 USPS <3
알리를 이용하다보니 1주일 정도는 광속 배송임.
물론 배송비도 무자비함 -_ㅜ
15개 한정판 adafruit 발 dev board

그놈이 틀림없으렸다.

오오 강려크한 포스

ESP32_Core_board_V2라고 적혀있군요.

ESP32 맞구요. 네네.
ESP32-D0WDQ6 라고 써있는데
D → Dual-core
0 → No internal flash
W → Wi-Fi
D → Dual-mode Bluetooth
Q → Quad Flat No-leads (QFN) package
6 → 6 mm × 6 mm package body size
라고 함.

길이는 이정도

모듈크기는 이정도

코어는 6mm밖에 안해!
여기에 전기만 넣으면 BLE+WIFI!
밑에 크고 발 8개 달린 놈은 FM25Q32라고 32Mbit 플래시메모리

ESP8266 DevBoard 동생이랑 비교
크고 아름다운 레귤레이터랑 CP2102 USB Driver가 붙어있음.
ESP8266 DevBoard엔 CH340G 인데 확 작아졌네.

머리를 맞대어 보았음.
모듈크기는 아주 약간 ESP32가 더 큰데 워낙에 핀이 많고 촘촘함.
ESP8266인 ESP12는 핀 간격이 2.00mm인데 비해
ESP32는 1.27mm 밖에 안함.
딱봐도 비교가 될 정도.
크고 아름다운 Pinouts


ESP8266 보드랑 별로 안달라보인다.
http://www.silabs.com/products/mcu/pages/usbtouartbridgevcpdrivers.aspx#mac
에서 CP2102 드라이버를 설치하고
screen 으로 연결해보자.
내 경우엔 tty.SLAB_USBtoUART 로 잡혔다.
어디서 기본 속도가 115200bps 라고 들은 적이 있어서
screen /dev/tty.SLAB_USBtoUART 115200
로 접속.

:>
프롬프트가 생소하다.
보드 정보를 보기 위해 reset을 해보니
:>ets Jun  8 2016 00:22:57
rst:0x1 (POWERON_RESET),boot:0x13 (SPI_FAST_FLASH_BOOT)
ets Jun  8 2016 00:22:57
rst:0x10 (RTCWDT_RTC_RESET),boot:0x13 (SPI_FAST_FLASH_BOOT)
configsip: 0, SPIWP:0x00
clk_drv:0x00,q_drv:0x00,d_drv:0x00,cs0_drv:0x00,hd_drv:0x00,wp_drv:0x00
mode:DOUT, clock div:2
load:0x3ffc0000,len:0
load:0x3ffc0000,len:2304
load:0x40078000,len:3788
ho 0 tail 12 room 4
load:0x40098000,len:532
entry 0x4009813c
;36m
m;36m**************************************
m;36m*       hello espressif ESP32!       *
m;36m*        2nd boot is running!        *
m;36m*            version (V0.1)          *
m;36m**************************************
m;36mcompile time 18:16:58
m;36m  SPI Speed      : 40MHz
m;36m  SPI Mode       : DOUT
m;36m  SPI Flash Size : 4MB
m;36mPartition Table:
m;36m## Label            Usage          Type ST Offset   Length
m;36m 0 factory          factory app      00 00 00010000 00100000
m;36m 1 rfdata           RF data          01 01 00110000 00040000
m;36m 2 wifidata         WiFi data        01 02 00150000 00040000
m;36mEnd of partition table
m;36mLoading app partition at offset 00010000
m;36msection 0: paddr=0x00000020 vaddr=0x00000000 size=0x0ffe8 ( 65512)
m;36msection 1: paddr=0x00010010 vaddr=0x3f400010 size=0x05b64 ( 23396) map
m;36msection 2: paddr=0x00015b7c vaddr=0x3ffba720 size=0x01378 (  4984) load
m;36msection 3: paddr=0x00016efc vaddr=0x40080000 size=0x00400 (  1024) load
m;36msection 4: paddr=0x00017304 vaddr=0x40080400 size=0x126ac ( 75436) load
m;36msection 5: paddr=0x000299b8 vaddr=0x00000000 size=0x06658 ( 26200)
m;36msection 6: paddr=0x00030018 vaddr=0x400d0018 size=0x325b4 (206260) map
m;36mstart: 0x400807ac
mInitializing heap allocator:
Region 19: 3FFBBA98 len 00024568 tag 0
Region 25: 3FFE8000 len 00018000 tag 1
Pro cpu up.
Pro cpu start user code
nvs_flash_init
frc2_timer_task_hdl:3ffbc564, prio:22, stack:2048
tcpip_task_hdlxxx : 3ffbeca8, prio:20,stack:2048
phy_version: 80, Aug 26 2016, 13:04:06, 0
pp_task_hdl : 3ffc34f0, prio:23, stack:8192
:>enter uart init
uart init wait fifo succeed
exit uart init
IDF version : master(db93bceb)
WIFI LIB version : master(934d079b)
ssc version : master(r283 4d376412)
!!!ready!!!
mode : softAP(26:0a:c4:00:03:98)
dhcp server start:(ip: 192.168.4.1, mask: 255.255.255.0, gw: 192.168.4.1)
+WIFI:AP_START
막막 칼라로 나온다.

쭉 보니까 여러가지 정보가 나오네
WIFI 를 보니

잘 잡힘.
신호도 양호.
BLE는 어떻게 하는지 잘 모르겠음.

help 라고 쳐보니 (?를 쳐도 같음)
:>
supported command:

Please refer to document ssc_commands.xlsx for detail
이렇게 나옴.
막상 ssc_commands.xlsx는 어디에?? 문서도 없고 검색도 안됨.

관전포인트

  1. 핀 간격이 1.27mm. 수작업으로 와이어링을 하기엔 다소 힘들것으로 보임
  2. NODEMCU 는 아직 미대응. esp-idf 가 플래싱을 포함한 이것저것 들어있는 툴.
  3. ESP8266시리즈처럼 AT 커맨드 셋이 아님. 아직 정확한 자료 없음.
  4. http://espressif.com/products/hardware/esp32/resources 관련 도구/문서들
  5. 기타 자세한 장치 스펙은 https://github.com/sparkfun/ESP32_Miscellany/blob/master/Documentation/ESP32_Specifications_EN_v1.pdf
  6. Getting Started : https://github.com/CHERTS/esp32-devkit/blob/master/Espressif/docs/ESP32/ESP32%20Getting%20Started%20Guide%20for%20SDK%20based%20on%20FreeRTOS.pdf

적다보니 Getting Started 문서 15페이지 부터 SSC 커맨드가 있음.
<명령어> -<플래그> <인자> 순의 조합.
-Q는 공통으로 Query(조회) -S는 Set(설정)이다.
-o 옵션은 1일때 STA, 2일때 AP, 3일때 둘 다

  1. op : wifi 모드 조회/설정
    1. op -Q
    2. op -S -o <mode>
  2. sta: station 모드 조회/설정
    1. sta -S -s <ssid> -b <bssid> -n <channel> -h
      sta -S 만 하면 전체 AP 조회
      -h는 hidden ssid 조회
    2. sta -Q 상태조회
    3. sta -C -s <ssid> -p <password> : AP연결
    4. sta -D : AP Disconnect
  3. ap: ap 모드 조회/설정
    1. ap -S -s <ssid> -p <password> -t encrypt -n <channel> -h -m <max_sta>
      1. -s ssid : SSID
      2. -p password : 암호
      3. -t encrypt 있으면 암호화
      4. -h : ssid 숨김
      5. -n channel : channel
      6. -m max_sta : 최대 접근 가능한 station 갯수
    2. ap -Q : 조회
    3. ap -L : MAC/IP Address 조회
  4. mac: mac 조회 -Q -o 를 사용하되 mode는 1,2만 있다.
    1. mac -Q -o 1 : STA MAC 정보
    2. mac -Q -o 2 : AP MAC 정보
  5. dhcp: mac 조회/설정
    1. dhcp -S -o mode : DHCP 시작
    2. dhcp -E -o mode : DHCP 종료
  6. ip: mac 조회/설정
    1. -Q -o 1 : STA IP정보
    2. -Q -o 2 : AP IP정보
    3. -Q -o 3 : STA & AP IP 정보
    4. -S -i <ip> -o <mode> -m <mask> -g <gateway> : IP 설정 -o는 1,2,3과 같다.
  7. reboot: 말그대로 reboot
  8. ram: heap size 반환
일단 여기까지만 나와있음. 음? BLE는? 그리고 설정밖에 없네??
BSSID로 scan은 가능하지만 접속은 안되네
한번 sta로 접속을 시도해본다. -s 에 SSID, -p에 암호를 각각 넣어서 시도.
:>sta -C -s XXXXXX -p QQQQQQ
+JAP:OK
:>n:11 0, o:1 0, ap:255 255, sta:11 0, prof:1n:11 0, o:11 0, ap:255 255, sta:11 0, prof:1state: 0 -> 2 (b0)stastate: 2 -> 3 (0)state: 3 -> 5 (10)add 0

:>
+JAP:WIFICONNECTED
:>ip: 192.168.0.47, mask: 255.255.255.0, gw: 192.168.0.1
+JAP:CONNECTED,AccessDenied
:>sta -Q
+JAP:CONNECTED,AccessDenied
:>
해보니까 scan 찾고 접속하는 건 잘된다.
ip도 조회해 본다.
:>ip -Q -o 1
+STAIP:192.168.0.47
+STAIPMASK:255.255.255.0
+STAIPGW:192.168.0.1
기분탓인지 모르겠지만 ESP8266보다 훨씬 빠르게 느껴진다. 응답이든 뭐든.

시간 날때 build 도전.

2016년 10월 3일 월요일

amok.js 에서 RxJS와 coffeescript 사용하기

amok은 live reload 가 가능해서 개발을 즐겁게 한다. 프로토타이핑할때 아주 좋다.
RxJS 라이브러리를 받고 index.js 도 만들자.

curl https://cdnjs.cloudflare.com/ajax/libs/rxjs/5.0.0-beta.12/Rx.min.js > Rx.min.js
touch index.js index.coffee

package.json 을 만들어 놓으면 편하다.
npm init 을 해서 엔터 연타하고 script 부분만 수정하자
{
  "name": "rxamok",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "start": "amok --browser chrome --interactive --hot Rx.min.js index.js"  },
  "author": "",
  "license": "ISC"
}
이러면 준비 끝.
amok 에서 coffeescript를 compile 하는 옵션이 deprecated 되었다고 한다.
하지만 걱정할 것 없다.
저장할때마다 자동으로 coffeescript를 컴파일 할 수 있다.

coffee -wco ./ *.coffee
해놓고 편하게 코딩하자.
rate = 1000/60
c = document.createElement 'canvas'
c.style.position = 'absolute'
c.style.top = '0'
c.style.left = '0'
resize = ->
  c.width = window.innerWidth
  c.height = window.innerHeight
resize()
document.body.appendChild c
ctx = c.getContext '2d'
window.addEventListener 'resize', resize
Rx.Observable.interval rate
.subscribe (frame)->
  ctx.fillStyle = '#000000'
  ctx.fillRect 0, 0, c.width, c.height
  ctx.fillStyle = '#ffffff'
  ctx.fillText "frame: #{frame}", 40, c.height-40
테스트용으로 이렇게 입력하고 subscribe 안쪽을 수정해보자.
저장할때마다 브라우저 상태를 유지한 채로 변경사항이 적용되는 것을 볼 수 있다.

# 비디오 필요 #