【Nature Remo】ブラウザから家電リモコンを操作する方法

Nature RemoはWiFi機能の付いた赤外線リモコンデバイスとなり、Nature Remoのスマホアプリで家電リモコンなどの赤外線リモコンを登録して使用します。

今回は、そのNature Remoを利用し、ブラウザから家電操作をできるようにする例となります。

Nature Remoをブラウザから操作する準備

今回の例では、ローカルに配置したHTMLファイルにブラウザでアクセスし、そのページからNature Remoアプリから登録したリモコンを、Cloud APIを利用して操作をできるようにします。

まずは、そのページを作成する前に以下の準備を行います。

Cloud APIを利用する準備

Nature RemoのCloud APIでは、登録したリモコン情報や現在の気温などを取得でき、利用するにはアクセストークンを発行する必要があります。

 APIのアクセストークンの発行方法については、以下のリンク先を参考にしてみてください。

APIのリクエスト制限について

 5分以内に30回 以上のAPIリクエストを送信すると、その後はステータスコード 429 を返し、リクエストエラーとなります。

アプリからリモコンを登録

今回の例では、Nature Remoアプリから以下の方法でリモコンを登録しています。

  • エアコンのリモコンは「ON」ボタンで一括登録
  • その他のリモコンはボタン毎に一つづつ登録

操作するページを作成

作成するページは、家電ごとに分かれたレイアウトとなり、それぞれのボタン・プルダウンメニューを選択することでリモコン操作を行います。

なお、既存の照明をリモコンで操作したい場合には、以下のような器具があります。

の画像

天井照明 リモコンスイッチ OCR-04W

天井の照明器具をリモコンで便利に操作

以下、Nature Remoをブラウザから操作するページのHTML・CSS・JavaScriptのコードとなります。

HTML・CSSのコード

HTML・CSSのコードは以下となります。また、CSSではリセットCSSを利用しています。

See the Pen Nature Remo Browser by yic666kr (@yic666kr) on CodePen.

上記コードではリモコン送信が成功した際に、ページ左上にある固定された箇所がCSSで点滅するようにします。

CSSで要素を点滅させる方法については以下のリンク先を参考にしてみてください。

HTMLのコードについて

以下、HTMLのポイントとなる箇所の説明となります。

ボタン毎に登録したリモコンについて

例のHTMLでは、アプリからボタン毎に一つづつ登録したリモコンの場合、指定したbutton要素をクリックすることで操作をします。

また、リモコン操作時にJavaScriptで利用するためbutton要素のvalueの値には、アプリでボタン登録時に設定する「名前」を指定します。

一括登録したエアコンのリモコンについて

例のHTMLでは、アプリから一括登録したエアコンのリモコンの場合、指定したbutton要素のクリックとoption要素の選択により操作します。

また、リモコン操作時にJavaScriptで利用するため、button要素とoption要素のvalueの値には、エアコンのリモコンを登録した際に設定される値を指定します。

それらの値は、APIで取得できるエアコンのリモコン情報から確認できます。詳しくは、以下のリンク先を参考にしてみてください。

JavaScriptのコード

以下、作成するページのJavaScriptとなり、上記のHTMLから読み込みます。

また、取得したAPIのアクセストークンは、以下のソースコード6行目のXXXXXXXXXXの箇所に記述します。

