- 전체 시스템 소개
- 조명 제어
- 스마트 가습기
- 수면 패턴 분석
- 시스템 연동
서비스 개요
1. 서비스 기능
스마트 가습기는 사용자로부터 목표 습도를 받아오고 목표 습도에 도달할 수 있도록 가습기를 자동으로 제어합니다.
이러한 기능을 제공하기 위해 필요한 서비스의 기능은 다음과 같습니다.
- Web Socket Server를 연다. (heartbeat를 통해 지속적인 서비스가 되도록 한다.)
- Enact App에서 목표 습도 값을 받는다.
- Enact App에서 스마트 가습기 상태 on/off 값을 받는다.
- ESP 디바이스로부터 현재 습도와 온도 값을 받아온다.
- 가습기 상태가 on일 경우 습도 값이 목표 습도보다 낮으면 가습기를 켜고, 목표 습도보다 높으면 가습기를 끈다.
- 가습기 상태가 off일 경우 목표 습도에 관계없이 가습기는 동작하지 않는다.
2. 서비스 함수
sendHumdOnOff
|
on/off 정보가 담긴 command를 device에 보낸다. |
controlHumd
|
가습기 상태와 습도 값을 얻어와 목표 습도와 비교한다. |
handleMessage
|
디바이스로부터 현재 습도와 온도를 받는다. |
startServer
|
서버 서비스를 등록한다. 이때, 서버 서비스가 끊기지 않게 하기 위해 heartbeat를 구독해야한다. |
getHumdControlData
|
현재 습도 제어 정보를 얻어온다. |
setHumdOn
|
가습기 동작 여부를 설정하는 서비스를 등록한다. |
setTargetHum
|
목표 습도를 설정하는 서비스를 등록한다. |
heartbeat
|
서비스가 지속적으로 동작하기 위해 heartbeat 서비스를 등록한다. |
서비스 개발
1. humdControlData
let humdControlData = {
currentHum : 45,
currentTemp : 18,
autoHumdOn : false,
isHumdOn : false,
wasHumdOn : false,
targetHum : 45
};
humdControlData에 data들을 저장합니다.
2. setHumdOnOff
const sendHumdOnOff = (value) => {
let statusString = value ? "on" : "off";
let command = {
msgType : "command",
deviceType : "humd",
// deviceID : message.payload.deviceID,
status : statusString
};
broadCastMessage(JSON.stringify(command));
};
디바이스에 가습기 상태 ON/OFF command를 보냅니다.
상태 설정 value 를 얻어 statusString 변수에 저장하고 JSON형식으로 디바이스에 전달합니다.
3. controlHumd
const controlHumd = () => {
let alarmText;
if (humdControlData.autoHumdOn) {
if(humdControlData.currentHum <= humdControlData.targetHum) {
humdControlData.isHumdOn = true;
sendHumdOnOff(true);
alarmText = '[가습기 자동 제어 모드] 실내가 건조하여 가습기를 실행합니다.';
}
else {
humdControlData.isHumdOn = false;
sendHumdOnOff(false);
alarmText = '[가습기 자동 제어 모드] 실내가 습하여 가습기를 종료합니다.';
}
}
else {
humdControlData.isHumdOn = false;
sendHumdOnOff(false);
alarmText = '가습기 동작을 종료합니다.';
}
if (humdControlData.isHumdOn != humdControlData.wasHumdOn) {
service.call("luna://com.webos.notification/createToast", {message:alarmText}, function(m2) {
console.log(logHeader, "SERVICE_METHOD_CALLED:com.webos.notification/createToast");
});
humdControlData.wasHumdOn = humdControlData.isHumdOn;
}
};
현재 가습기 동작 상태와 현재습도 값을 얻어와 목표습도보다 크면 가습기를 끄고, 목표습도보다 작으면 가습키를 켜는 코드를 작성합니다.
이때, alarmText변수를 사용하여 가습기가 켜고 꺼질 때 ToastMessage가 뜨도록 합니다.
4. handleMessage
const handleMessage = (message) => {
console.log('Recieved: ', message);
let msg = JSON.parse(message);
if (msg.msgType == 'sendValue') {
if(msg.deviceType == 'humd') {
humdControlData.currentHum = msg.currHum;
humdControlData.currentTemp = msg.currTemp;
controlHumd();
}
}
};
msgType이 'sendValue'이고, deviceType이 'humd'인 msg가 도착하면 현재 온도와 습도 값을 받아와 변수에 저장합니다..
5. getHumdControlData
// get 습도 제어 정보
service.register('getHumdControlData', function(message){
// console.log('getTargetHum: ', humdControlData);
message.respond({
returnValue : true,
data:humdControlData
});
});
service.register를 사용하여 getHumdControlData 서비스를 등록합니다.
디바이스의 message에서 humdControlData를 받아 respond합니다.
6. setHumdOn
// set 가습기 동작 여부
service.register('setHumdOn', function(message){
console.log('setHumdOn, Received: ', humdControlData.isHumdOn);
humdControlData.autoHumdOn = message.payload.value;
controlHumd();
message.respond({
returnValue : true
});
});
service.register를 사용하여 setHumdOn 서비스를 등록합니다.
가습기 동작 여부 value를 받아 isHumdOn에 저장합니다.
7. setTargetHum
// set 목표 습도
service.register('setTargetHum', function(message){
console.log('setTargetHum, Received: ', message.payload.value);
humdControlData.targetHum = message.payload.value;
message.respond({
returnValue : true
});
});
service.register를 사용하여 setTargetHum 서비스를 등록합니다.
목표 습도 value를 받아 targetHum에 저장합니다.
+ startServer
app.ws('/', function(ws, req) {
console.log('Recieved something');
ws.on('message', handleMessage);
});
const broadCastMessage = (message) => {
aWss.clients.forEach(function (client) {
client.send(message);
});
}
service.register("startServer", function(message) {
console.log('WebSocketServer on?');
app.listen(port);
console.log('WebSocketServer on!');
// heartbeat 구독
const sub = service.subscribe(`luna://${pkgInfo.name}/heartbeat`, {subscribe:true});
const max = 120;
let count = 0;
sub.addListener("response", function(msg) {
console.log(JSON.stringify(msg.payload));
if (++count >= max) {
sub.cancel();
setTimeout(function(){
// console.log(max+" responses recieved, exiting...");
process.exit(0);
}, 1000);
}
});
message.respond({
returnValue: true,
Response: "Start Server!"
});
});
service.register를 사용하여 startServer서비스를 등록합니다.
webSocketServer는 다음 사이트를 참고하여 작성되었습니다.
https://www.npmjs.com/package/express-ws
++ heatbeat
webSocketServer 서비스가 계속해서 동작하기 위해 heartbeat를 구독해야 합니다.
자세한 내용은 heatbeat 사용하기 포스팅을 통해 확인해주시기 바랍니다.
// heartbeat
// handle subscription requests
const subscriptions = {};
let heartbeatinterval;
let x = 1;
function createHeartBeatInterval() {
if (heartbeatinterval) {
return;
}
console.log(logHeader, "create heartbeatinterval");
heartbeatinterval = setInterval(function() {
sendResponses();
}, 1000);
}
// send responses to each subscribed client
function sendResponses() {
console.log(logHeader, "send_response");
console.log("Sending responses, subscription count=" + Object.keys(subscriptions).length);
for (const i in subscriptions) {
if (Object.prototype.hasOwnProperty.call(subscriptions, i)) {
const s = subscriptions[i];
s.respond({
returnValue: true,
event: "beat " + x
});
}
}
x++;
}
var heartbeat = service.register("heartbeat");
heartbeat.on("request", function(message) {
console.log(logHeader, "SERVICE_METHOD_CALLED:/heartbeat");
message.respond({event: "beat"}); // initial response
if (message.isSubscription) {
subscriptions[message.uniqueToken] = message; //add message to "subscriptions"
if (!heartbeatinterval) {
createHeartBeatInterval();
}
}
});
heartbeat.on("cancel", function(message) {
delete subscriptions[message.uniqueToken]; // remove message from "subscriptions"
var keys = Object.keys(subscriptions);
if (keys.length === 0) { // count the remaining subscriptions
console.log("no more subscriptions, canceling interval");
clearInterval(heartbeatinterval);
heartbeatinterval = undefined;
}
});
전체 서비스 코드
1. service.js
const pkgInfo = require('./package.json');
const Service = require('webos-service');
const service = new Service(pkgInfo.name); // Create service by service name on package.json
const logHeader = "[" + pkgInfo.name + "]";
var express = require('express');
var app = express();
var expressWs = require('express-ws')(app);
var aWss = expressWs.getWss('/');
const port = 9999;
// ***************************** Websocket Server *****************************
app.use(function (req, res, next) {
console.log('middleware');
req.testing = 'testing';
return next();
});
app.ws('/', function(ws, req) {
console.log('Recieved something');
ws.on('message', handleMessage);
});
const handleMessage = (message) => {
console.log('Recieved: ', message);
let msg = JSON.parse(message);
if (msg.msgType == 'sendValue') {
if(msg.deviceType == 'humd') {
humdControlData.currentHum = msg.currHum;
humdControlData.currentTemp = msg.currTemp;
controlHumd();
}
}
};
const broadCastMessage = (message) => {
aWss.clients.forEach(function (client) {
client.send(message);
});
}
service.register("startServer", function(message) {
console.log('WebSocketServer on?');
app.listen(port);
console.log('WebSocketServer on!');
// heartbeat 구독
const sub = service.subscribe(`luna://${pkgInfo.name}/heartbeat`, {subscribe:true});
// const max = 120;
// let count = 0;
sub.addListener("response", function(msg) {
console.log(JSON.stringify(msg.payload));
// if (++count >= max) {
// sub.cancel();
// setTimeout(function(){
// // console.log(max+" responses recieved, exiting...");
// process.exit(0);
// }, 1000);
// }
});
message.respond({
returnValue: true,
Response: "Start Server!"
});
});
// ***************************** Heartbeat *****************************
// send responses to each subscribed client
function sendResponses() {
// console.log(logHeader, "send_response");
// console.log("Sending responses, subscription count=" + Object.keys(subscriptions).length);
for (const i in subscriptions) {
if (Object.prototype.hasOwnProperty.call(subscriptions, i)) {
const s = subscriptions[i];
s.respond({
returnValue: true,
event: "beat " + x
});
}
}
x++;
}
// handle subscription requests
const subscriptions = {};
let interval;
let x = 1;
function createInterval() {
if (interval) {
return;
}
console.log(logHeader, "create_interval");
console.log("create new interval");
interval = setInterval(function() {
sendResponses();
}, 1000);
}
// ***************************** Create Toast *****************************
function createToast(message) {
service.call("luna://com.webos.notification/createToast", {message:message}, function(m2) {
console.log(logHeader, "SERVICE_METHOD_CALLED:com.webos.notification/createToast");
});
}
const heartbeat2 = service.register("heartbeat");
heartbeat2.on("request", function(message) {
console.log(logHeader, "SERVICE_METHOD_CALLED:/heartbeat/request");
console.log("heartbeat callback");
message.respond({event: "beat"});
if (message.isSubscription) {
subscriptions[message.uniqueToken] = message;
if (!interval) {
createInterval();
}
}
});
heartbeat2.on("cancel", function(message) {
console.log(logHeader, "SERVICE_METHOD_CALLED:/heartbeat/cancel");
console.log("Canceled " + message.uniqueToken);
delete subscriptions[message.uniqueToken];
const keys = Object.keys(subscriptions);
if (keys.length === 0) {
console.log("no more subscriptions, canceling interval");
clearInterval(interval);
interval = undefined;
}
});
// ***************************** 스마트 가습기 *****************************
let humdControlData = {
currentHum : 45,
currentTemp : 18,
autoHumdOn : false,
isHumdOn : false,
wasHumdOn : false,
targetHum : 45
};
const sendHumdOnOff = (value) => {
let statusString = value ? "on" : "off";
let command = {
msgType : "command",
deviceType : "humd",
// deviceID : message.payload.deviceID,
status : statusString
};
broadCastMessage(JSON.stringify(command));
};
const controlHumd = () => {
let alarmText;
if (humdControlData.autoHumdOn) {
if(humdControlData.currentHum <= humdControlData.targetHum) {
humdControlData.isHumdOn = true;
sendHumdOnOff(true);
alarmText = '[가습기 자동 제어 모드] 실내가 건조하여 가습기를 실행합니다.';
}
else {
humdControlData.isHumdOn = false;
sendHumdOnOff(false);
alarmText = '[가습기 자동 제어 모드] 실내가 습하여 가습기를 종료합니다.';
}
}
else {
humdControlData.isHumdOn = false;
sendHumdOnOff(false);
alarmText = '가습기 동작을 종료합니다.';
}
if (humdControlData.isHumdOn != humdControlData.wasHumdOn) {
service.call("luna://com.webos.notification/createToast", {message:alarmText}, function(m2) {
console.log(logHeader, "SERVICE_METHOD_CALLED:com.webos.notification/createToast");
});
humdControlData.wasHumdOn = humdControlData.isHumdOn;
}
};
// get 습도 제어 정보
service.register('getHumdControlData', function(message){
// console.log('getTargetHum: ', humdControlData);
message.respond({
returnValue : true,
data:humdControlData
});
});
// set 가습기 동작 여부
service.register('setHumdOn', function(message){
console.log('setHumdOn, Received: ', humdControlData.isHumdOn);
humdControlData.autoHumdOn = message.payload.value;
controlHumd();
message.respond({
returnValue : true
});
});
// set 목표 습도
service.register('setTargetHum', function(message){
console.log('setTargetHum, Received: ', message.payload.value);
humdControlData.targetHum = message.payload.value;
message.respond({
returnValue : true
});
});
'webOS 프로젝트' 카테고리의 다른 글
webOS를 활용한 HomeIoT : 수면패턴분석2 - Enact App (0) | 2021.11.27 |
---|---|
webOS를 활용한 HomeIoT : 수면패턴분석1 - 디바이스 (0) | 2021.11.27 |
webOS를 활용한 HomeIoT : 스마트 가습기2 - Enact App (0) | 2021.11.27 |
webOS를 활용한 HomeIoT : 스마트 가습기1 - 디바이스 (0) | 2021.11.27 |
webOS를 활용한 HomeIoT : 조명제어3 - 서비스 (0) | 2021.11.27 |