webOS 프로젝트

webOS를 활용한 HomeIoT : 스마트 가습기2 - Enact App

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

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

 

Enact webapplication 개발하기


1. Enact 기본 앱 만들기

Enact webapplication의 설치 및 Enact App개발 시작에 관한 것은 기존 게시글을 참고해주세요.

 

2. wepappication의 UI 디자인 & 기능

UI 디자인 및 기능 설명

코드


1. MainPanel.js 전체 코드

import imgHumidOn from '../../../resources/humidOn.png';
import imgHumidOff from '../../../resources/humidOff.png';
import './HumidityPanel.style.css';

import Switch from '@enact/sandstone/Switch';
import Dropdown from '@enact/sandstone/Dropdown';
import {useState} from 'react';
import Image from '@enact/sandstone/Image';
import BodyText from '@enact/sandstone/BodyText';
import Button from '@enact/sandstone/Button';
import LS2Request from '@enact/webos/LS2Request';
import ReactInterval from 'react-interval';

const bridge = new LS2Request();
const serviceUrl = 'luna://com.cosmos.project.service';

const lsSetTargetHum = (value) => {
	let request = {
		service: serviceUrl,
      	method: 'setTargetHum',
		parameters: {value:value},
		onSuccess: (msg) => {console.log(msg);},
		onFailure: (msg) => {console.log(msg);},
	};
	bridge.send(request);
};

const lsSetIsHumdOn = (value) => {
	let request = {
		service: serviceUrl,
      	method: 'setHumdOn',
		parameters: {value:value},
		onSuccess: (msg) => {console.log(msg);},
		onFailure: (msg) => {console.log(msg);},
	};
	bridge.send(request);
};

const startServer = () => {
	const onSocketOnSuccess = (msg) => {console.log(msg)};
	const onSocketOnFailure = (msg) => {console.log(msg)};
	let request = {
		service: serviceUrl,
      	method: 'startServer',
		parameters: {},
		onSuccess: onSocketOnSuccess,
		onFailure: onSocketOnFailure,
	};
	bridge.send(request);
};

const WhichText = function(props) {
	if(props.isSwitchOn) {
		if(props.isHumdOn) {
			return (<span> [자동모드 On] 가습기 동작 중입니다.</span>);
		}

		else {
			return (<span> [자동모드 On] 목표습도에 도달하였습니다.</span>);
		}
	}

	else {
		return (<span> [자동모드 Off] 가습기가 꺼져있습니다. </span>);
	}
};

const getIndexFromHumdValue = (value) => {
	let index = 0;
	switch (value) {
		case 45: index = 0; break;
		case 50: index = 1; break;
		case 55: index = 2; break;
		case 60: index = 3; break;
	}
	return index;
};

const getHumdValueFromIndex = (index) => {
	let value = 0;
	switch (index) {
		case 0: value = 45;	break;
		case 1: value = 50;	break;
		case 2: value = 55;	break;
		case 3: value = 60;	break;
	}
	return value;
};

const HumidImage = (props) => {
    return (
        <>
        <Image
            src={props.isOnOff ? imgHumidOn : imgHumidOff}
            sizing={'fill'}
            onLoad={console.log('image onLoad')}
            style={{
                border: '#ffa500 dashed 0px'
            }}
        ></Image>
        </>
    );
};