document.addEventListener('DOMContentLoaded', function(){

	// APIのリクエストヘッダーをセット
	const headers = new Headers({
		'Accept': 'application/json',
		'Authorization': 'Bearer XXXXXXXXXX'
	});

	// APIのPOSTリクエスト時に利用する関数
	function postSend(url, params = []){
		return fetch(url, {
			method: 'Post',
			body: params,
			headers: headers
		})
		.then(function(response) {
			if(response.ok) {
				document.getElementsByClassName('light_box')[0].classList.add('flash');
				setTimeout(function() {
					document.getElementsByClassName('light_box')[0].classList.remove('flash');
				}, 1000);
				return;
			}
			throw new Error('Network response was not ok.');
		})
		.catch(function(error) {
			console.error('Error:', error);
			document.getElementById('error_box').style.display = 'block';
			setTimeout(function() {
				document.getElementById('error_box').style.display = 'none';
			}, 8000);
		});
	};

	// APIのGETリクエスト時に利用する関数
	function getJson(url){
		return fetch(url, {
			headers: headers
		})
		.then(function(response) {
			if(response.ok) {
				return response.json();
			}
			throw new Error('Network response was not ok.');
		})
		.catch(function(error) {
			console.error('Error:', error);
		});
	};


	// エアコンの状態を扱う際に利用する変数
	let acOpe; // ON・OFF
	let acMode; // モード
	let acTemp; // 温度
	let acVol; // 風量


	// 現在のエアコンの状態を取得して、ページ上の表示を変更する関数
	function acNow() {
		const opeElem = document.getElementById('now_ope');
		opeElem.classList.remove('now_cool', 'now_warm', 'now_dry');
		if(acOpe == 'power-off'){
			document.getElementById('now_onoff').textContent = '停止中';
		}else{
			document.getElementById('now_onoff').textContent = '運転中';

			['cool', 'warm', 'dry'].forEach(function(mode){
				if(acMode == mode){
					opeElem.classList.add('now_' + mode);
					return;
				}
			});
		}
	}


	// APIからリモコンのデータを取得して、利用する値をセットする関数
	function dataSet() {
		url = 'https://api.nature.global/1/appliances';
		getJson(url)
		.then(function(dataJson) {
			// APIのレスポンスから利用するデータを取得
			sessionStorage.setItem('signals', JSON.stringify(dataJson[0]['signals']));
			sessionStorage.setItem('appliance', JSON.stringify(dataJson[1]['id']));
			settings = dataJson[1]['settings'];

			// ページ上のセレクトボックスで選択中の項目を現在のデータに変更する関数
			function selectSet(id, settings){
				let select = document.getElementById(id);
				let options = select.options ;
				for (let i = 0; i < options.length; i++){
					if(options[i].value == settings){
						options[i].selected = true;
						return;
					}
				}
			}

			// 上記関数をそれぞれのプルダウンメニューに適用
			acMode = settings['mode'];
			selectSet('mode_select', acMode);
			acTemp = settings['temp'];
			selectSet('temp_select', acTemp);
			acVol = settings['vol'];
			selectSet('vol_select', acVol);

			// 現在のON・OFFの状態をセット
			acOpe = settings['button'];
			
			// ページ上のエアコンの状態を変更
			acNow();
		});
	}
	dataSet();


	// APIから気温を取得して表示する関数
	function tempSet(){
		url = 'https://api.nature.global/1/devices';
		getJson(url)
		.then(function(devicesJson) {
			let now_temp = devicesJson[0]['newest_events']['te']['val'];
			now_temp = now_temp.toFixed(1);
			document.getElementById('now_temp').textContent = now_temp + '°' ;
		});
	}
	tempSet();


	let startTime = Date.now();
	let intervalId;

	// ウィンドウがアクティブになった際に実行する関数
	function play() {
		intervalId = setInterval(function() {
			// 変数startTimeの時刻から30分経過した場合
			if (Date.now() > (startTime + (30 * 60 * 1000))) {  
				// データ更新
				dataSet();
				tempSet();
				// 変数startTimeの時刻を更新
				startTime = Date.now();
			}
		}, 1000);
	}

	// ウィンドウがアクティブでなくなった際に実行する関数
 	function pause() {
		clearInterval(intervalId);
	}

	window.addEventListener('focus', play);
	window.addEventListener('blur', pause);


	// ボタン毎に登録をしたリモコンを送信する関数
	function irSend(value){
		let signals = sessionStorage.getItem('signals');
		signals = JSON.parse(signals);

		function signalGet(item) {
			if (item.name == value) {
				return true;
			} 
		}
		let signal = signals.filter(signalGet);
		let signalID = signal[0]['id'];

		url = 'https://api.nature.global/1/signals/' + signalID + '/send';
		postSend(url);
	}


	// エアコンのリモコンを送信する関数
	function acSend(){
		// プルダウンメニューで選択中のvalueを取得
		acMode = document.getElementById('mode_select').value;
		acTemp = document.getElementById('temp_select').value;
		acVol = document.getElementById('vol_select').value;
		// ページ上のエアコンの状態を変更
		acNow();

		// POSTリクエストのbodyの内容をセット
		let params = new URLSearchParams({
			'operation_mode': acMode,
			'temperature': acTemp,
			'air_volume': acVol
		}); 
		if(acOpe == 'power-off'){
			params.append('button', acOpe); 
		}

		let appliances = sessionStorage.getItem('appliance');
		appliances = JSON.parse(appliances);
		url = 'https://api.nature.global/1/appliances/' + appliances + '/aircon_settings';
		postSend(url, params);
	}


	// リモコンボタンのクリック時に関数を実行(ボタン毎に登録をしたリモコン)
	const irButton = document.getElementsByName('ir');
	for(let i = 0; i < irButton.length; i++) {
		irButton[i].addEventListener('click', function(){
			let irValue = irButton[i].value;
			irSend(irValue);
		});
	}


	// エアコンリモコンのボタンクリック時・操作時に関数を実行
	const acButton = document.getElementsByClassName('ac');
	for(let i = 0; i < acButton.length; i++) {
		let acTag = acButton[i].tagName;
		if(acTag == 'BUTTON'){
			acButton[i].addEventListener('click', function(){
				// button要素のvalue値を取得しエアコンのオン・オフ設定にセット
				let acValue = acButton[i].value;
				acOpe = acValue;
				acSend();
			});
		}else{
			acButton[i].addEventListener('change', acSend);
		}
	}

});

