【Nature Remo】PCやスマホのブラウザから家電リモコンを操作する方法|Cloud API

スマートリモコン『Nature Remo』では、赤外線送信などの操作ができるCloud APIとLocal APIが公開されています。
今回は、そのNature RemoのCloud APIを利用して、パソコンやスマホのブラウザから家電操作を行う例となります。
Nature Remoをブラウザから操作する準備
今回の例では、ローカルに配置したHTMLファイルにブラウザでアクセスし、そのページからNature Remoアプリから登録したリモコンを、Cloud APIを利用して操作をできるようにします。
また、リモコン機能のない照明や家電などに、リモコン機能を簡単に取り付ける機器もあります。詳しくは以下のリンク先を参考にしてみてください。
Cloud APIを利用する準備
Nature RemoのCloud APIでは、リモコン送信や現在の気温などを取得でき、利用するにはアクセストークンを発行する必要があります。
アクセストークンの発行方法については、以下のリンク先を参考にしてみてください。
APIのリクエスト制限について
5分以内に30回 以上のAPIリクエストを送信すると、その後はステータスコード 429 を返し、リクエストエラーとなります。
アプリからリモコンを登録
今回の例では、Nature Remoアプリで次のようにリモコンを登録しています。
エアコンのリモコン
エアコンのリモコンの場合は、「新しい家電」で「エアコン」を追加します。そして、エアコンリモコンの「ON」ボタンで一括登録します。
テレビのリモコン
テレビのリモコンの場合は、「新しい家電」で「テレビ」を追加します。そして、テレビリモコンの「ON」ボタンで一括登録します。
ボタン毎に登録するリモコン
ボタン毎に登録するリモコンの場合は、「新しい家電」で「その他」を追加します。そして、その家電内に、すべてのリモコンボタンを登録します。
Nature Remoを操作するページを作成
作成するページは、家電ごとに分かれたレイアウトとなり、それぞれのボタン・プルダウンメニューを選択することでリモコン操作を行います。
スマホからローカルのHTMLファイルにアクセスする場合
例ではCSS・JavaScriptファイルをHTMLファイルから読み込む形としていますが、スマホによってはローカルのHTMLファイルから外部ファイルを読み込めない場合があります。
その場合には、CSS・JavaScriptをインラインで記述します。
また、Androidスマホで、ローカルファイルのショートカットをホーム画面に追加する方法は、以下のリンク先を参考にしてみてください。
HTML・CSSのコード
HTML・CSSのコードは以下となります。また、CSSではリセットCSSを利用しています。
See the Pen Nature Remo Browser by yic666kr (@yic666kr) on CodePen.
上記コードではリモコン送信が成功した際に、ページ左上にある固定された箇所をCSSで点滅させています。
CSSで要素を点滅させる方法については以下のリンク先を参考にしてみてください。
HTML内のvalueの値を指定
リモコン操作時にJavaScriptで利用するため、HTML内のbutton要素・option要素のvalueには、Cloud APIで取得できるJSONデータから、それぞれのリモコン情報を次のように指定します。
Cloud APIでリモコン情報を取得する方法は、以下のリンク先を参考にしてみてください。
エアコンのリモコンボタン
エアコンのリモコンボタン・プルダウンメニューの場合、button要素とoption要素のvalueの値には、エアコンのリモコンボタン情報となる、JSON内のキーairconにあるデータをそれぞれ指定します。
テレビのリモコンボタン
テレビのリモコンボタンの場合、valueの値には、テレビのリモコンボタン情報となる、JSON内のキーbuttonsにあるnameの値を、それぞれ指定します。
ボタン毎に登録するリモコンボタン
ボタン毎に登録するリモコンボタンの場合、valueの値には、ボタン毎に登録したリモコンボタン情報となる、JSON内のキーsignalsにあるnameの値を、それぞれ指定します。
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);
});
};
// 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');
opeElement.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){
opeElement.classList.add('now_' + mode);
return;
}
});
}
}
// APIからリモコンなどのデータを取得して、利用する値を扱う関数
function dataSet() {
const url = 'https://api.nature.global/1/appliances';
getJson(url)
.then(function(dataJson) {
// 家電ごとのデータをセットするループ
for(let i = 0; i < dataJson.length; i++) {
const type = dataJson[i]['type'];
if(type == 'AC'){ // エアコンリモコンのデータ
sessionStorage.setItem('aircon', JSON.stringify(dataJson[i]['id']));
// エアコンの状態をセット
const settings = dataJson[i]['settings'];
acMode = settings['mode'];
acTemp = settings['temp'];
acVol = settings['vol'];
acOpe = settings['button'];
// エアコンのプルダウンメニューの状態を変更
const acSettings = new Map([
['mode_select', acMode],
['temp_select', acTemp],
['vol_select', acVol]
]);
acSettings.forEach(function(setting, id){
const select = document.getElementById(id);
const options = select.options ;
for (let i = 0; i < options.length; i++){
if(options[i].value == setting){
options[i].selected = true;
return;
}
}
});
// ページ上のエアコン表示を変更
acNow();
}else if(type == 'TV'){ // テレビリモコンのデータ
sessionStorage.setItem('tv', JSON.stringify(dataJson[i]['id']));
}else if(type == 'IR'){ // ボタン毎に登録したリモコンのデータ
sessionStorage.setItem('ir', JSON.stringify(dataJson[i]['signals']));
}
}
});
}
dataSet();
// 気温を取得して表示する関数
function tempSet(){
const url = 'https://api.nature.global/1/devices';
getJson(url)
.then(function(devicesJson) {
const now_temp = devicesJson[0]['newest_events']['te']['val'].toFixed(1);
document.getElementById('now_temp').textContent = now_temp + '°' ;
});
}
tempSet();
let startTime = Date.now();
let intervId;
// ウィンドウがアクティブ時に実行させる関数
function play() {
intervId = setInterval(function() {
// console.log(Date.now());
// 変数startTimeの時刻から一定時刻後の場合
if (Date.now() > (startTime + (30 * 60 * 1000))) {
// データ更新
dataSet();
tempSet();
// setInterval()内で利用する時刻を更新
startTime = Date.now();
}
}, 1000);
}
// ウィンドウが非アクティブ時に実行させる関数
function pause() {
clearInterval(intervId);
}
window.addEventListener('focus', play);
window.addEventListener('blur', pause);
// エアコンのリモコンを送信する関数
function acSend(){
// プルダウンメニューで選択中のvalueを取得
acMode = document.getElementById("mode_select").value;
acTemp = document.getElementById("temp_select").value;
acVol = document.getElementById("vol_select").value;
// ページ上のエアコンの表示を変更
acNow();
const aircon = sessionStorage.getItem('aircon');
const appliance = JSON.parse(aircon);
let params = new URLSearchParams({
'operation_mode': acMode,
'temperature': acTemp,
'air_volume': acVol
});
if(acOpe == 'power-off'){
params.append('button', acOpe);
}
const url = 'https://api.nature.global/1/appliances/' + appliance + '/aircon_settings';
postSend(url, params);
}
// テレビのリモコンを送信する関数
function tvSend(buttonName){
const tv = sessionStorage.getItem('tv');
const appliance = JSON.parse(tv);
const params = new URLSearchParams({
'button': buttonName
});
const url = 'https://api.nature.global/1/appliances/' + appliance + '/tv';
postSend(url, params);
}
// ボタン毎に登録をしたリモコンを送信する関数
function irSend(value){
const ir = sessionStorage.getItem('ir');
const signals = JSON.parse(ir);
function signalGet(item) {
if (item.name == value) {
return true;
}
}
const signal = signals.filter(signalGet);
const signalID = signal[0]['id'];
const url = 'https://api.nature.global/1/signals/' + signalID + '/send';
postSend(url);
}
// クリック・操作時に実行する関数を追加(エアコンのリモコン)
const acButton = document.getElementsByClassName('ac');
for(let i = 0; i < acButton.length; i++) {
const acTag = acButton[i].tagName;
if(acTag == 'BUTTON'){
acButton[i].addEventListener('click', function(){
// button要素のvalue値を取得しエアコンのオン・オフ設定にセット
const acValue = acButton[i].value;
acOpe = acValue;
acSend();
});
}else{
acButton[i].addEventListener('change', acSend);
}
}
// クリック時に実行する関数を追加(TVリモコン)
const tvButton = document.getElementsByName('tv');
for(let i = 0; i < tvButton.length; i++) {
tvButton[i].addEventListener('click', function(){
const tvValue = tvButton[i].value;
tvSend(tvValue);
});
}
// クリック時に実行する関数を追加(ボタン毎に登録をしたリモコン)
const irButton = document.getElementsByName('ir');
for(let i = 0; i < irButton.length; i++) {
irButton[i].addEventListener('click', function(){
const irValue = irButton[i].value;
irSend(irValue);
});
}
});
JavaScriptのコードについて
JavaScriptの簡単な説明となります。
APIリクエスト時に利用する関数
ソースコード3行目からの箇所では、APIのリクエスト時に利用する関数を、POSTとGETリクエストでそれぞれ作成しています。
また、APIリクエストにはFetchを利用しています。
// 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);
});
};
// 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);
});
};
エアコンの状態をセットする変数
ソースコード48行目からの箇所では、エアコンの状態を扱う際に利用する変数を宣言しています。
// エアコンの状態をセットする変数
let acOpe; // ON・OFF
let acMode; // モード
let acTemp; // 温度
let acVol; // 風量
ページ上のエアコン表示を変更する関数
ソースコード55行目からの箇所は、ページ上のエアコン表示を変更する関数となります。
// ページ上のエアコン表示を変更する関数
function acNow() {
const opeElement = document.getElementById('now_ope');
opeElement.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){
opeElement.classList.add('now_' + mode);
return;
}
});
}
}
上記の関数内では、エアコンの運転状況(ON-OFF)、モード(暖房・冷房・DRY)を取得して、ページ上のエアコン表示を変更しています。
また、上記関数内ではclassの値を変更していますが、詳しくは以下のリンク先を参考にしてみてください。
APIからデータを取得して利用する値を扱う関数
ソースコード74行目からの箇所では、APIからリモコンなどのデータを取得して、利用する値を扱う関数を作成し、その関数を呼び出しています。
// APIからリモコンなどのデータを取得して、利用する値を扱う関数
function dataSet() {
const url = 'https://api.nature.global/1/appliances';
getJson(url)
.then(function(dataJson) {
// 家電ごとのデータをセットするループ
for(let i = 0; i < dataJson.length; i++) {
const type = dataJson[i]['type'];
if(type == 'AC'){ // エアコンリモコンのデータ
sessionStorage.setItem('aircon', JSON.stringify(dataJson[i]['id']));
// エアコンの状態をセット
const settings = dataJson[i]['settings'];
acMode = settings['mode'];
acTemp = settings['temp'];
acVol = settings['vol'];
acOpe = settings['button'];
// エアコンのプルダウンメニューの状態を変更
const acSettings = new Map([
['mode_select', acMode],
['temp_select', acTemp],
['vol_select', acVol]
]);
acSettings.forEach(function(setting, id){
const select = document.getElementById(id);
const options = select.options ;
for (let i = 0; i < options.length; i++){
if(options[i].value == setting){
options[i].selected = true;
return;
}
}
});
// ページ上のエアコン表示を変更
acNow();
}else if(type == 'TV'){ // テレビリモコンのデータ
sessionStorage.setItem('tv', JSON.stringify(dataJson[i]['id']));
}else if(type == 'IR'){ // ボタン毎に登録したリモコンのデータ
sessionStorage.setItem('ir', JSON.stringify(dataJson[i]['signals']));
}
}
});
}
dataSet();
上記の関数内では、主に次の処理を行っています。
Nature Remoに登録したリモコン情報を返すAPIから、リモコン操作で利用する値や、エアコンの状態の値を取得し、変数やセッションストレージにセットしています。
また、上記関数内ではセレクトボックスの値を変更していますが、詳しくは以下のリンク先を参考にしてみてください。
APIから気温を取得して表示する関数
ソースコード125行目からの箇所では、APIから気温を取得して表示する関数を作成し、その関数を呼び出しています。
// 気温を取得して表示する関数
function tempSet(){
const url = 'https://api.nature.global/1/devices';
getJson(url)
.then(function(devicesJson) {
const now_temp = devicesJson[0]['newest_events']['te']['val'].toFixed(1);
document.getElementById('now_temp').textContent = now_temp + '°' ;
});
}
tempSet();
上記関数内では、Nature Remo本体がもつ情報を返すAPIから、現在の気温のデータを取得しています。
Nature Remo本体がもつ情報を返すCloud APIについては、以下のリンク先を参考にしてみてください。
一定時間経過ごとに処理を実行
ソースコード137行目からの以下の箇所は、スリープ復帰時など一定時間が経過した際に、ページ上に表示する気温とエアコンの設定を更新したいため、30分経過ごとに指定した関数を実行しています。
let startTime = Date.now();
let intervId;
// ウィンドウがアクティブ時に実行させる関数
function play() {
intervId = setInterval(function() {
// console.log(Date.now());
// 変数startTimeの時刻から一定時刻後の場合
if (Date.now() > (startTime + (30 * 60 * 1000))) {
// データ更新
dataSet();
tempSet();
// setInterval()内で利用する時刻を更新
startTime = Date.now();
}
}, 1000);
}
// ウィンドウが非アクティブ時に実行させる関数
function pause() {
clearInterval(intervId);
}
window.addEventListener('focus', play);
window.addEventListener('blur', pause);
上記では、ウィンドウがアクティブになった際に、setInterval()を利用し、30分経過ごと(スリープ時の経過時間も含む)を判断して処理を実行させています。
そして、ウィンドウがアクティブでなくなった際に、setInterval()の繰り返し処理を解除しています。
setInterval()について
setInterval()に関するコードについては、以下のリンク先を参考にしてみてください。
ウィンドウのアクティブ・非アクティブを判断
ウィンドウがアクティブになった際・アクティブでなくなった際に関数を実行する方法については、以下のリンク先を参考にしてみてください。
リモコン操作をする関数
ソースコード161行目からの箇所は、APIを利用してリモコンを操作する、それぞれの関数となります。
// エアコンのリモコンを送信する関数
function acSend(){
// プルダウンメニューで選択中のvalueを取得
acMode = document.getElementById("mode_select").value;
acTemp = document.getElementById("temp_select").value;
acVol = document.getElementById("vol_select").value;
// ページ上のエアコンの表示を変更
acNow();
const aircon = sessionStorage.getItem('aircon');
const appliance = JSON.parse(aircon);
let params = new URLSearchParams({
'operation_mode': acMode,
'temperature': acTemp,
'air_volume': acVol
});
if(acOpe == 'power-off'){
params.append('button', acOpe);
}
const url = 'https://api.nature.global/1/appliances/' + appliance + '/aircon_settings';
postSend(url, params);
}
// テレビのリモコンを送信する関数
function tvSend(buttonName){
const tv = sessionStorage.getItem('tv');
const appliance = JSON.parse(tv);
const params = new URLSearchParams({
'button': buttonName
});
const url = 'https://api.nature.global/1/appliances/' + appliance + '/tv';
postSend(url, params);
}
// ボタン毎に登録をしたリモコンを送信する関数
function irSend(value){
const ir = sessionStorage.getItem('ir');
const signals = JSON.parse(ir);
function signalGet(item) {
if (item.name == value) {
return true;
}
}
const signal = signals.filter(signalGet);
const signalID = signal[0]['id'];
const url = 'https://api.nature.global/1/signals/' + signalID + '/send';
postSend(url);
}
リモコン操作をする要素を指定
ソースコード217行目からの箇所は、リモコンを操作する要素に対して、それぞれaddEventListener()でイベントリスナーを指定しています。
それにより、それぞれのイベント発生時に、リモコンを操作する関数を実行します。
// クリック・操作時に実行する関数を追加(エアコンのリモコン)
const acButton = document.getElementsByClassName('ac');
for(let i = 0; i < acButton.length; i++) {
const acTag = acButton[i].tagName;
if(acTag == 'BUTTON'){
acButton[i].addEventListener('click', function(){
// button要素のvalue値を取得しエアコンのオン・オフ設定にセット
const acValue = acButton[i].value;
acOpe = acValue;
acSend();
});
}else{
acButton[i].addEventListener('change', acSend);
}
}
// クリック時に実行する関数を追加(TVリモコン)
const tvButton = document.getElementsByName('tv');
for(let i = 0; i < tvButton.length; i++) {
tvButton[i].addEventListener('click', function(){
const tvValue = tvButton[i].value;
tvSend(tvValue);
});
}
// クリック時に実行する関数を追加(ボタン毎に登録をしたリモコン)
const irButton = document.getElementsByName('ir');
for(let i = 0; i < irButton.length; i++) {
irButton[i].addEventListener('click', function(){
const irValue = irButton[i].value;
irSend(irValue);
});
}
上記のように、指定したすべての要素に対してクリックイベントを適用させる方法については、以下のリンク先を参考にしてみてください。