Meteor로 공공 IoT한 썰

Appsoulute 이재호

github @acidsound

https://spectrumdig.blogspot.kr/

오늘의 주제는

생활 밀착형 IoT 프로젝트

생활 밀착형 IoT 프로젝트

생존형

전주 버스 정류장 안내판

이미 있는데 왜?

i18n

승차권 자동발매기와 인터넷 예매시스템에 외국어 지원이 부실해 고속버스 승차권 구매가 어려운 외국인 관광객들이 전주 관광에 시내버스 이용도 쉽지 않은 것으로 나타났다. 외국어 안내가 전혀 없기 때문이다.

전주시가 한옥마을 1,000만 관광객 유치 달성을 기원해 지난 5월 관광객들을 위한 전주 시내버스 ‘1000번’을 신설했지만, 정작 안내방송과 노선표를 한국어로만 제공하고 있어 외국인 관광객 배려에 인색하다는 지적이다.

특히 전주시 전 노선 시내버스의 안내방송은 물론, 정류장에서 노선 안내도를 외국어로 제공하지 않아 외국 관광객들의 시내버스 이용에 불편이 크다.

상황 요약

  • 다국어 고도화 프로젝트
  • exe 실행파일이랑 이미지 리소스만 덜렁
  • 문서가 있지만 버전 관리가. 하하하...;;;
  • TCP 통신이 필요함
  • 센서/카메라(몰카????) 정보 업로드
  • XP에 준하는 Windows Embed (32bit)
  • 촉박한 마감
  • 기타 여러 혼돈들의 도가니탕

그래도 카드값은 내야지...

뭘로 만들까?

Native? 시간이 없다!

Chrome App으로 가자

  • 빠른 코딩
  • 빠른 디자인
  • 빠른 피드백
  • Serial I/O 지원
  • TCP/IP 지원
  • 추후 ARM/Linux 머신으로
    포팅하는 큰 그림

그렇다면 당연히

최신인 Electron!

...은 안된데요.

그렇다고 합니다...

그렇다면 1000여개 정류장 전부 OS 업데이트 하면 되겠네요? 데헷

어른의 사정이란게 있습니다.

하지만 NW.js라면 어떨까?

win32 밑줄 쫙!

게다가 소스 보호도 된다고 함

구현 요소 검증

Serial(sensor)

TCP

Audio

Camera

FTP

REST

chrome.serial

chrome.sockets.tcp

webAudio

webRTC

npm

fetch

무엇보다 좋은 점

Web이 아니다

즉, Internet Explorer 를 고려하지 않아도 된다

Chrome Application이라면

  • document.querySelector를 쓸 수 있다.
  • map, filter, reduce를 쓸 수 있다.
  • require로 npm 모듈을 쓸 수 있다.
  • layout을 flexbox로 구현할 수 있다.
  • fetch를 쓸 수 있다.
  • 비동기 처리로 promise를 쓸 수 있다.
  • => fat arrow 를 쓸 수 있다. funtcion은 오타 그만
  • Enhanced Object Literal을 쓸 수 있다.
  • Spread syntax를 쓸 수 있다.
  • webGL을 쓸 수 있다. 등등등등...

빠르게 개발

UI 특성

반복적인 UI

이미지 리소스 개발과 함께 진행

전체/지역 상태변화

상태값이 바뀌면 Scope별 자동 갱신 필요

Reactive
+
Hot code push
+
config-less

역시 Meteor

NW.js랑 어떻게?

개발환경

1. NW.js 설정

{
  "main": "http://localhost:3000",
  "node-remote": "http://localhost:3000",
  "scripts": {
    "start": "meteor npx nw --enable-logging=stderr ."
  },
  "window": {
    "fullscreen": false,
    "toolbar": true,
    "always-on-top": true,
    "resizable": false
  },
  "dependencies": {
    "nw": "^0.14.7-sdk"
  },
  "devDependencies": {
    "nw-builder": "^3.5.1"
  }
}

node-remote가 meteor 개발 서버를 바라보도록

2. Meteor 서버 실행

meteor run

3. NW.js 실행

nw

시연

mkdir nwmeteor
cd nwmeteor
npm init
npm install --save nw@sdk
vi package.json
```
"main": "http://localhost:3000",
"node-remote": "http://localhost:3000",
```
meteor create app
cd app
meteor run
cd ..
./node_modules/.bin/nw

/* for fun */
cd app
meteor add spectrum:tlecss

시간 절약 위해 만든걸 보세요

~/Documents/js/nwjs/template1

일단 통신부터

TCP Echo 서버를 띄우자

$ node
> require('net').createServer(socket=>socket.pipe(socket)).listen(32000, '0.0.0.0');

nc 로 검증

$ nc 0.0.0.0 32000
야!
야!
어?
어?

TCP/IP socket