JavaScriptのコードについて

以下、JavaScriptのポイントとなる箇所の説明となります。

APIリクエスト時に利用する関数

ソースコード3行目からの以下の箇所では、APIのリクエスト時に利用する関数を、POSTとGETリクエストでそれぞれ作成しています。

// APIのリクエストヘッダーをセット
const headers = new Headers({
	'Accept': 'application/json',
	'Authorization': 'Bearer XXXXXXXXXX'
});

// APIのPOSTリクエスト時に利用する関数
function postSend(url, params = []){
	return fetch(url, {
		method: 'Post',
		body: params,
		headers: headers
	})
	.then(function(response) {
		if(response.ok) {
			document.getElementsByClassName('light_box')[0].classList.add('flash');
			setTimeout(function() {
				document.getElementsByClassName('light_box')[0].classList.remove('flash');
			}, 1000);
			return;
		}
		throw new Error('Network response was not ok.');
	})
	.catch(function(error) {
		console.error('Error:', error);
		document.getElementById('error_box').style.display = 'block';
		setTimeout(function() {
			document.getElementById('error_box').style.display = 'none';
		}, 8000);
	});
};

// APIのGETリクエスト時に利用する関数
function getJson(url){
	return fetch(url, {
		headers: headers
	})
	.then(function(response) {
		if(response.ok) {
			return response.json();
		}
		throw new Error('Network response was not ok.');
	})
	.catch(function(error) {
		console.error('Error:', error);
	});
};

上記の関数内ではFetchを利用してAPIリクエストを行っています。Fetchについては、以下のリンク先を参考にしてみてください。

Fetch を使う - Web API | MDN

エアコンの状態をセットする変数

ソースコード52行目からの以下の箇所では、エアコンの状態を扱う際に利用する変数を宣言しています。

// エアコンの状態を扱う際に利用する変数
let acOpe; // ON・OFF
let acMode; // モード
let acTemp; // 温度
let acVol; // 風量

現在のエアコンの状態をページ上に表示する関数

ソースコード59行目からの以下の箇所は、現在のエアコンの状態を取得して、ページ上の表示を設定する関数となります。

