Webアプリでデータの保存

HTML5+JavaScriptでローカルにデータを保存する実験。

  • データの保存にはWebStorageを用いる。
  • WebSotrageはKey-Valueストア方式。
  • 永続保存されるlocalStorageとブラウザ閉じたら消えるsessionStorageがある。
  • 保存/読み出しはsetItem/getItemメソッドを使う書き方と、キーをプロパティ名にする書き方がある。
  • 保存できる値は文字列のみである。
  • 数値なら読み出し後に数値に変換する。
  • 配列ならjoinで連結して保存し、読み出し後にsplitで分割する。
  • オブジェクトならJSON形式の文字列に変換して保存。

簡単なサンプル

<!DOCTYPE html><html lang="ja"><head>
<meta charset="UTF-8">
<title>Web Storage</title>
</head>
<body>
<p>
キー:<input id="key" type="text">
値:<input id="val" type="text">
<input type="button" value="保存" onClick="set_data()">
</p>
<p>
<div id="data_list"></div>
<input type="button" value="クリア" onClick="clear_data()">
</p>
<script>
//データを保存
function set_data() {
  var key = document.getElementById("key").value;
  var val = document.getElementById("val").value;
  localStorage.setItem(key, val);
  // リスト表示を更新
  show_list();
}

//データをクリア
function clear_data() {
  // 同オリジン(プロトコル+ドメイン+ポート)の保存データ全削除なので注意
  // 1個ずつ消すなら localStorage.removeItem(キー)
  localStorage.clear();
  // リスト表示を更新
  show_list();
}

//保存データをリスト表示
function show_list() {
  var result = "";
  for(var i=0; i<localStorage.length; i++){
    var key = localStorage.key(i);
    var val = localStorage.getItem(key);
    result += key + ":" + val + "<br>";
  }
  document.getElementById("data_list").innerHTML = result;
}

// 読み込み時にリスト表示
window.onload = show_list;

</script></body></html>

数値の場合

  // 保存
  var x = 1;
  var y = 2;
  localStorage.x = x;
  localStorage.y = y;

  // 読み出し  
  var x = parseInt(localStorage.x, 10); // 10進数として変換
  var y = parseInt(localStorage.y, 10); // 10進数として変換
  console.log(x+y);

配列の場合

  // 保存
  var ary = ["AAA","BBB","CCC"];
  localStorage.ary = ary.join(":"); // : を区切り文字として連結

  // 読み出し  
  var ary = localStorage.ary.split(":"); // : を区切り文字として分割
  console.log(ary[1]);

オブジェクトの場合

  // 保存
  var obj = {name:"Tarou", age:30};
  localStorage.obj = JSON.stringify(obj); // JSON形式にシリアライズ

  // 読み出し  
  var obj = JSON.parse(localStorage.obj); // JSON形式からデシリアライズ
  console.log(obj.name);

Webアプリで加速度センサ

HTML5+JavaScriptで加速度センサのデータを取得する実験。
PCのブラウザで開いても何も起こらないが、スマホのブラウザで開くと 向き・加速度・角速度のデータが表示される。スマホでも角速度センサ(ジャイロ)を持たないでは角速度は表示されない。

  • z軸は画面から垂直上向き、x軸は画面左向き、y軸は画面上向き
  • 角度のα, β, γはおのおのz, x, y軸まわりの角度 (オイラー角)
  • 向きを検出するイベントは deviceorientationであり、単位はdeg ( °)
  • 加速度・角速度を検出するイベントは devicemotion
  • accelerationは重力加速度をのぞいた加速度であり、単位はm/s^2
  • accelerationIncludingGravityは重力加速度をふくんだ加速度であり、単位はm/s^2
  • rotationRateは角速度であり、単位はdeg/s
<!DOCTYPE html><html><head>
<meta charset="UTF-8">
<title>Sensor Test</title>
<meta name="viewport" content="width=device-width">
</head><body>

<h2>向き[deg]</h2>
<p>a: <span id="ori-a"></span></p>
<p>b: <span id="ori-b"></span></p>
<p>g: <span id="ori-g"></span></p>