chrome.sockets.tcp.create({}, createInfo => {
  console.log("socket created", createInfo);
  chrome.sockets.tcp.connect(
    createInfo.socketId,
    '0.0.0.0',
    32000,
    result=>
      chrome.sockets.tcp.send(
        createInfo.socketId,
        ((new TextEncoder()).encode('야')).buffer,
        ()=>console.log("sent")
      )
  );
  chrome.sockets.tcp.onReceive.addListener(info => {
    if (info.socketId !== createInfo.socketId) return;
    console.log((new TextDecoder()).decode(info.data))
  });
})

<< 소켓 생성

<< 소켓 연결

<<  패킷 전송

<<  패킷 수신

Serial Port

chrome.serial.getDevices(ports=>console.log(ports.map(o=>o.path)));
> ["COM3"]

var id;
chrome.serial.connect("COM3", {}, connectionInfo => id=connectionInfo.connectionId);

id
> 10

/* listener */
chrome.serial.onReceive.addListener(info=>console.log(new TextDecoder().decode(info.data)));

/* sender */
chrome.serial.send(id, new TextEncoder().encode('node.info()\n').buffer, console.log);

chrome.serial.disconnect(id, o=>console.log(o));
> true

<< 포트 검색

<< 포트 연결

<<  패킷 전송

<<  패킷 수신

<<  포트 종료

REST - fetch

fetch("https://jsonplaceholder.typicode.com/users")
.then(o=>o.json()).then(p=>console.log(JSON.stringify(p, null,2)))

[
  {
    "id": 1,
    "name": "Leanne Graham",
    "username": "Bret",
    "email": "Sincere@april.biz",
    "address": {
      "street": "Kulas Light",
      "suite": "Apt. 556",
      "city": "Gwenborough",
      "zipcode": "92998-3874",
      "geo": {
        "lat": "-37.3159",
        "lng": "81.1496"
      }
    },
    "phone": "1-770-736-8031 x56442",
    "website": "hildegard.org",
    "company": {
      "name": "Romaguera-Crona",
      "catchPhrase": "Multi-layered client-server neural-net",
      "bs": "harness real-time e-markets"
    }
  },
  {
    "id": 2,
    "name": "Ervin Howell",
    "username": "Antonette",
    "email": "Shanna@melissa.tv",
    "address": {
      "street": "Victor Plains",
      "suite": "Suite 879",
      "city": "Wisokyburgh",
      "zipcode": "90566-7771",
      "geo": {
        "lat": "-43.9509",
        "lng": "-34.4618"
      }
    },
    "phone": "010-692-6593 x09125",
    "website": "anastasia.net",
    "company": {
      "name": "Deckow-Crist",
      "catchPhrase": "Proactive didactic contingency",
      "bs": "synergize scalable supply-chains"
    }
  },
  {
    "id": 3,
    "name": "Clementine Bauch",
    "username": "Samantha",
    "email": "Nathan@yesenia.net",
    "address": {
      "street": "Douglas Extension",
      "suite": "Suite 847",
      "city": "McKenziehaven",
      "zipcode": "59590-4157",
      "geo": {
        "lat": "-68.6102",
        "lng": "-47.0653"
      }
    },
    "phone": "1-463-123-4447",
    "website": "ramiro.info",
    "company": {
      "name": "Romaguera-Jacobson",
      "catchPhrase": "Face to face bifurcated interface",
      "bs": "e-enable strategic applications"
    }
  },
  {
    "id": 4,
    "name": "Patricia Lebsack",
    "username": "Karianne",
    "email": "Julianne.OConner@kory.org",
    "address": {
      "street": "Hoeger Mall",
      "suite": "Apt. 692",
      "city": "South Elvis",
      "zipcode": "53919-4257",
      "geo": {
        "lat": "29.4572",
        "lng": "-164.2990"
      }
    },
    "phone": "493-170-9623 x156",
    "website": "kale.biz",
    "company": {
      "name": "Robel-Corkery",
      "catchPhrase": "Multi-tiered zero tolerance productivity",
      "bs": "transition cutting-edge web services"
    }
  },
  {
    "id": 5,
    "name": "Chelsey Dietrich",
    "username": "Kamren",
    "email": "Lucio_Hettinger@annie.ca",
    "address": {
      "street": "Skiles Walks",
      "suite": "Suite 351",
      "city": "Roscoeview",
      "zipcode": "33263",
      "geo": {
        "lat": "-31.8129",
        "lng": "62.5342"
      }
    },
    "phone": "(254)954-1289",
    "website": "demarco.info",
    "company": {
      "name": "Keebler LLC",
      "catchPhrase": "User-centric fault-tolerant solution",
      "bs": "revolutionize end-to-end systems"
    }
  },
  {
    "id": 6,
    "name": "Mrs. Dennis Schulist",
    "username": "Leopoldo_Corkery",
    "email": "Karley_Dach@jasper.info",
    "address": {
      "street": "Norberto Crossing",
      "suite": "Apt. 950",
      "city": "South Christy",
      "zipcode": "23505-1337",
      "geo": {
        "lat": "-71.4197",
        "lng": "71.7478"
      }
    },
    "phone": "1-477-935-8478 x6430",
    "website": "ola.org",
    "company": {
      "name": "Considine-Lockman",
      "catchPhrase": "Synchronised bottom-line interface",
      "bs": "e-enable innovative applications"
    }
  },
  {
    "id": 7,
    "name": "Kurtis Weissnat",
    "username": "Elwyn.Skiles",
    "email": "Telly.Hoeger@billy.biz",
    "address": {
      "street": "Rex Trail",
      "suite": "Suite 280",
      "city": "Howemouth",
      "zipcode": "58804-1099",
      "geo": {
        "lat": "24.8918",
        "lng": "21.8984"
      }
    },
    "phone": "210.067.6132",
    "website": "elvis.io",
    "company": {
      "name": "Johns Group",
      "catchPhrase": "Configurable multimedia task-force",
      "bs": "generate enterprise e-tailers"
    }
  },
  {
    "id": 8,
    "name": "Nicholas Runolfsdottir V",
    "username": "Maxime_Nienow",
    "email": "Sherwood@rosamond.me",
    "address": {
      "street": "Ellsworth Summit",
      "suite": "Suite 729",
      "city": "Aliyaview",
      "zipcode": "45169",
      "geo": {
        "lat": "-14.3990",
        "lng": "-120.7677"
      }
    },
    "phone": "586.493.6943 x140",
    "website": "jacynthe.com",
    "company": {
      "name": "Abernathy Group",
      "catchPhrase": "Implemented secondary concept",
      "bs": "e-enable extensible e-tailers"
    }
  },
  {
    "id": 9,
    "name": "Glenna Reichert",
    "username": "Delphine",
    "email": "Chaim_McDermott@dana.io",
    "address": {
      "street": "Dayna Park",
      "suite": "Suite 449",
      "city": "Bartholomebury",
      "zipcode": "76495-3109",
      "geo": {
        "lat": "24.6463",
        "lng": "-168.8889"
      }
    },
    "phone": "(775)976-6794 x41206",
    "website": "conrad.com",
    "company": {
      "name": "Yost and Sons",
      "catchPhrase": "Switchable contextually-based project",
      "bs": "aggregate real-time technologies"
    }
  },
  {
    "id": 10,
    "name": "Clementina DuBuque",
    "username": "Moriah.Stanton",
    "email": "Rey.Padberg@karina.biz",
    "address": {
      "street": "Kattie Turnpike",
      "suite": "Suite 198",
      "city": "Lebsackbury",
      "zipcode": "31428-2261",
      "geo": {
        "lat": "-38.2386",
        "lng": "57.2232"
      }
    },
    "phone": "024-648-3804",
    "website": "ambrose.net",
    "company": {
      "name": "Hoeger LLC",
      "catchPhrase": "Centralized empowering task-force",
      "bs": "target end-to-end models"
    }
  }
]