const HumidityPanel = () => {
	// 현재 온도
	let [currentTemp, setCurrentTemp] = useState(18);

	// 현재 습도
	let [currentHum, setCurrentHum] = useState(45);

	// 가습기 동작 중인지 아닌지
	let [isHumdOn, setIsHumdOn] = useState(false);

	// 토글버튼 on인지 off인지
	let [isSwitchOn, setIsSwitchOn] = useState(false);
	
	// 목표 습도
	let [targetHum, setTargetHum] = useState(45);

	const handleHumdControlData = (data) => {
		setCurrentTemp(data.currentTemp);
		setCurrentHum(data.currentHum);
		setIsHumdOn(data.isHumdOn);
		setTargetHum(data.targetHum);
	};

	let request = {
		service: serviceUrl,
      	method: 'getHumdControlData',
		parameters: {},
		onSuccess: (msg) => {handleHumdControlData(msg.data);},
		onFailure: (msg) => {console.log('getHumdControlDataFailure', msg);},
	};
	bridge.send(request);

    return (
        <>
		<div className="my-container">			
			<div className="imgtop">
				<HumidImage isOnOff={isHumdOn}></HumidImage>
			</div>
			<div className="drop">
				<Dropdown
					className = "down"
					defaultSelected={getIndexFromHumdValue(targetHum)}
					onSelect={(e)=>{targetHum = getHumdValueFromIndex(e.selected); lsSetTargetHum(targetHum);}}
					inline
					title="목표 습도를 선택하세요(Humidifier)">
					{['45%(겨울)', '50%(봄, 가을)', '55%(여름)','60%(기본 값)']}			
				</Dropdown>
			</div>
			<div className="texttop">
				<BodyText>
					------------------ 현재습도 ------------------
				</BodyText>
				<BodyText>
					현재 이 곳은 온도 {currentTemp}도, 습도는 {currentHum}%입니다.
				</BodyText>
			</div>
			<div className="textmid">
				<BodyText>
					--------------- 가습기 ON/OFF ---------------
				</BodyText>
				<div>
					<span><Switch onToggle={ (e)=>{setIsSwitchOn(e.selected); setIsHumdOn(e.selected); lsSetIsHumdOn(e.selected);}}/></span>
					<WhichText isHumdOn={isHumdOn} isSwitchOn={isSwitchOn}></WhichText>
				</div>
				<Button onClick={startServer}>server 켜기</Button>
			</div>
			<ReactInterval timeout={1000} enabled={true} 
				callback={() => {bridge.send(request);}} />
		</div>
        </>
    );
};

export default HumidityPanel;

2. MainPanel.js 코드 설명

  •  필요한 기능을 import 
import Switch from '@enact/sandstone/Switch';
import Dropdown from '@enact/sandstone/Dropdown';
import {useState} from 'react';
import Image from '@enact/sandstone/Image';
import BodyText from '@enact/sandstone/BodyText';
import Button from '@enact/sandstone/Button';
import LS2Request from '@enact/webos/LS2Request';
import ReactInterval from 'react-interval';

ui 디자인에 있는 이미지도 같이 import 해줍니다.

import imgHumidOn from '../../../resources/humidOn.png';
import imgHumidOff from '../../../resources/humidOff.png';
import './HumidityPanel.style.css';
  • 서비스 구현

 1. 서버를 여는 service

'server' 버튼을 누르면 서버 여는 서비스를 실행하는 함수를 구현합니다.

Enact에서 LS2 API(Luna Service API) 사용하기에 대해 <Enact LS2 API 호출하기>에 자세하게 설명되어 있으니 참고바랍니다. 

const startServer = () => {
	const onSocketOnSuccess = (msg) => {console.log(msg)};
	const onSocketOnFailure = (msg) => {console.log(msg)};
	let request = {
		service: serviceUrl,
      	method: 'startServer',
		parameters: {},
		onSuccess: onSocketOnSuccess,
		onFailure: onSocketOnFailure,
	};
	bridge.send(request);
};

2. Enact app에서 service로 value 값 전달

목표습도의 value값과 가습기가 on/off 상태를 service로 보냅니다.

값이 잘 전달되었는지 log를 통해 확인합니다.

const lsSetTargetHum = (value) => {
	let request = {
		service: serviceUrl,
      	method: 'setTargetHum',
		parameters: {value:value},
		onSuccess: (msg) => {console.log(msg);},
		onFailure: (msg) => {console.log(msg);},
	};
	bridge.send(request);
};

const lsSetIsHumdOn = (value) => {
	let request = {
		service: serviceUrl,
      	method: 'setHumdOn',
		parameters: {value:value},
		onSuccess: (msg) => {console.log(msg);},
		onFailure: (msg) => {console.log(msg);},
	};
	bridge.send(request);
};

3. 가습기 on/off상태와 습도에 따라 문구를 설정

가습기가 켜져있고, 목표 습도에 아직 도달하지 않았을 경우 ->  "가습기가 동작 중입니다." 

가습기가 켜져있고, 목표 습도에 도달한 경우 -> "목표습도에 도달하였습니다." 