<h2>加速度(重力加速度のぞく)[m/s^2]</h2>
<p>x: <span id="acc-x"></span></p>
<p>y: <span id="acc-y"></span></p>
<p>z: <span id="acc-z"></span></p>

<h2>加速度(重力加速度ふくむ)[m/s^2]</h2>
<p>x: <span id="accG-x"></span></p>
<p>y: <span id="accG-y"></span></p>
<p>z: <span id="accG-z"></span></p>

<h2>角速度[deg/s]</h2>
<p>a: <span id="rot-a"></span></p>
<p>b: <span id="rot-b"></span></p>
<p>g: <span id="rot-g"></span></p>

<script>
window.addEventListener("deviceorientation", function(e){
 document.getElementById("ori-a").innerHTML = e.alpha.toFixed(3);
 document.getElementById("ori-b").innerHTML = e.beta.toFixed(3);
 document.getElementById("ori-g").innerHTML = e.gamma.toFixed(3);
});

window.addEventListener("devicemotion", function(e){
  var acc = e.acceleration;
  var accG = e.accelerationIncludingGravity;
  var rot = e.rotationRate;
  document.getElementById("acc-x").innerHTML = acc.x.toFixed(3);
  document.getElementById("acc-y").innerHTML = acc.y.toFixed(3);
  document.getElementById("acc-z").innerHTML = acc.z.toFixed(3);
  document.getElementById("accG-x").innerHTML = accG.x.toFixed(3);
  document.getElementById("accG-y").innerHTML = accG.y.toFixed(3);
  document.getElementById("accG-z").innerHTML = accG.z.toFixed(3);
  document.getElementById("rot-a").innerHTML = rot.alpha.toFixed(3);
  document.getElementById("rot-b").innerHTML = rot.beta.toFixed(3);
  document.getElementById("rot-g").innerHTML = rot.gamma.toFixed(3);
});
</script></body></html>

Webアプリでマルチタッチ

HTML5+JavaScriptでマルチタッチを取得する実験。
なるほど。マウスでは発生しないタッチ特有のイベントがあるのね。指が触れたとき、動いたとき、離れたときに各タッチのIDと座標がイベントから得られるのはAndroidアプリやiOSアプリと基本的に同じ考え方。

  • touchstart : 指が触れたとき
  • touchmove : 指が動いたとき
  • touchend : 指が離れたとき

下記のページにPCのブラウザからアクセスしてマウスでクリックやドラッグしても何も起こらない。スマホのブラウザからアクセスして赤い枠線内をタッチするとIDと座標が表示される。

<!DOCTYPE html><html><head>
<meta charset="UTF-8">
<title>Touch Event</title>
<meta name="viewport" content="width=device-width">
<style> canvas{ border: solid 1px red; } </style>
</head>
<body>
<canvas id="my canvas" width="300" height="300"></canvas>
<div id="monitor"></div>
<script>
var my_canvas = document.getElementById("my canvas");

// 指が触れたとき
my_canvas.addEventListener("touchstart" , touch_monitor);
// 指が動いたとき
my_canvas.addEventListener("touchmove" , touch_monitor);
// 指が離れたとき
my_canvas.addEventListener("touchend" , touch_monitor);

// 各タッチの座標を表示
function touch_monitor(e) {
  e.preventDefault(); // デフォルトイベントをキャンセル
  var s = "";
  for (var i = 0; i < e.touches.length; i++){
    var t = e.touches[i];
    s += "[" + t.identifier + "]";
    s += "x=" + t.pageX + ",";
    s += "y=" + t.pageY + "<br>";
  }
  document.getElementById("monitor").innerHTML = s;
}

</script>
</body></html>

「Androidでハードウェア制御」という物語

これは昔話。昔話なので記憶ちがいもあるかもしれない。

今は昔

Androidでハードウェアを制御してみようというストーリーは古くからあった。まだスマホがそれほど普及していない2010年ごろにはすでにあった。いろんなものが現れては消えた。まだ過去形で語るには早すぎるかもしれないが、もう一区切りついたのではないか。
f:id:licheng:20181124230503j:plain

ADK

