0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Cordovaでスマートディスプレイを作る

Last updated at Posted at 2026-01-01

SwitchBotからスマートデイリーステーションという温度や天気などを表示するダッシュボードを発売するそうです。
であれば、手元にある余ったAndroidタブレットで同様の表示をするスマートディスプレイを作ってみました。

もろもろのファイルを以下に置きました。

通常はこのようにリモートのSambaファイルサーバに置かれている画像ファイルをランダムに表示しています。

image.png

左上角をタップすると以下のようなダッシュボードが表示されます。

image.png

おまけとして、右上をタップすると、スマホについているカメラを使って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]&current=uv_index,precipitation_probability,pressure_msl,windspeed_10m,winddirection_10m,relative_humidity_2m,temperature_2m&daily=sunrise,sunset&timezone=Asia/Tokyo

室内温湿度の取得

室内温湿度は、SwitchBotを置いているのでそこから取得しています。

SmartClock\www\js\switchbot.js
"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();
    });
}

こんな感じで使っています。

SmartClock\www\js\start.js
            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()を最適に呼び出して、フォルダ配下のすべての画像ファイルのリストを作成するのに使っています。

SmartClock\www\js\comp\comp_top.js
      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で配信

以下を参照してください。

MediaMTXで映像配信

事前設定

設定ダイアログで変更な能な設定ができますが、環境に合わせて事前に以下の設定をしておく必要があります。

SmartClock\www\js\store.js
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を変更します。

変更前

SmartClock\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>

変更後

SmartClock\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" />
-->

    <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で映像配信

以上

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?