가습기가 꺼져있는 경우 -> "가습기가 꺼져있습니다."

const WhichText = function(props) {
	if(props.isSwitchOn) {
		if(props.isHumdOn) {
			return (<span> [자동모드 On] 가습기 동작 중입니다.</span>);
		}

		else {
			return (<span> [자동모드 On] 목표습도에 도달하였습니다.</span>);
		}
	}

	else {
		return (<span> [자동모드 Off] 가습기가 꺼져있습니다. </span>);
	}
};

4. 목표 습도에 따라 case를 분류해줍니다.

const getIndexFromHumdValue = (value) => {
	let index = 0;
	switch (value) {
		case 45: index = 0; break;
		case 50: index = 1; break;
		case 55: index = 2; break;
		case 60: index = 3; break;
	}
	return index;
};

const getHumdValueFromIndex = (index) => {
	let value = 0;
	switch (index) {
		case 0: value = 45;	break;
		case 1: value = 50;	break;
		case 2: value = 55;	break;
		case 3: value = 60;	break;
	}
	return value;
};

5.  현재 온도, 습도와 토글버튼과 가습기 on/off 여부를 service로 보내줍니다.

useState를 이용하여 현재 온도는 18도, 현재 습도는 45%로 초기 값을 설정해줍니다. 

가습기의 동작 여부와 토글 버튼의 상태는 false 변수로 저장합니다.

const HumidityPanel = () => {
	// 현재 온도
	let [currentTemp, setCurrentTemp] = useState(18);

	// 현재 습도
	let [currentHum, setCurrentHum] = useState(45);

	// 가습기 동작 중인지 아닌지
	let [isHumdOn, setIsHumdOn] = useState(false);

	// 토글버튼 on인지 off인지
	let [isSwitchOn, setIsSwitchOn] = useState(false);
	
	// 목표 습도
	let [targetHum, setTargetHum] = useState(45);

	const handleHumdControlData = (data) => {
		setCurrentTemp(data.currentTemp);
		setCurrentHum(data.currentHum);
		setIsHumdOn(data.isHumdOn);
		setTargetHum(data.targetHum);
	};

	let request = {
		service: serviceUrl,
      	method: 'getHumdControlData',
		parameters: {},
		onSuccess: (msg) => {handleHumdControlData(msg.data);},
		onFailure: (msg) => {console.log('getHumdControlDataFailure', msg);},
	};
	bridge.send(request);

6. Enact 기능

목표 습도를 선택하는 드롭다운과 가습기 on/off를 제어하는 토글버튼을 구현해줍니다.

또한, 현재 온도와 습도 값을 받아와 화면에 표시되도록 코딩하였습니다.

	<div className="my-container">			
			<div className="imgtop">
				<HumidImage isOnOff={isHumdOn}></HumidImage>
			</div>
			<div className="drop">
				<Dropdown
					className = "down"
					defaultSelected={getIndexFromHumdValue(targetHum)}
					onSelect={(e)=>{targetHum = getHumdValueFromIndex(e.selected); lsSetTargetHum(targetHum);}}
					inline
					title="목표 습도를 선택하세요(Humidifier)">
					{['45%(겨울)', '50%(봄, 가을)', '55%(여름)','60%(기본 값)']}			
				</Dropdown>
			</div>
			<div className="texttop">
				<BodyText>
					------------------ 현재습도 ------------------
				</BodyText>
				<BodyText>
					현재 이 곳은 온도 {currentTemp}도, 습도는 {currentHum}%입니다.
				</BodyText>
			</div>
			<div className="textmid">
				<BodyText>
					--------------- 가습기 ON/OFF ---------------
				</BodyText>
				<div>
					<span><Switch onToggle={ (e)=>{setIsSwitchOn(e.selected); setIsHumdOn(e.selected); lsSetIsHumdOn(e.selected);}}/></span>
					<WhichText isHumdOn={isHumdOn} isSwitchOn={isSwitchOn}></WhichText>
				</div>
				<Button onClick={startServer}>server 켜기</Button>
			</div>
			<ReactInterval timeout={1000} enabled={true} 
				callback={() => {bridge.send(request);}} />
		</div>
        </>
    );
};

결과


최종 ui 디자인