2011年のことである。Google I/OADK(Android Open Accessory Development Kit)が発表された。AndroidスマホタブレットとアクセサリをUSBで接続しようというものだった。Android2.3.4以降と3.1以降が対応。アクセサリモードとホストモードがあって、アクセサリモードではAndroid側がUSBデバイス、ホストモードではAndroid側がUSBホストとなる。ただしホストモードに対応するのはAndroid3.x系のみであった。当時はAndroid2.x系とタブレット向けのAndroid3.x系に分かれていた時期で、スマホではUSBホスト機能が使えなかった。アクセサリモードのターゲットとしては、Arduino UNO+USBホストシールドなどが使えた。
日本Androidの会の神戸支部秋葉原支部ロボ部、横浜支部ロボ部の開発者のあいだでも盛り上がり、こんな本とかこんな本とか出た。神戸支部の@yishiiさんなどはADKが使えるPICボード(PIC ADK Miniboard)を開発したりされていた。とても思い出深い。でもADKじたいはその後マッハで廃れた。なにしろAndroid 4.0以降スマホでもUSBホスト機能が使えるようになったので存在意義が薄れてしまった。アクセサリというコンセプトがいまいちエコシステムを築けなかったようにも思う。こんなふうに短命ではあったが「Androidでハードウェア制御」の道を開いた歴史的意義は大きいと思う。

microbridge

おなじく2011年のことである。ADB(Android Debug Bridge)を利用してAndroidとハードウェアをUSBでつなごういう話が出てきた。それがmicrobridgeである。ADKに対応しない古いAndroidでもUSBデバッグモードをONにさえすれば利用できるメリットがあった。やはりArduino UNO+USBホストシールドなどがターゲットとして使えた。ADKと比べてもマイナーな存在だったけど、電子工作界隈ではけっこう流行った。ぼくもシャアザクカメラとか作った。今は昔の話である。なにぶんデバイス側にUSBホストが必要で対応ハードが限られているのはADKと同様で、ADKと同様にフェードアウトしていった。

FTDriver / Physicaloid

ADKがはやくもオワコンとか言われだした2013年、@ksksueさんがFTDriver / Physicaloidを公開した。FTDriverはAndroidのUSBホストAPIをラップしてFTDIのUSBシリアルICなどと手軽にシリアル通信できるようにしたもの。Physicaloidはさらに機能を統合し、Arduinoのファームを書き換えられるようにしたものだった。Android 4.x系の普及でスマホでもUSBホストが使えるようになったので、ターゲット側にUSBホスト機能が必要なADKやmicrobridgeよりこっちのほうがターゲットの幅が広がるというメリットがあった。同様のコンセプトのプロジェクトとしてusb-serial-for-androidというのもあった。

組込みAndroid

もう一つの流れとして、スマホタブレットでなく組込みLinuxボードにAndroidを載せようというアプローチがあった。当時はArmadilloとかBeagleBoardとか組込みLinuxボードがいろいろ出て来ており、組込みLinuxじたいが盛り上がっていた時期でもあった。そういったボードにAndroidを載せようという動きである。組込みLinuxアプリ開発がたいへんだからAndroid載せて開発を効率化しようぜ、というストーリーだった。
先に述べたスマホにハードウェアを接続するアプローチにはカジュアル勢も多かったのに比べ、こちらに取り組んでいたのはおもにガチ勢だったように思う。なにぶんLinuxカーネルやらデバイスドライバの知識が要るし、ビルドにはかなりのCPUパワーが要る。ハードウェアを叩くにはAndroid NDK、つまりあのめんどくさいJNIを使うことになる。しかも組込み機器には一般的にはスマホタブレットのようなGUIバイスは必ずしも存在しない。そしてどんどんスペック要求のあがっていくAndroidに対して当時の組込みLinuxボードはやや非力であった。加えて2012年に登場したラズパイがとにかく安くてお手軽だったのでLinuxボード使いたいカジュアル勢はみんなラズパイのほうに行ってしまった。

いつしか下火に

