【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」ボタンで一括登録
  • その他のリモコンはボタン毎に一つづつ登録

操作するページを作成

作成するページは、家電ごとに分かれたレイアウトとなり、それぞれのボタン・プルダウンメニューを選択することでリモコン操作を行います。また、リモコン送信が成功した際には、ページ左上にある固定された箇所が点滅するようにします。

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

HTML・CSSのコード

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

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

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 opeElement = document.getElementById('now_ope');
		const removeClasses = opeElement.classList.remove('now_cool', 'now_warm', 'now_dry');
		if(acOpe == 'power-off'){
			document.getElementById('now_onoff').textContent = '停止中';
			removeClasses;
		}else{
			document.getElementById('now_onoff').textContent = '運転中';
			removeClasses;

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


	// 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();


	// setInterval()のコールバック関数内で利用するため、現在の時刻を取得
	let startTime = Date.now();
	// 5秒毎に実行
	setInterval(function() {
		// 変数startTimeの時刻から30分経過した場合
		if (Date.now() > (startTime + (30 * 60 * 1000))) {  
			// データ更新
			dataSet();
			tempSet();
			// 変数startTimeの時刻を更新
			startTime = Date.now();
		}
	}, 5000);


	// ボタン毎に登録をしたリモコンを送信する関数
	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, tag = ''){
		if( tag == 'BUTTON'){
			// ON・OFFボタンがクリックされた場合に運転状態を変更
			const acOnoff = document.getElementsByName('ac_onoff');
			for(let i = 0; i < acOnoff.length; i++) {
				if(acOnoff[i].value == value){
					acOpe = value;
					break;
				}
			}
		}

		// プルダウンメニューで選択中の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++) {
		let irValue = irButton[i].value;
		irButton[i].addEventListener('click', function(){
			irSend(irValue);
		});
	}


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

});

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 opeElement = document.getElementById('now_ope');
	const removeClasses = opeElement.classList.remove('now_cool', 'now_warm', 'now_dry');
	if(acOpe == 'power-off'){
		document.getElementById('now_onoff').textContent = '停止中';
		removeClasses;
	}else{
		document.getElementById('now_onoff').textContent = '運転中';
		removeClasses;

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

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

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

ソースコード79行目からの以下の箇所では、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から気温を取得して表示する関数

ソースコード119行目からの以下の箇所では、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のエンドポイントから、現在の温度のデータを取得しています。

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

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

// setInterval()のコールバック関数内で利用するため、現在の時刻を取得
let startTime = Date.now();
// 5秒毎に実行
setInterval(function() {
	// 変数startTimeの時刻から30分経過した場合
	if (Date.now() > (startTime + (30 * 60 * 1000))) {  
		// データ更新
		dataSet();
		tempSet();
		// 変数startTimeの時刻を更新
		startTime = Date.now();
	}
}, 5000);

上記のsetInterval()では、5秒毎に処理を実行し、その際に現在時刻が変数startTimeにセットした時間+30分を超えていれば、指定した関数を実行します。そして、新たに変数startTimeに現在時刻をセットします。

それにより、スリープ時も含む一定時間経過ごとに処理を実行しています。

リモコン操作をする関数

ソースコード147行目からの以下の箇所は、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, tag = ''){
	if( tag == 'BUTTON'){
		// ON・OFFボタンがクリックされた場合に運転状態を変更
		const acOnoff = document.getElementsByName('ac_onoff');
		for(let i = 0; i < acOnoff.length; i++) {
			if(acOnoff[i].value == value){
				acOpe = value;
				break;
			}
		}
	}

	// プルダウンメニューで選択中の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);
}

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

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

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

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


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

 

参考サイトなど

コメント投稿またはTwitterで返信

コメントは、以下の項目(*は必須項目)を入力し「コメントを送信」ボタンから送信お願いします。メールアドレスは公開されることはありません。
Twitterで返信する場合はこちらから。

また、コメントは承認制となります。