강좌 전체보기
.
앞 장에서 ESP32 모듈에 다양한 WiFi 기능을 구현하는 방법들을 다뤘습니다. 이제 센서장치가 외부 서버든 모바일이든 WiFi 로 연결되어 통신할 수 있는 준비는 갖춰졌습니다. 큰 산을 넘은 것이라 할 수 있습니다.
하지만 사물인터넷 서비스는 센서장치가 통신 기능을 갖추는 것만으로 완성되지 않습니다. 실제 서비스를 사용하는 사용자에 따른 동작 시나리오와 UI를 결정해야 하고, 사물인터넷에 참여하는 각 주체가 가장 효율적으로 동작할 수 있는 방식을 고려해야 하며, 사물 인터넷의 각 노드들이 통신할 때 사용할 데이터 전송규칙(프로토콜)을 정해야 합니다.
만약 상용 서비스를 구현하는 단계가 아닌 프로토타이핑 수준이라면 이 모든 것들을 고려해서 시작할 필요는 없습니다. 일단은 자주 사용되는 사물인터넷 서비스 구현 방법 몇 가지 중 자신에게 필요한 방법을 선택하고, 해당 서비스 구현을 위해 필요한 기본 코드부터 구현하고 살을 붙여나가는 것이 빠르고 효율적입니다.
이번 장의 목표가 바로 이것입니다. 센서장치, 서버, 모바일이 함께 동작할 수 있는 몇 가지 서비스 시나리오를 제시하고, 각각을 구현하는 기본 코드를 제공해서 빠르게 프로토타이핑을 시작할 수 있도록 하는 것입니다.
이번 파트에서는 Node.js 를 이용해 웹 서버를 구축하고 센서장치가 랜덤한 숫자 데이터를 생성해서 HTTP POST 로 보내주도록 만들겠습니다. 서버는 이 데이터를 수집해서 저장하고 있다가, 외부에서 HTTP 요청이 오면 수집된 데이터 중 최신 데이터를 HTML 파일 형태로 보여줄 수 있도록 예제 서비스를 만들어 보겠습니다.
.
.
NodeJS 를 이용한 웹 서버 구축
웹 서버 또는 API 서버를 구축해서 서버를 중심으로 데이터가 모이고 분산되는게 기본입니다. 이걸 어떤 플랫폼/언어로 구현할지가 문제이지요. 웹을 기본으로 하는 서버를 구축할때는, 특히 프로토타이핑 수준에서라면, Node.js 가장 합리적인 해답이 될 수 있습니다.
Node.js 는 Java Script 기반의 웹 서버 플랫폼입니다. 많이 사용되는 플랫폼이기 때문에 관련된 자료 구하기도 쉽고 각종 모듈/라이브러리 구하기도 쉽습니다. 그리고 라즈베리파이에 올려 구동 시키는데도 무리가 없습니다.
이번 파트에서는 Node.js를 이용해서 웹 서버를 구축해 보겠습니다. 센서장치가 수집한 데이터는 Node.js 에 모이고, 웹으로 확인이 가능하도록 만들겠습니다.
라즈베리파이에 Node.js 를 설치부터 해야겠지요. 여기서 설치과정 전부를 다루기엔 내용이 길어 별도의 포스트로 만들었으니 링크 3개를 참고해서 라즈베리파이에 Node.js 를 설치하고 기본적인 테스트를 해주세요.
그럼 라즈베리파이에서 Node.js 를 굴리는 기본 방법은 습득하셨을 겁니다.
위 링크에서는 Node.js 서버 구축하고 간단한 HTML 코드를 보여주는 정도의 예제였습니다. 하지만 우리는 이보다는 좀 더 복잡한 서버가 필요합니다. 아래 일들을 처래해줘야 합니다.
- 센서장치에서 서버에 데이터 전송할 수 있는 API
- 센서장치에서는 HTTP POST 방식으로 데이터를 전송
- 센서장치에서 수집한 데이터를 저장
- 외부에서 HTTP 요청이 오면 수집된 데이터 중 최신 데이터를 HTML 화면으로 전송
데이터 저장을 위해서는 DB를 쓰는 것이 정석이겠지만, 여기서는 구현의 편리를 위해 파일로 저장하겠습니다. 대신, 마지막 장에서 홈 오토메이션 시스템을 구축할 때 DB를 쓰도록 하겠습니다.
서버 코드 수정에 앞서 HTTP 요청을 통해 들어오는 body를 파싱하기 위해 body-parser 라는 모듈을 설치해야 합니다.
npm install body-parser
이상의 요구사항을 충족하는 코드는 아래와 같습니다. index.js 파일을 아래와 같이 수정해주세요.
.
var express = require('express') var fs = require('fs') var path = require('path'); var bodyParser = require('body-parser'); var app = express() app.locals.pretty = true app.set('views', './view_file') app.set('view engine', 'pug') app.use(bodyParser.urlencoded({ extended: true })); app.listen(3000, () => { console.log("Server has been started") }) var dataFolderPath = path.join(__dirname, '/data') var dataPath = path.join(dataFolderPath, '/data.txt') app.get("/", (req, res) => { res.redirect('/hello') }) // 저장된 데이터가 있으면 데이터 출력 app.get("/hello", (req, res) => { if(!fs.existsSync(dataFolderPath) || !fs.existsSync(dataPath)) { res.render('hello', { title: 'Hello', message: 'Hello World!!!'}) } else { fs.readFile(dataPath, 'utf-8', (err, data)=> { res.render('data', { title: 'Hello', data: data.split('\n')}) }) } }) // 센서장치에서 데이터를 업데이트하는 API app.post("/data", (req, res) => { var recvData = req.body.data // 데이터 저장 폴더 및 데이터 저장 파일 생성 if(!fs.existsSync(dataFolderPath)) { fs.mkdir(dataFolderPath) } if(!fs.existsSync(dataPath)) { fs.appendFile(dataPath, recvData+'\n', (error) => { if(error) { res.status(500).json({ 'msg': 'Internal server error' }); } else { res.status(200).json({ 'msg': 'Data registered successfully' }); } }) } else { fs.readFile(dataPath, 'utf-8', (err, data)=> { // 10개 이상 데이터 추가 시 10개만 저장 var dataArr = data.split('\n') if(dataArr.length < 10) { fs.appendFile(dataPath, recvData+'\n', (error) => { if(error) { res.status(500).json({ 'msg': 'Internal server error' }); } else { res.status(200).json({ 'msg': 'Data registered successfully' }); } }) } else { dataArr.splice(dataArr.length-1, 1) dataArr.splice(0,dataArr.length - 9) dataArr.push(recvData) var file = fs.createWriteStream(dataPath); file.on('error', (err) => { if(err) console.log(err) }) dataArr.forEach((item) => { file.write(item + '\n') }) file.end(); } }) } })
.
서버에서는 [ http://localhost:3000/data ] 경로로 POST 요청이 들어오면 HTTP body 에 담겨있는 데이터를 추출해서 data.txt 파일에 저장합니다. 그리고 외부의 웹브라우저에서 http://localhost:3000/ 로 접속을 하면 data.txt 파일을 읽어와 데이터를 보여줍니다. data.txt 파일은 최근에 들어온 데이터 10개만 저장합니다.
저장된 데이터를 외부 브라우저 요청이 오면 보여질 수 있도록 화면을 만들어야겠죠. 아래 코드를 data.pug 파일로 만들어서 view 파일을 모아둔 [view_file] 폴더에 넣어주세요.
.
html head title= title body if data for val in data h1= val
.
데이터가 존재할 시 렌더링할 웹페이지를 구현한 pug 파일입니다. data 배열이 전달되면 이를 한 라인씩 화면에 출력합니다. 수정된 파일을 다운로드 받아 사용하고 싶으시면 아래 링크를 사용하세요.
여기까지 준비가 되었으면 Node.JS 서버를 시작해주세요.
- node index.js
이로써 서버 쪽 준비는 완료되었습니다.
.
.
센서장치 구현
센서장치 역할을 하는 ESP32 모듈에서 서버로 POST 요청을 하기 위한 코드를 짜 보도록 하겠습니다. 아래 링크의 소스를 참고하세요.
이미 HTTP POST 에 대해서는 [4-2 센서장치와 HTTP Request] 에서 다뤘으니 여기서는 주요 소스코드만 보여드리겠습니다. 아래 코드에서 ssid, password, (라즈베리파이)서버 주소는 자신의 환경에 맞게 수정해서 업로드 하셔야 합니다.
.
const char* ssid = "your_ssid"; // 와이파이 SSID const char* password = "your_pw"; // 와이파이 비밀번호 void setup() { ...... WiFi.begin(ssid, password); // 와이파이망에 연결 while (WiFi.status() != WL_CONNECTED) { delay(500); Serial.print("."); } ...... } void loop() { HTTPClient http; http.begin("http://www.server_addr.com:3000/data"); // 서버 주소 http.addHeader("Content-Type", "application/x-www-form-urlencoded"); // 랜덤 번호 생성 esp_random(); String num = String(random(0, 256)); Serial.println("num : " + num); // POST 후 결과 받음 int httpResponseCode = http.POST("data="+num); if(httpResponseCode>0){ String response = http.getString(); Serial.println(httpResponseCode); Serial.println(response); }else{ Serial.print("Error on sending POST: "); Serial.println(httpResponseCode); } http.end(); // 리소스 해제 delay(10000); }
.
10초에 한 번씩 HTTP POST 요청을 서버의 [ http://라즈베리파이주소:3000/data ] 경로로 보냅니다. 이 때 HTTP body 에는 전송할 데이터가 [ data=랜덤숫자 ] 형태로 들어가 있습니다. GET 방식에서 사용하는 URL 파라미터 표기방식을 HTTP body 에다가 넣은겁니다. 그래서 HTTP header 에 아래처럼 Mime type을 지정해 줬습니다.
http.addHeader("Content-Type", "application/x-www-form-urlencoded");
ESP32 에 업로드를 하고 공유기에 정상 접속되면 10초에 한번씩 서버로 데이터를 보냅니다.
이제 서버에 데이터가 기록될테니 서버로 접속해서 확인해보면 되겠네요. 같은 공유기에 연결된 PC나 폰 등에서 브라우저로 아래 주소에 접속하세요.
- http://라즈베리파이주소:3000/
아래처럼 화면이 보이면 성공입니다. 간단하지만 센서장치-서버-웹 UI 가 연동된 서비스가 완성되었습니다!!
.
활용
사물인터넷 서비스 구축에서 서버가 차지하는 비중이 매우 큽니다. 물론 웨어러블 장치처럼 센서장치와 모바일 폰 등이 직접 연결되어 동작하는 경우도 있지만, 대부분의 경우 센서장치가 수집한 데이터들은 한데 모여서 가공, 배포되어야 하기 때문입니다.
센서장치와 서버 코드의 기본 틀을 완성했다면 이후 세세한 기능들을 추가하고 다듬는 작업은 훨씬 속도가 날 것입니다.
참고자료
- Node.js tutorial
- https://opentutorials.org/course/2136
- https://nodejs.org/en/download/package-manager/
주의!!! [사물 인터넷 네트워크와 서비스 구축 강좌] 시리즈 관련 문서들은 무단으로 내용의 일부 또는 전체를 게시하여서는 안됩니다. 계속 내용이 업데이트 되는 문서이며, 문서에 인용된 자료의 경우 원작자의 라이센스 문제가 있을 수 있습니다.
.
.
강좌 전체보기
.