// 現在のエアコンの状態を取得して、ページ上の表示を変更する関数
function acNow() {
	const opeElem = document.getElementById('now_ope');
	opeElem.classList.remove('now_cool', 'now_warm', 'now_dry');
	if(acOpe == 'power-off'){
		document.getElementById('now_onoff').textContent = '停止中';
	}else{
		document.getElementById('now_onoff').textContent = '運転中';

		['cool', 'warm', 'dry'].forEach(function(mode){
			if(acMode == mode){
				opeElem.classList.add('now_' + mode);
				return;
			}
		});
	}
}

上記の関数内では、現在のエアコンの運転状況(ON-OFF)、モード(暖房・冷房・DRY)を取得して、ページ上に表示しています。

また、上記関数内ではclassの値を変更していますが、詳しくは以下のリンク先を参考にしてみてください。

APIからリモコンのデータを取得して利用する値をセットする関数

ソースコード78行目からの以下の箇所では、APIからリモコンのデータを取得して、利用する値をセットする関数を作成し、その関数を呼び出しています。

// APIからリモコンのデータを取得して、利用する値をセットする関数
function dataSet() {
	url = 'https://api.nature.global/1/appliances';
	getJson(url)
	.then(function(dataJson) {
		// APIのレスポンスから利用するデータを取得
		sessionStorage.setItem('signals', JSON.stringify(dataJson[0]['signals']));
		sessionStorage.setItem('appliance', JSON.stringify(dataJson[1]['id']));
		settings = dataJson[1]['settings'];

		// ページ上のセレクトボックスで選択中の項目を現在のデータに変更する関数
		function selectSet(id, settings){
			let select = document.getElementById(id);
			let options = select.options ;
			for (let i = 0; i < options.length; i++){
				if(options[i].value == settings){
					options[i].selected = true;
					return;
				}
			}
		}

		// 上記関数をそれぞれのプルダウンメニューに適用
		acMode = settings['mode'];
		selectSet('mode_select', acMode);
		acTemp = settings['temp'];
		selectSet('temp_select', acTemp);
		acVol = settings['vol'];
		selectSet('vol_select', acVol);

		// 現在のON・OFFの状態をセット
		acOpe = settings['button'];
		
		// ページ上のエアコンの状態を変更
		acNow();
	});
}
dataSet();

上記の関数内では、まず、Nature Remoに登録したリモコン情報などを取得できるAPIのエンドポイントから、リモコン操作やエアコンの現在の状態を表示する際に利用する値を取得しています。

そして、ページ上に表示する現在のエアコンの状態を設定しています。

また、上記関数内ではセレクトボックスの値を変更していますが、詳しくは以下のリンク先を参考にしてみてください。

APIから気温を取得して表示する関数

ソースコード118行目からの以下の箇所では、APIから気温を取得して表示する関数を作成し、その関数を呼び出しています。

// APIから気温を取得して表示する関数
function tempSet(){
	url = 'https://api.nature.global/1/devices';
	getJson(url)
	.then(function(devicesJson) {
		let now_temp = devicesJson[0]['newest_events']['te']['val'];
		now_temp = now_temp.toFixed(1);
		document.getElementById('now_temp').textContent = now_temp + '°' ;
	});
}
tempSet();

上記関数内では、Nature Remoのデバイスの情報を取得できるAPIのエンドポイントから、現在の温度のデータを取得しています。

一定時間経過ごとに処理を実行

ソースコード131行目からの以下の箇所は、スリープ復帰時など一定時間が経過した際に、ページ上に表示する気温とエアコンの設定を更新したいため、30分経過ごとに指定した関数を実行しています。

let startTime = Date.now();
let intervalId;

// ウィンドウがアクティブになった際に実行する関数
function play() {
	intervalId = setInterval(function() {
		// 変数startTimeの時刻から30分経過した場合
		if (Date.now() > (startTime + (30 * 60 * 1000))) {  
			// データ更新
			dataSet();
			tempSet();
			// 変数startTimeの時刻を更新
			startTime = Date.now();
		}
	}, 1000);
}