2010年代も半ばになると、だれもが当たり前にスマホを持っている時代になった。Androidは当たり前の技術になり、技術者界隈もかつての目新しさゆえににぎわいはなくなり、もともとイロモノ系色の強かった「Androidでハードウェア制御」という物語は語られることも少なくなった。アプリ技術者とかがカジュアルにモノづくりするならArduinoかラズパイが定番となり、AndroidスマホとはWiFiBluetoothを使って連携という形をとることが多くなった。そうなるとAndroid側は技術的には通常のアプリとなんら変わるところはなくなった。いや、目新しいところとしてはBLE(Bluetooth Low Energy)があったか。AndroidはBLE対応ではiOSに後れを取り、またAPIがクソだとか言われながらも、しばらくはその話題で盛り上がった。それも2016年ごろにはだいぶ落ち着いたように思う。

Android Things

2010年代半ばごろから新たなバズワードとして「IoT」が大きく喧伝されるようになった。2015年、ラズパイなどで動くIoT向けWindowsとしてWindows 10 IoT Coreが登場した。そしてそれに呼応するかのように2016年末にはAndroid Thingsが発表された。おなじくラズパイなどで動くIoT向けのAndroidである。画面は必須でない・単一アプリ動作(システムアプリ不在)など組込み向けに整理された軽量OSであり、ラズパイの性能が向上したこともあって、従来の組込みAndroidに比べて無理筋感がかなりなくなったと思う。めんどくさいJNI不要でハードウェアを叩けるThings Support Libraryも用意された。悪くないかもしれん。ただまあThings Support Libraryは本格的な組込み制御にはとうてい使えない。というよりガチの組込みに使うことを考えたAPIではないと思う。アプリ技術者にカジュアルにハードウェアを叩いてもらうためのAPIという印象を受けた。

2018年現在のぼくの答え

ぼくは組込み系のワンチップマイコンにリッチなUIを担わせるのは悪手だと思ってる。どうがんばってもスマホに比べれば見劣りするし、なにより開発効率が悪すぎる。だから何らかの通信手段を介してスマホタブレットに組込み機器のUIを担わせるのがスマートだと思ってる。通信手段としてはWiFiBluetooth(BLEまたはSPP)、USB(シリアル通信)の3つだろう。とりわけWiFiBluetoothは、ESP8266やESP32といった無線マイコンモジュールが出て来たことでハードウェア側の敷居が大いに下がった。コスト的にも技術的にも。
もう一つのアプローチ、組込みボードにAndroid Thingsを載せるのは、Androidの豊富なフレームワークを利用してアプリ開発を効率化しようというストーリーとしてはアリかもしれんと思ってる。ただしThings Support Libraryはおもちゃだと思ってるので、カジュアルなものと承知の上で割り切って使うか、ガチのリアルタイム部分はサブマイコンに任せるのが正解だろう。

個人的な今後の予定

個人的なプロジェクトとしては、しばらく放置気味だったスマホラジコンGPduino/GPPropoシリーズを再開したいと思ってる。Android Thingsのほうは、まあ気が向いたら何かやるかも(笑)

VC++のDLLをC#で使うなど

VC++でDLLを作る

  • [新しいプロジェクト] > [Visual C++] > [Win32アプリケーション] でプロジェクトを作成。
  • ここでは名前を MyCppDll にしたとする。
  • [アプリケーションの種類] は [DLL] を選択。
  • MyCppDll.cpp を下記のように編集してビルド。
#include "stdafx.h"

extern "C" __declspec(dllexport) int __stdcall MyAdd(int a, int b)
{
    return a + b;
}

VC++でDLLを使う

  • 同ソリューションに [新しいプロジェクト] > [Visual C++] > [Win32コンソール アプリケーション] でプロジェクトを作成。
  • ここでは名前を MyCppApp にしたとする。
  • MyCppApp を [スタートアップ プロジェクトに設定]。
  • [追加] > [参照] > [新しい参照の追加] で MyCppDll を追加。
  • MyCppApp.cpp を下記のように編集してビルド。
  • Ctrl+F5 で実行して動作を確認。(下記コードはふつうに実行すると一瞬で終了するため)
#include "stdafx.h"

extern "C" __declspec(dllexport) int __stdcall MyAdd(int a, int b);

