SwitchBotからスマートデイリーステーションという温度や天気などを表示するダッシュボードを発売するそうです。
であれば、手元にある余ったAndroidタブレットで同様の表示をするスマートディスプレイを作ってみました。
もろもろのファイルを以下に置きました。
通常はこのようにリモートのSambaファイルサーバに置かれている画像ファイルをランダムに表示しています。
左上角をタップすると以下のようなダッシュボードが表示されます。
おまけとして、右上をタップすると、スマホについているカメラを使ってWebRTC配信することができます。対向サーバとして、MediaMTXがセットアップされている前提ですが。
必要な設定は、左下角をタップして表示される設定ダイアログから行います。
画像ファイルをダウンロードするリモートSambaサーバのフォルダ名、ダッシュボードに表示する緯度経度と、必要に応じて、WebRTC配信する先のMediaMTXの
デスクトップに表示する情報の取得
Open-Metro.comを使っています。
以下のように呼び出しています。
https://api.open-meteo.com/v1/forecast?latitude=[lat]&longitude=[lng]&daily=weathercode,temperature_2m_max,temperature_2m_min&timezone=Asia/Tokyo
https://api.open-meteo.com/v1/forecast?latitude=[lat]&longitude=[lng]¤t=uv_index,precipitation_probability,pressure_msl,windspeed_10m,winddirection_10m,relative_humidity_2m,temperature_2m&daily=sunrise,sunset&timezone=Asia/Tokyo
室内温湿度の取得
室内温湿度は、SwitchBotを置いているのでそこから取得しています。
"use strict";
const base_url = "https://api.switch-bot.com/v1.1";
class SwitchBot{
constructor(token, secret){
this.authorization = token;
this.secret = secret;
}
async makeSign() {
const t = new Date().getTime();
const nonce = "RequestID";
const data = this.authorization + t + nonce;
const keyData = new TextEncoder().encode(this.secret);
const key = await crypto.subtle.importKey(
"raw",
keyData,
{ name: "HMAC", hash: "SHA-256" },
false,
["sign"]
);
const dataBuffer = new TextEncoder().encode(data);
const signature = await crypto.subtle.sign("HMAC", key, dataBuffer);
const sign = btoa(String.fromCharCode(...new Uint8Array(signature)));
return {
Authorization: this.authorization,
sign: sign,
nonce: nonce,
t: t
};
}
async getDeviceList(){
var headers = await this.makeSign();
var json = await do_get_with_authorization(base_url + "/devices", null, headers);
if( json.statusCode != 100 )
throw new Error("statusCode is not 100");
return json.body;
}
async getDeviceStatus(deviceId){
var headers = await this.makeSign();
var json = await do_get_with_authorization(base_url + "/devices/" + deviceId + "/status", null, headers);
if( json.statusCode != 100 )
throw new Error("statusCode is not 100");
return json.body;
}
async sendDeviceControlCommand(deviceId, commandType, command, parameter ){
var headers = await this.makeSign();
var params = {
command: command,
parameter: parameter,
commandType: commandType
};
var json = await do_post_with_authorization(base_url + "/devices/" + deviceId + "/commands", params, headers);
if( json.statusCode != 100 )
throw new Error("statusCode is not 100");
}
}
function do_get_with_authorization(url, qs, authorization) {
var params = new URLSearchParams(qs ? qs : {});
var params_str = params.toString();
var postfix = (params_str == "") ? "" : ((url.indexOf('?') >= 0) ? ('&' + params_str) : ('?' + params_str));
console.log(url + postfix);
return _fetch(url + postfix, {
method: 'GET',
headers: authorization
})
.then((response) => {
if (!response.ok)
throw new Error('status is not 200');
return response.json();
});
}
function do_post_with_authorization(url, body, authorization) {
var headers = JSON.parse(JSON.stringify(headers));
headers["Content-Type"] = "application/json";
return _fetch(url, {
method: 'POST',
body: JSON.stringify(body),
headers: headers
})
.then((response) => {
if (!response.ok)
throw new Error('status is not 200');
return response.json();
});
}
こんな感じで使っています。
const switchbot = new SwitchBot(SWITCHBOT_OPENTOKEN, SWITCHBOT_SECRET);
var result = await switchbot.getDeviceList();
console.log(result);
var item = result.deviceList.find(item => item.deviceType == 'WoIOSensor' || item.deviceType == 'Remote' );
if( item ){
var result = await switchbot.getDeviceStatus(item.deviceId);
console.log(result);
this.room = {
humidity: result.humidity,
temperature: result.temperature
}
}
Sambaサーバからの画像ファイルの取得
Sambaサーバとの連携ように、Cordovaプラグインを作成しました。
こんな感じで、list()を最適に呼び出して、フォルダ配下のすべての画像ファイルのリストを作成するのに使っています。
var result = await simplesamba_plugin.auth(SAMBA_USER, SAMBA_PASSWORD, SAMBA_HOST);
console.log(result);
var folder = this.$store.state.config.samba_folder || "/";
const scan_folder = async (list, folder) => {
var result = await simplesamba_plugin.list(folder);
// console.log(result);
for( let item of result.list ){
if( item.isDirectory ){
await scan_folder(list, folder + item.name);
}else{
var fname = item.name.toLowerCase();
if( fname.endsWith(".jpg") || fname.endsWith(".jpeg") || fname.endsWith(".png") )
list.push(folder + item.name);
}
}
};
var list = [];
await scan_folder(list, folder);
this.smb_list = list;
WebRTCで配信
以下を参照してください。
事前設定
設定ダイアログで変更な能な設定ができますが、環境に合わせて事前に以下の設定をしておく必要があります。
const SAMBA_USER = "[Sambaサーバのユーザ名]";
const SAMBA_PASSWORD = "[Sambaサーバのパスワード]";
const SAMBA_HOST = "[Sambaサーバのサーバ名]";
const SWITCHBOT_OPENTOKEN = "[SwitchBotのオープントークン)";
const SWITCHBOT_SECRET = "[SwitchBotのシークレット]";
const WEBRTC_USER = "[MediaMTXのユーザ名]";
const WEBRTC_PASSWORD = "[MediaMTXのパスワード]";
const WEBRTC_URL = "https://[MediaMTXのホスト名:28889";
コンパイル
% cd SmartClock
% cordova prepare
% cordova build android
% cordova run android
HTMLコンテンツをアプリに埋め込むのではなく、サーバに配置する場合は以下のようにしてください。
以下の2つのフォルダをサーバにコピーします。同じフォルダに配置します。
・SmartClock\www
・SmartClock\platforms\android\platform_www
config.xmlを変更します。
変更前
<?xml version='1.0' encoding='utf-8'?>
<widget id="jp.or.sample.SmartClock" version="1.0.0" xmlns="http://www.w3.org/ns/widgets" xmlns:cdv="http://cordova.apache.org/ns/1.0" xmlns:android="http://schemas.android.com/apk/res/android">
<name>SmartClock</name>
<description>Sample Apache Cordova App</description>
<author email="dev@cordova.apache.org" href="https://cordova.apache.org">
Apache Cordova Team
</author>
<content src="index.html" />
<preference name="Orientation" value="landscape" />
<platform name="android">
<config-file target="AndroidManifest.xml" parent="/manifest">
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.CAMERA"/>
<uses-permission android:name="android.permission.RECORD_AUDIO" />
<uses-feature android:name="android.hardware.camera" android:required="true"/>
</config-file>
<preference name="Fullscreen" value="true"/>
<preference name="AndroidLaunchMode" value="singleTask" />
<edit-config file="AndroidManifest.xml" target="/manifest/application" mode="merge">
<application android:usesCleartextTraffic="true" />
</edit-config>
</platform>
<allow-intent href="http://*/*" />
<allow-intent href="https://*/*" />
</widget>
変更後
<?xml version='1.0' encoding='utf-8'?>
<widget id="jp.or.sample.SmartClock" version="1.0.0" xmlns="http://www.w3.org/ns/widgets" xmlns:cdv="http://cordova.apache.org/ns/1.0" xmlns:android="http://schemas.android.com/apk/res/android">
<name>SmartClock</name>
<description>Sample Apache Cordova App</description>
<author email="dev@cordova.apache.org" href="https://cordova.apache.org">
Apache Cordova Team
</author>
<!--
<content src="index.html" />
-->
<allow-navigation href="https://[配置したホスト名]/cordova_base/*" />
<preference name="Orientation" value="landscape" />
<platform name="android">
<config-file target="AndroidManifest.xml" parent="/manifest">
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.CAMERA"/>
<uses-permission android:name="android.permission.RECORD_AUDIO" />
<uses-feature android:name="android.hardware.camera" android:required="true"/>
</config-file>
<preference name="Fullscreen" value="true"/>
<preference name="AndroidLaunchMode" value="singleTask" />
<edit-config file="AndroidManifest.xml" target="/manifest/application" mode="merge">
<application android:usesCleartextTraffic="true" />
</edit-config>
<content src="https://[配置したホスト名]/cordova_base/smartclock/android/index.html" />
</platform>
<allow-intent href="http://*/*" />
<allow-intent href="https://*/*" />
</widget>
変更後以下を再実行します。
% cordova build android
% cordova run android
参考
・Cordovaアプリ開発の備忘録
・Cordovaアプリ開発の備忘録(プラグイン編)
・MediaMTXで映像配信
以上

