4
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

JavaScriptを使用してiPhoneのセンサ情報を取得してみる

Last updated at Posted at 2024-09-11

現在、遠隔地の車両データをモニタリングしたり解析できるクラウド計測解析システム「Smart Logging Service」、「Opti Meister Online」を開発しております。
SmartLoggingServiceでは専用デバイスで車両のCAN情報やGPS、加速度、カメラ映像など計測していますが、iPhoneがあれば専用のデバイスがなくてもGPS、カメラ、加速度など簡易的な計測できるはずです。iPhoneのセンサデータをSmartLoggingServiceにつなげるとどんな風に見えるのかまずは簡単なWebアプリを作成してみることにしました。

hw.drawio.png

作りたいもの

  • iPhoneのWebブラウザだけで動作する(chrome)
  • iPhoneの加速度、位置情報、カメラ画像をクラウドに送信して遠隔地でモニタリングする
  • モニタ用の画面は既存システムであるSmartLoggingServiceのモニタ画面につなげる

できたもの

iPhoneでの画面表示

iPhone画面上にカメラ画像、加速度、GPS座標を表示しています。これらのデータをクラウドに送信しています。

iphone.gif

SmartLoggingServiceのモニタ画面

iPhoneのカメラ画像、加速度、GPS座標をSmartLoggingServiceのモニタ画面からモニタリングすることができました。ほぼリアルタイムで体感0.2~0.3秒ほどの時間差で表示できていそうです。

smalog.gif

構成

iPhoneのセンサーデータはJavaScriptで取得します。
アプリはAWS ChaliceとJinja2を使ったサーバレスアプリとしました。Lambdaのソースコード内にhtmlやjavascriptを配置して配信しています。
データの共有はJavaScriptのpahoを使用してMQTTデータをAWS IotCoreに送信してします。
JavaScriptからAWSIoTCoreにアクセスするための認証情報はCognitoを使用しています。

design.png

センサーデータの取得

JavaScript+HTMLで加速度、GPS、カメラ画像のプレビューを表示します

GPS

Geolocation APIのNavigator:geolocationを使用します
https://developer.mozilla.org/en-US/docs/Web/API/Navigator/geolocation

GPS情報(html)
<div class="pl-2">
    <p>Device Position</p>
    <span id="longitude">0</span>
    <span id="latitude">0</span>
</div>
GPS情報(javascript)
function onSuccessGetPosition(position){
    document.getElementById('latitude').innerHTML = position.coords.latitude;
    document.getElementById('longitude').innerHTML = position.coords.longitude;
}
navigator.geolocation.getCurrentPosition(onSuccessGetPosition)

加速度

Device orientation eventsのDeviceMotionEventを使用します
https://developer.mozilla.org/en-US/docs/Web/API/DeviceMotionEvent

加速度(html)
<div class="pl-2">
    <p>Device Motion</p>
    <span id="x">0</span>
    <span id="y">0</span>
    <span id="z">0</span>
</div>
加速度(javascript)
function deviceMotionRequest () {
    if (DeviceMotionEvent.requestPermission) {
        DeviceMotionEvent.requestPermission()
        .then(permissionState => {
            if (permissionState === 'granted') {
                window.addEventListener("devicemotion", function (event) {
                    if (!event.accelerationIncludingGravity) {
                        alert('event.accelerationIncludingGravity is null');
                        return;
                    }
                    document.getElementById('x').innerHTML = event.accelerationIncludingGravity.x;
                    document.getElementById('y').innerHTML = event.accelerationIncludingGravity.y;
                    document.getElementById('z').innerHTML = event.accelerationIncludingGravity.z;
                })
            }
        })
        .catch(console.error);
    } else {
        alert('DeviceMotionEvent.requestPermission is not found')
    }
}

カメラ

Media Capture and Streams APIのMediaDevices: getUserMedia()を使用します
https://developer.mozilla.org/en-US/docs/Web/API/MediaDevices/getUserMedia

camera画像(html)
<div>      
    <p>Device Camera</p>  
    <button onClick="publishCamera()" class="btn btn-primary mr-1">Publish Camera</button>
    <video id="video" autoplay playsinline></video>
    <canvas id="canvas" width="320" height="240"></canvas>
</div>
camera画像(javascript)
async function startCamera() {
    try {
        const constraints = {
            video: {
                facingMode: "environment", // 背面カメラを使用
                width: { max: 320 },
                height: { max: 240 }
            }
        };
        
        const stream = await navigator.mediaDevices.getUserMedia(constraints);
        const video = document.getElementById('video');
        video.srcObject = stream;
    } catch (err) {
        console.error('Error accessing the camera: ', err);
    }
}
document.addEventListener('DOMContentLoaded', startCamera);

MQTT送信

pahoを使用してMQTTのpub/subを行います。
MQTTはIoT通信に多く活用されている軽量なデータ通信プロトコルです
認証情報にはあらかじめCongitoから取得した認証キーを使用します(認証情報取得するソースコードは省略)