// ウィンドウがアクティブでなくなった際に実行する関数
function pause() {
	clearInterval(intervalId);
}

window.addEventListener('focus', play);
window.addEventListener('blur', pause);

上記では、ウィンドウがアクティブになった際に、setInterval()を利用し、30分経過ごと(スリープ時の経過時間も含む)を判断して処理を実行させています。

そして、ウィンドウがアクティブでなくなった際に、setInterval()の繰り返し処理を解除しています。

setInterval()について

setInterval()に関するコードについては、以下のリンク先を参考にしてみてください。

ウィンドウのアクティブ・非アクティブを判断

ウィンドウがアクティブになった際・アクティブでなくなった際に関数を実行する方法については、以下のリンク先を参考にしてみてください。

リモコン操作をする関数

ソースコード157行目からの以下の箇所は、APIを利用しボタン毎に登録をしたリモコンと、エアコンのリモコンを操作する、それぞれの関数となります。

また、ボタン毎に登録をしたリモコンと、エアコンのリモコンを送信するAPIではエンドポイントが異なります。

// ボタン毎に登録をしたリモコンを送信する関数
function irSend(value){
	let signals = sessionStorage.getItem('signals');
	signals = JSON.parse(signals);

	function signalGet(item) {
		if (item.name == value) {
			return true;
		} 
	}
	let signal = signals.filter(signalGet);
	let signalID = signal[0]['id'];

	url = 'https://api.nature.global/1/signals/' + signalID + '/send';
	postSend(url);
}


// エアコンのリモコンを送信する関数
function acSend(){
	// プルダウンメニューで選択中のvalueを取得
	acMode = document.getElementById('mode_select').value;
	acTemp = document.getElementById('temp_select').value;
	acVol = document.getElementById('vol_select').value;
	// ページ上のエアコンの状態を変更
	acNow();

	// POSTリクエストのbodyの内容をセット
	let params = new URLSearchParams({
		'operation_mode': acMode,
		'temperature': acTemp,
		'air_volume': acVol
	}); 
	if(acOpe == 'power-off'){
		params.append('button', acOpe); 
	}

	let appliances = sessionStorage.getItem('appliance');
	appliances = JSON.parse(appliances);
	url = 'https://api.nature.global/1/appliances/' + appliances + '/aircon_settings';
	postSend(url, params);
}

リモコン操作をする要素を指定

ソースコード201行目からの以下の箇所は、ボタン毎に登録をしたリモコンと、エアコンのリモコンを操作する要素に対して、それぞれaddEventListener()でイベントリスナーを指定しています。

それにより、それぞれのイベント発生時に、リモコンを操作する関数を実行しています。

// リモコンボタンのクリック時に関数を実行(ボタン毎に登録をしたリモコン)
const irButton = document.getElementsByName('ir');
for(let i = 0; i < irButton.length; i++) {
	irButton[i].addEventListener('click', function(){
		let irValue = irButton[i].value;
		irSend(irValue);
	});
}


// エアコンリモコンのボタンクリック時・操作時に関数を実行
const acButton = document.getElementsByClassName('ac');
for(let i = 0; i < acButton.length; i++) {
	let acTag = acButton[i].tagName;
	if(acTag == 'BUTTON'){
		acButton[i].addEventListener('click', function(){
			// button要素のvalue値を取得しエアコンのオン・オフ設定にセット
			let acValue = acButton[i].value;
			acOpe = acValue;
			acSend();
		});
	}else{
		acButton[i].addEventListener('change', acSend);
}

上記のように、指定したすべての要素に対してクリックイベントを適用させる方法については、以下のリンク先を参考にしてみてください。

参考サイトなど

コメント投稿コメント投稿欄を開く

コメントは、項目欄(*は必須項目)を入力し、「コメントを送信」ボタンをクリックしてください。 (メールアドレスは公開されることはありません。コメントの公開は承認制となります。)

Twitterで返信する場合はこちらから。