int _tmain(int argc, _TCHAR* argv[])
{
    int a = 10, b = 20;
    int c = MyAdd(a, b);
    printf("%d + %d = %d\n", a, b, c);
    return 0;
}

C#でDLLを使う

  • 同ソリューションに [新しいプロジェクト] > [Visual C#] > [コンソール アプリケーション] でプロジェクトを作成。
  • ここでは名前を MyCsApp にしたとする。
  • MyCsApp を [スタートアップ プロジェクトに設定]。
  • Program.cs を下記のように編集してビルド。
  • DLLのパス指定に注意。同ディレクトリに配置すればファイル名だけで可。
  • Ctrl+F5 で実行して動作を確認。(下記コードはふつうに実行すると一瞬で終了するため)
using System;
using System.Runtime.InteropServices;

namespace MyCsApp
{
    class Program
    {
        [DllImport(@"..\..\..\Debug\MyCppDll.dll")]
        extern static int MyAdd(int a, int b);
        
        static void Main(string[] args)
        {
            int a = 10, b = 20;
            int c = MyAdd(a, b);
            Console.WriteLine(a + " + " + b + " = " + c);
        }
    }
}

VC++で.NETのDLLを作る

こんどは.NETクラスライブラリのDLLを作ってみる。

  • [新しいプロジェクト] > [Visual C++] > [CLR] > [クラス ライブラリ] でプロジェクトを作成。
  • ここでは名前を MyClrDll にしたとする。
  • MyClrDll.h と MyClrDll.cpp を下記のように編集してビルド。
#pragma once

using namespace System;

namespace MyClrDll {

    public ref class MyClrClass
    {
    public:
        static int MyAdd(int a, int b);
    };
}
#include "stdafx.h"
#include "MyClrDll.h"

using namespace MyClrDll;

int MyClrClass::MyAdd(int a, int b)
{
    return a + b;
}

C#で.NETのDLLを使う

  • 先に作成したC#アプリ MyCsApp を次のように変更する。
  • [追加] > [参照] で MyClrDll を追加。
  • Program.cs を下記のように編集してビルド。
  • Ctrl+F5 で実行して動作を確認。(下記コードはふつうに実行すると一瞬で終了するため)
using System;

namespace MyCsApp
{
    class Program
    {
        static void Main(string[] args)
        {
            int a = 10, b = 20;
            int c = MyClrDll.MyClrClass.MyAdd(a, b);
            Console.WriteLine(a + " + " + b + " = " + c);
        }
    }
}

Node.js と Socket.IO

Node.js と Socket.IO とは

  • Node.js : サーバーサイドをJavaScriptで書けるやつ
  • Socket.IO : Node.jsでWebSocketができるやつ

今回やること

  • WindowsのPCにNode.jsをインストールし、ローカルで実験。
  • Socket.IOを用いて、Node.jsとブラウザ間でWebSocket通信。

Node.jsのインストール

>node --version
v10.13.0
>npm --version
6.4.1

【注意】
うっかりBoxstarterのインストールを許可すると面倒なことになる。めっちゃ時間かかるし、確認もなしにかってにPCを再起動するし、再起動後もかってにバッチを走らせるし、かってにWindows Update自動起動を無効にするし、かってにPythonとかインストールしたりする。これを止めるには、バッチのウィンドウを閉じて、タスクマネージャの「スタートアップ」から boxstarter-post-restart.bat を「無効」にする。また、Windows Update自動起動の設定は、タスクマネージャの「サービス」の「サービス管理ツール」で Windows Update を「自動」にする。この面倒を避けるには、インストール途中の「Tools for Native Modules」の画面で、「Automatically install うんぬん」にチェックしないこと。

プロジェクトフォルダの作成とSocket.IOのインストール

てきとうなフォルダを作ってそこにSocket.IOをインストールする。DOS窓での作業となる。

cd てきとうなフォルダ
npm init -y
npm install socket.io

サーバのコード

app.jsという名前で同フォルダに作成する。
自分の環境ではポート80だとなんかエラーが出たので、替えてポート8080を使用した。

// HTTPサーバ
var app = require('http').createServer(handler)
// Socket.IO
var io = require('socket.io')(app);
// ファイルシステム
var fs = require('fs');

// HTTPサーバをポート8080で待ち受けさせる
app.listen(8080);

// HTTPリクエストのハンドラ
function handler (req, res) {
  // index.html を読み込む
  fs.readFile(__dirname + '/index.html',
  function (err, data) {
    // 読み込めなかったらエラー応答する (エラー500)
    if (err) {
      res.writeHead(500);
      return res.end('Error loading index.html');
    }
    // 読み込めたら 正常応答(index.htmlを送信)する
    res.writeHead(200);
    res.end(data);
  });
}

// Socket.IOの接続時のハンドラ
io.on('connection', function (socket) {
  // クライアントへ fugaイベントとデータ "FUGA" を送信
  socket.emit('fuga', 'FUGA');

  // hogeイベント時のハンドラ
  socket.on('hoge', function (data) {
    // クライアントから受け取ったデータをコンソールに出力する
    console.log(data);
    // クライアントへ piyoイベントとデータ "PIYO" を送信
    socket.emit('piyo', 'PIYO');
  });
})

クライアントのコード

index.htmlという名前で同フォルダに作成する。

<!DOCTYPE html>
<html lang="ja">
<head>
  <meta charset="UTF-8">
  <title>Client</title>
  <script src="/socket.io/socket.io.js"></script>
</head>
<body>
  <input type="button" id="hoge button" value="HOGE">
  <script>
    // 通信先のサーバを指定する
    var socket = io('http://localhost:8080'); // ポート指定注意

    // fugaイベントのハンドラ
    socket.on('fuga', function (data) {
      // サーバから受け取ったデータをコンソールに出力する
      console.log(data);
    });
    // piyoイベントのハンドラ
    socket.on('piyo', function (data) {
      // サーバから受け取ったデータをコンソールに出力する
      console.log(data);
    });
    // HOGEボタンのハンドラ
    window.addEventListener('DOMContentLoaded',function(e){
      // サーバーにhogeイベントとデータ "HOGE" を送信
      document.getElementById('hoge button').addEventListener('click',function(e){
        socket.emit('hoge', 'HOGE');
      });
    });
  </script>
</body>
</html>

動作確認

  • サーバを起動する。
node app.js
  • ブラウザで http://localhost:8080 を開くと、HOGEボタンが表示される。
  • このとき、ブラウザ側のコンソールに"FUGA"が出力される。
  • HOGEボタンを押すとサーバ側のコンソールに "HOGE"が出力される。
  • 応答としてブラウザ側のコンソールに"PIYO"が出力される。

ブラウザのコンソールは、Chromeならメニュー > [その他のツール] > [デベロッパーツール] で表示されるツールの [Console]タブ。Firefoxならメニュー [ツール] > [ウェブ開発] > [ウェブコンソール]で表示される。

f:id:licheng:20181122202804p:plain

参考ページ

webcake.stars.ne.jp

Xorshiftアルゴリズムで乱数

XorshiftアルゴリズムとはXORとシフトだけで品質の良い疑似乱数を生成するアルゴリズム

// 周期 (2^32 - 1) 版 Xorshiftアルゴリズム
#include <stdint.h>

static uint32_t x = 2463534242;

void xorshift32_seed(uint32_t seed)
{
    if (seed != 0){
        x = seed;
    }
}

uint32_t xorshift32(void)
{
    x ^= (x << 13);
    x ^= (x >> 17);
    x ^= (x << 15);
    return x;
}
// 周期 (2^128 - 1) 版 Xorshiftアルゴリズム
#include <stdint.h>

static uint32_t x = 123456789;
static uint32_t y = 362436069;
static uint32_t z = 521288629;
static uint32_t w = 88675123;

void xorshift128_seed(uint32_t seed)
{
    if (seed != 0){
        x = y = z = w = seed;
    }
}

uint32_t xorshift128(void)
{
    uint32_t t;

    t = x ^ (x << 11);
    x = y; y = z; z = w;
    w = (w ^ (w >> 19)) ^ (t ^ (t >> 8));
    
    return w;
}