webOS 프로젝트

webOS를 활용한 HomeIoT : 조명제어3 - 서비스

하이하정 2021. 11. 27. 13:39

  • 전체 시스템 소개
  • 조명 제어
  • 스마트 가습기
  • 수면 패턴 분석
  • 시스템 연동

 

서비스 개요


1. 서비스 기능

스마트 조명은 사용자(app)으로부터 각 방 조명에 대한 On/Off 명령을 받아 각 방의 조명을 제어합니다.

또한 전체 소등/ 전체 점등 기능으로 모든 방의 조명을 한 번에 제어할 수도 있습니다.

 

이러한 기능을 제공하기 위해 필요한 서비스의 기능은 다음과 같습니다.

  1. Enact app으로부터 각 조명 On/Off 명령을 받는다
  2. 디바이스에게 On/Off 명령을 전달한다

 

2. 서비스 함수

getLedStates 디바이스에서 On/Off 상태를 받아온다
setLedState Enact app으로부터 Led1~5의 각 조명 On/Off 명령을 받아와서 디바이스에 전달한다
   
startServer webSocket을 이용하여 양방향 통신이 가능하도록 한다
heartbeat heartbeat 서비스를 이용하여 서비스가 계속해서 수행되도록 한다

 

서비스 개발


1. getLedStates

let ledStates = {
  'LED01' : false,
  'LED02' : false,
  'LED03' : false,
  'LED04' : false,
  'LED05' : false,
};

service.register('getLedStates', function(message){
    message.respond({
        returnValue : true,
        data : ledStates
    });
});

service.register를 사용하여 getLedStatus 서비스를 등록합니다.

디바이스의 message에서 현재 led의 On/Off상태 value를 받아  respond합니다.

led는 총 5개로, ledStates 변수 안에 각 LED의 상태가 false(OFF)를 기본으로 저장되어있습니다.

 

2. setLedState

service.register('setLedState', function(message){
    let deviceID = message.payload.deviceID;
    ledStates[deviceID] = message.payload.value;
    console.log('setLedState', ledStates);

    let statusString = ledStates[deviceID] ? "on" : "off";
    let command = {
        msgType : "command",
        deviceType : "led",
        deviceID : deviceID,
        status : statusString
    };
    broadCastMessage(JSON.stringify(command));
    message.respond({
        returnValue : true,
    });
});

Enact App의 message에서 조명 On/Off 상태설정 value 를 얻어 각 ledStates 에 저장합니다.

 저장한 값을 JSON형식으로 디바이스에 전달합니다.

 

3. startServer

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 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!"
    });
});

socket server를 여는 startServer를 등록합니다.

사이트

 

4. heartbeat

webSocketServer 서비스가 계속해서 동작하기 위해 heartbeat를 구독해주어야 합니다.

자세한 내용은 heartbeat 사용하기 포스팅을 통해 확인해주시기 바랍니다.

 

// ***************************** 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);
}

 

전체 서비스 코드


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();
        }
        else if(msg.deviceType == 'sleepSensor') {
            handleSleepData(msg.deviceID, msg.value);
        }
    }
};

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 ledStates = {
  'LED01' : false,
  'LED02' : false,
  'LED03' : false,
  'LED04' : false,
  'LED05' : false,
};

service.register('getLedStates', function(message){
    message.respond({
        returnValue : true,
        data : ledStates
    });
});

service.register('setLedState', function(message){
    let deviceID = message.payload.deviceID;
    ledStates[deviceID] = message.payload.value;
    console.log('setLedState', ledStates);

    let statusString = ledStates[deviceID] ? "on" : "off";
    let command = {
        msgType : "command",
        deviceType : "led",
        deviceID : deviceID,
        status : statusString
    };
    broadCastMessage(JSON.stringify(command));
    message.respond({
        returnValue : true,
    });
});