WebCAM

with getUserMedia

document.body.append(v=document.createElement('video'));
navigator.webkitGetUserMedia({ video: true },
  stream => {
    v.srcObject = stream;
    v.onloadedmetadata = v.play;
  },
  () => {
    console.error("webcam Initilization failed");
  }
);

/* capture */
document.body.append(c=document.createElement("canvas"));
ctx=c.getContext('2d');
ctx.drawImage(v,0,0,c.width,c.height);

NW.js Application을 만들어보자

Export

meteor project를 html,js,css 3개로 내보내기

$ cd app
$ npm install --save meteor-build-client
$ ./node_modules/.bin/meteor-build-client ../dist --path ""
혹은 meteor npx meteor-build-client ../dist --path ""
(meteor version ^1.6)

$ cd ../dist
$ ls
acfd4763f552de1a31c4e9bd7043fba2593962ff.js
acfd4763f552de1a31c4e9bd7043fba2593962ff.stats.json
index.html

$ npm init
name: (dist) 
version: (1.0.0) 
description: 
entry point: (acfd4763f552de1a31c4e9bd7043fba2593962ff.js) index.html
...

$ ../node_modules/.bin/nw

Build

최종 실행파일을 생성

# project root
$ npm install --save nw-builder

# options
$ ./node_modules/.bin/nwbuild --help

# osx
$ ./node_modules/nw-builder/bin/nwbuild -p osx64 -o ./build ./dist

# win32 (version 지정)
$ ./node_modules/nw-builder/bin/nwbuild -p win32 -v 0.14.7 -o ./build ./dist

Latest Version: v0.26.6
Using v0.26.6 (sdk)
Create cache folder in /Users/spectrum/Documents/js/nwjs/template1/node_modules/nw-builder/cache/0.26.6-sdk
Downloading: https://dl.nwjs.io/v0.26.6/nwjs-sdk-v0.26.6-osx-x64.zip
  downloading [====================] 100% 0.0s

Create release folder in .... /template1/build/dist/osx64

감사합니다

못다한 얘기들

  • nw.require
  • meteor packages
  • router
  • FXXKING EUC-KR (TextDecoder)
  • flexbox
  • figma
  • .INI 파일 읽는 법
  • settings.json
  • reactiveVar

Meteor로 공공 IoT한 썰

By Lee Jaeho

Meteor로 공공 IoT한 썰

  • 1,168
Loading comments...

More from Lee Jaeho