mqtt.js
/**
 * mqttクラス
 */
class mqttClient {
    /**
     * コンストラクタ
     */
    constructor() {
        this.client = null;
    }

    /**
     * 接続
     * @param  {string} regionName リージョン ex)ap-northeast-1
     * @param  {string} awsIotEndpoint IoTCoreエンドポイント ex)xxxxxxxxx.iot.ap-northeast-1.amazonaws.com
     * @param  {string} accessKey アクセスキー
     * @param  {string} secretKey シークレットキー
     * @param  {object} onMessage メッセージ取得時の処理
     */
    connect(regionName, awsIotEndpoint, accessKey, secretKey, sessionToken, onMessage, onSuccess, onFailure) {        
        const endpoint = this.#createEndpoint(regionName, awsIotEndpoint, accessKey, secretKey, sessionToken);
        const clientId = Math.random().toString(36).substring(7);
        this.client = new Paho.MQTT.Client(endpoint, clientId);
        const connectOptions = {
            useSSL: true,
            timeout: 3,
            mqttVersion: 4,
            onSuccess : onSuccess,
            onFailure: onFailure
        };
        this.client.connect(connectOptions);
        this.client.onMessageArrived = onMessage;
        this.client.onConnectionLost = (e) => { console.log(e) };
    }

    publish(topicName, message){
        let mqtt_message = new Paho.MQTT.Message(message);
        mqtt_message.destinationName = topicName;
        this.client.send(mqtt_message);
    }

    /**
     * 受信トピック指定
     * @param  {string} topicName トピック名
     */
    subscribe(topicName, onSuccess) {
        const options = {
            onSuccess: onSuccess,
            onFailure: () => {
                console.log("Failed subscribe.");
            }
        }
        this.client.subscribe(topicName, options);
    }

    unsubscribe(topicName, onSuccess){
        const options = {
            onSuccess: onSuccess,
            onFailure: () => {
                console.log("Failed unsubscribe.");
            }
        }
        this.client.unsubscribe(topicName, options);
    }

    #createEndpoint(regionName, awsIotEndpoint, accessKey, secretKey, sessionToken) {
        var time = moment.utc();
        var dateStamp = time.format('YYYYMMDD');
        var amzdate = dateStamp + 'T' + time.format('HHmmss') + 'Z';
        var service = 'iotdevicegateway';
        var region = regionName;
        var secretKey = secretKey;
        var accessKey = accessKey;
        var algorithm = 'AWS4-HMAC-SHA256';
        var method = 'GET';
        var canonicalUri = '/mqtt';
        var host = awsIotEndpoint;
        var sessionToken = sessionToken;
   
        var credentialScope = dateStamp + '/' + region + '/' + service + '/' + 'aws4_request';
        var canonicalQuerystring = 'X-Amz-Algorithm=AWS4-HMAC-SHA256';
        canonicalQuerystring += '&X-Amz-Credential=' + encodeURIComponent(accessKey + '/' + credentialScope);
        canonicalQuerystring += '&X-Amz-Date=' + amzdate;
        canonicalQuerystring += '&X-Amz-SignedHeaders=host';
        var canonicalHeaders = 'host:' + host + '\n';
        const payloadHash = this.#sha256("");
        const canonicalRequest = method + '\n' + canonicalUri + '\n' + canonicalQuerystring + '\n' + canonicalHeaders + '\nhost\n' + payloadHash;   
        const string2Sign = algorithm + '\n' +  amzdate + '\n' +  credentialScope + '\n' +  this.#sha256(canonicalRequest);
        //getSignatureKey
        const hmacDate = CryptoJS.HmacSHA256(dateStamp, 'AWS4' + secretKey);
        const hmacRegion = CryptoJS.HmacSHA256(region, hmacDate);
        const hmacService = CryptoJS.HmacSHA256(service, hmacRegion);
        const signingKey = CryptoJS.HmacSHA256('aws4_request', hmacService);
        //sign
        const signature = CryptoJS.HmacSHA256(string2Sign, signingKey).toString(CryptoJS.enc.Hex);
   
        canonicalQuerystring += '&X-Amz-Signature=' + signature;
        return 'wss://' + host + canonicalUri + '?' + canonicalQuerystring + '&X-Amz-Security-Token=' + encodeURIComponent(sessionToken);
    }

    #sha256(msg) {
        const hash = CryptoJS.SHA256(msg);
        return hash.toString(CryptoJS.enc.Hex);
    };

}

まとめ

JavaScriptで簡単にiPhoneのセンサ情報を取得することができました。また、思っていたよりもリアルタイム性高くモニタ表示できたことに驚きました。今後は複数のiPhoneを使用した多地点分散計測やBluetoothやLightningケーブルでiPhoneに接続した外部のセンサ情報なども取得できないかトライしてみたいと思います。

参考

4
2
2

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
4
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?