TI SensorTagをブラウザから使う(1)：with Chromeブラウザ

TIが販売している小型で電池駆動のSensorTagを使ってみます。
　http://www.tij.co.jp/tool/jp/CC2650STK

計10個のセンサが付いているにもかかわらず、低価格です。

* 光
* デジタル・マイク
* 磁気センサ
* 湿度
* 圧力
* 加速度計
* ジャイロスコープ
* 磁力計
* 物体の温度
* 周囲温度

しかも、デバッガボードCC-DEVPACK-DEBUGを使うと、ファームウェアのカスタマイズができます。これも低価格です。
　https://service.macnica.co.jp/library/120701

いろいろできそうなので、以降、2回の投稿に渡っていじってみます。

1回目は、WebBluetooth APIを使ってブラウザから接続し、センサ情報を表示します。WebBluetooth APIが対応しているブラウザであれば大丈夫なのですが、今回は定番のChromeを使います。
Android、Windows10で動くことを確認しました。（Windows10のChromeで動いたのは驚きでした）

2回目は、LINE ThingsのBLE機能を使って接続します。単に、LINE Thingsの勉強のためです。
ですが、2回目は、デバッガボードCC-DEVPACK-DEBUGを所有している方のみが対象です。LINE Thingsに対応させるためにファームウェアのカスタマイズが必要だからです。

#WebBluetooth APIを使ったSensorTag用クラスを作成する

単に、HTMLとJavascriptを書いて、HTTPSからブラウズできる場所に置くだけです。
WebBluetooth APIを使うので、HTTPSである必要があります。

まずは、SensorTagを扱いやすくするためのクラスを作成します。
WebBluetooth APIを存分に使っています。

以下を参考にさせていただきました。

* npm sensortag
　https://github.com/sandeepmistry/node-sensortag

* CC2650 SensorTag User's Guide
　http://processors.wiki.ti.com/index.php/CC2650_SensorTag_User%27s_Guide#IR_Temperature_Sensor

* WebBluetooth API
　https://webbluetoothcg.github.io/web-bluetooth/

```sensortag.js
'use stricts';

const UUID_SERVICE_LINETHINGS = "【LINE Thingsのサービス探索用serviceUUID】"; //1回目では使いません。 

const UUID_SERVICE_IR_TEMPERATURE = 'f000aa00-0451-4000-b000-000000000000';
const UUID_CHAR_IR_TEMPERATURE_DATA = 'f000aa01-0451-4000-b000-000000000000';
const UUID_CHAR_IR_TEMPERATURE_ENABLE = 'f000aa02-0451-4000-b000-000000000000';
const UUID_CHAR_IR_TEMPERATURE_PERIOD = 'f000aa03-0451-4000-b000-000000000000';

const UUID_SERVICE_HUMIDITY = 'f000aa20-0451-4000-b000-000000000000';
const UUID_CHAR_HUMIDITY_DATA = 'f000aa21-0451-4000-b000-000000000000';
const UUID_CHAR_HUMIDITY_ENABLE = 'f000aa22-0451-4000-b000-000000000000';
const UUID_CHAR_HUMIDITY_PERIOD = 'f000aa23-0451-4000-b000-000000000000';

const UUID_SERVICE_BAROMETRIC_PRESSURE = 'f000aa40-0451-4000-b000-000000000000';
const UUID_CHAR_BAROMETRIC_PRESSURE_DATA = 'f000aa41-0451-4000-b000-000000000000';
const UUID_CHAR_BAROMETRIC_PRESSURE_ENABLE = 'f000aa42-0451-4000-b000-000000000000';
const UUID_CHAR_BAROMETRIC_PRESSURE_PERIOD = 'f000aa44-0451-4000-b000-000000000000';

const UUID_SERVICE_MOVEMENT = 'f000aa80-0451-4000-b000-000000000000';
const UUID_CHAR_MOVEMENT_DATA = 'f000aa81-0451-4000-b000-000000000000';
const UUID_CHAR_MOVEMENT_ENABLE = 'f000aa82-0451-4000-b000-000000000000';
const UUID_CHAR_MOVEMENT_PERIOD = 'f000aa83-0451-4000-b000-000000000000';

const UUID_SERVICE_OPTICAL = 'f000aa70-0451-4000-b000-000000000000';
const UUID_CHAR_OPTICAL_DATA = 'f000aa71-0451-4000-b000-000000000000';
const UUID_CHAR_OPTICAL_ENABLE = 'f000aa72-0451-4000-b000-000000000000';
const UUID_CHAR_OPTICAL_PERIOD = 'f000aa73-0451-4000-b000-000000000000';

const UUID_SERVICE_IO = 'f000aa64-0451-4000-b000-000000000000';
const UUID_CHAR_IO_DATA = 'f000aa65-0451-4000-b000-000000000000';
const UUID_CHAR_IO_CONFIG = 'f000aa66-0451-4000-b000-000000000000';

const UUID_SERVICE_SIMPLE_KEYS = BluetoothUUID.canonicalUUID(0xffe0);
const UUID_CHAR_SIMPLE_KEYS_DATA = BluetoothUUID.canonicalUUID(0xffe1);

class SensorTag{
    constructor(){
		this.bluetoothDevice = null;
        this.characteristics = new Map();
    }

    is_opened(){
        return this.bluetoothDevice ? true : false;
    }

    open(){
		return this.requestDevice(UUID_SERVICE_LINETHINGS);
    }

    close() {
		if (!this.is_opened())
			throw "Bluetooth Device is not opened";

		return Promise.resolve()
		.then(() =>{
			if (this.bluetoothDevice.gatt.connected) {
				console.log('Execute : disconnect');
                this.bluetoothDevice.gatt.disconnect();
                this.bluetoothDevice = null;
				this.characteristics.clear();
			} else {
                this.bluetoothDevice = null;
				this.characteristics.clear();
				throw "Bluetooth Device is already disconnected";
			}
		});
	}

    setup(){
		console.log('Execute : setup');

		return this.bluetoothDevice.gatt.connect()
		.then(server => {
            return this.setup_ir_tempeature(server);
        })
		.then(server => {
            return this.setup_humidity(server);
        })
        .then(server => {
            return this.setup_barometric_pressure(server);
        })
		.then(server => {
            return this.setup_movement(server);
        })
		.then(server => {
            return this.setup_optical(server);
        })
		.then(server => {
            return this.setup_io(server);
        })
		.then(server => {
            return this.setup_simple_keys(server);
        })
		.then(server =>{
			console.log('setup done');
			return this.bluetoothDevice.name;
		});
    }

    set_enable_movement(xg, yg, zg, xa, ya, za, mag, wakeon, arange){
        if (!this.is_opened())
            return;

        var bits = 0;
        bits |= (xg ? 0x01 : 0x00) << 13;
        bits |= (yg ? 0x01 : 0x00) << 14;
        bits |= (zg ? 0x01 : 0x00) << 15;
        bits |= (xa ? 0x01 : 0x00) << 10;
        bits |= (ya ? 0x01 : 0x00) << 11;
        bits |= (za ? 0x01 : 0x00) << 12;
        bits |= (mag ? 0x01 : 0x00) << 9;
        bits |= (wakeon ? 0x01 : 0x00) << 8;
        bits |= (arange & 0x03) << 6;
        this.paRange = Math.pow(2, (arange & 0x03) + 1);
        return this.characteristics.get(UUID_CHAR_MOVEMENT_ENABLE).writeValue(Uint8Array.from([(bits >> 8) & 0xff, bits & 0xff]));
    }

    set_enable(uuid, enable){
        if (!this.is_opened())
            return;

        return this.characteristics.get(uuid).writeValue(Uint8Array.from([enable ? 0x01 : 0x00]));
    }

    set_period(uuid, period){
        return this.characteristics.get(uuid).writeValue(Uint8Array.from([period & 0xff]));
    }

    set_callback(callback){
        this.callback = callback;
    }

    onDataChanged(event){
        console.log('onDataChanged');
        let characteristic = event.target;

        switch(characteristic.uuid){
            case UUID_CHAR_IR_TEMPERATURE_DATA:{
                var ambientTemperature = characteristic.value.getUint16(2, true) / 128.0;
                var objectTemperature = characteristic.value.getUint16(0, true) / 128.0;
                if( this.callback ){
                    this.callback({
                        type: 'temperature',
                        ambient_temperature: ambientTemperature,
                        object_temperature: objectTemperature
                    });
                }
                break;
            }
            case UUID_CHAR_HUMIDITY_DATA:{
                var temperature  = -40 + ((165  * characteristic.value.getUint16(0, true)) / 65536.0);
                var humidity  = characteristic.value.getUint16(2, true) * 100 / 65536.0;
                if( this.callback ){
                    this.callback({
                        type: 'humidity',
                        temperature: temperature,
                        humidity : humidity 
                    });
                }
                break;
            }
            case UUID_CHAR_BAROMETRIC_PRESSURE_DATA:{
                var flTempBMP;
                var flPressure;
              
                flTempBMP = (characteristic.value.getUint16(0, true) | (characteristic.value.getUint8(2) << 16))/ 100.0;
                flPressure = (characteristic.value.getUint16(3, true) | (characteristic.value.getUint8(5) << 16)) / 100.0;
                if( this.callback ){
                    this.callback({
                        type: 'pressure',
                        pressure: flPressure,
                        temperature: flTempBMP
                    });
                }

                break;
            }
            case UUID_CHAR_MOVEMENT_DATA:{
                // 250 deg/s range
                var xG = characteristic.value.getInt16(0, true) / 128.0;
                var yG = characteristic.value.getInt16(2, true) / 128.0;
                var zG = characteristic.value.getInt16(4, true) / 128.0;

                // we specify 8G range in setup
                var xA = characteristic.value.getInt16(6, true) / (32768.0 / this.paRange);
                var yA = characteristic.value.getInt16(8, true) / (32768.0 / this.paRange);
                var zA = characteristic.value.getInt16(10, true) / (32768.0 / this.paRange);

                // magnetometer (page 50 of http://www.invensense.com/mems/gyro/documents/RM-MPU-9250A-00.pdf)
                var xM = characteristic.value.getInt16(12, true);
                var yM = characteristic.value.getInt16(14, true);
                var zM = characteristic.value.getInt16(16, true);
                if( this.callback ){
                    this.callback({
                        type: 'movement',
                        xG: xG,
                        yG: yG,
                        zG: zG,
                        xA: xA,
                        yA: yA,
                        zA: zA,
                        xM: xM,
                        yM: yM,
                        zM: zM,
                    });
                }
                break;
            }
            case UUID_CHAR_OPTICAL_DATA:{
                var rawLux = characteristic.value.getUint16(0, true);

                var exponent = (rawLux & 0xF000) >> 12;
                var mantissa = (rawLux & 0x0FFF);
              
                var flLux = mantissa * Math.pow(2, exponent) / 100.0;
                if( this.callback ){
                    this.callback({
                        type: 'optical',
                        luminance : flLux
                    });
                }

                break;
            }
            case UUID_CHAR_SIMPLE_KEYS_DATA:{
                if( this.callback ){
                    this.callback({
                        type: 'keys',
                        keys : characteristic.value.getUint8(0)
                    });
                }

                break;

            }
            default:
                console.log('Unkown data', characteristic);
                break;
        }
    }

    onDisconnect(event){
		console.log('onDisconnect');
	}

    requestDevice(service_uuid){
        return new Promise((resolve, reject) =>{
            liff.init(data => resolve(data), error => reject(error) );
        })
        .then(data =>{
            console.log('Execute : requestDevice(liff)');
            return this.liffCheckAvailablityAndDo()
            .then( () =>{
                return liff.bluetooth.requestDevice();
            })
            .then(device =>{
                console.log("requestDevice OK");
                this.characteristics.clear();
                this.bluetoothDevice = device;
                this.bluetoothDevice.addEventListener('gattserverdisconnected', (event) => {
                    this.onDisconnect(event);
                });
                return this.bluetoothDevice.name;
            });
        })
        .catch(error =>{
            console.log('Execute : requestDevice(normal)');
            return navigator.bluetooth.requestDevice({
                filters: [{services:[ /* service_uuid */ BluetoothUUID.canonicalUUID(0x180f) ]}], // 1回目ではとりあえずこんな感じ
                  optionalServices: [
                      UUID_SERVICE_IR_TEMPERATURE,
                      UUID_SERVICE_HUMIDITY,
                      UUID_SERVICE_BAROMETRIC_PRESSURE,
                      UUID_SERVICE_MOVEMENT,
                      UUID_SERVICE_OPTICAL,
                      UUID_SERVICE_IO,
                      UUID_SERVICE_SIMPLE_KEYS
                ]
            })
            .then(device => {
                console.log("requestDevice OK");
                this.characteristics.clear();
                this.bluetoothDevice = device;
                this.bluetoothDevice.addEventListener('gattserverdisconnected', (event) => {
                    this.onDisconnect(event)
                });
                return this.bluetoothDevice.name;
            });
        });
    }

    async liffCheckAvailablityAndDo() {
        console.log('calling liffCheckAvailablityAndDo');

        await liff.initPlugins(['bluetooth']);
        for( var i = 0 ; i < 10 ; i++ ){
            var isAvailable = await liff.bluetooth.getAvailability();
            if( isAvailable )
                return;
            await wait_async(3000);
        }
        throw 'error liff.bluetooth.getAvailability';
    }

	setCharacteristic(service, characteristicUuid) {
		return service.getCharacteristic(characteristicUuid)
		.then(characteristic => {
			console.log('setCharacteristic : ' + characteristicUuid);
			this.characteristics.set(characteristicUuid, characteristic);
			return service;
		});
	}

    startNotify(uuid) {
		console.log('Execute : startNotifications');
        var characteristic = this.characteristics.get(uuid);
		if( characteristic === undefined )
			throw "Not Connected";

		characteristic.addEventListener('characteristicvaluechanged', (event) =>{
			this.onDataChanged(event);
		});
		return characteristic.startNotifications();
	}

	stopNotify(uuid){
		console.log('Execute : stopNotifications');
		var characteristic = this.characteristics.get(uuid);
		if( characteristic === undefined )
			throw "Not Connected";

		return characteristic.stopNotifications();
    }
    
    setup_ir_tempeature(server){
        return server.getPrimaryService(UUID_SERVICE_IR_TEMPERATURE)
        .then(service =>{
            return Promise.all([
                this.setCharacteristic(service, UUID_CHAR_IR_TEMPERATURE_DATA),
                this.setCharacteristic(service, UUID_CHAR_IR_TEMPERATURE_ENABLE),
                this.setCharacteristic(service, UUID_CHAR_IR_TEMPERATURE_PERIOD)
            ]);
        })
        .then(values =>{
            return this.startNotify(UUID_CHAR_IR_TEMPERATURE_DATA);
        })
        .then(()=>{
            return server;
        });
    }

    setup_humidity(server){
        return server.getPrimaryService(UUID_SERVICE_HUMIDITY)
        .then(service =>{
            return Promise.all([
                this.setCharacteristic(service, UUID_CHAR_HUMIDITY_DATA),
                this.setCharacteristic(service, UUID_CHAR_HUMIDITY_ENABLE),
                this.setCharacteristic(service, UUID_CHAR_HUMIDITY_PERIOD)
            ]);
        })
        .then(values =>{
            return this.startNotify(UUID_CHAR_HUMIDITY_DATA);
        })
        .then(()=>{
            return server;
        });
    }

    setup_barometric_pressure(server){
        return server.getPrimaryService(UUID_SERVICE_BAROMETRIC_PRESSURE)
        .then(service =>{
            return Promise.all([
                this.setCharacteristic(service, UUID_CHAR_BAROMETRIC_PRESSURE_DATA),
                this.setCharacteristic(service, UUID_CHAR_BAROMETRIC_PRESSURE_ENABLE),
                this.setCharacteristic(service, UUID_CHAR_BAROMETRIC_PRESSURE_PERIOD)
            ]);
        })
        .then(values =>{
            return this.startNotify(UUID_CHAR_BAROMETRIC_PRESSURE_DATA);
        })
        .then(()=>{
            return server;
        });
    }

    setup_movement(server){
        return server.getPrimaryService(UUID_SERVICE_MOVEMENT)
        .then(service =>{
            return Promise.all([
                this.setCharacteristic(service, UUID_CHAR_MOVEMENT_DATA),
                this.setCharacteristic(service, UUID_CHAR_MOVEMENT_ENABLE),
                this.setCharacteristic(service, UUID_CHAR_MOVEMENT_PERIOD)
            ]);
        })
        .then(values =>{
            return this.startNotify(UUID_CHAR_MOVEMENT_DATA);
        })
        .then(()=>{
            return server;
        });
    }

    setup_optical(server){
        return server.getPrimaryService(UUID_SERVICE_OPTICAL)
        .then(service =>{
            return Promise.all([
                this.setCharacteristic(service, UUID_CHAR_OPTICAL_DATA),
                this.setCharacteristic(service, UUID_CHAR_OPTICAL_ENABLE),
                this.setCharacteristic(service, UUID_CHAR_OPTICAL_PERIOD)
            ]);
        })
        .then(values =>{
            return this.startNotify(UUID_CHAR_OPTICAL_DATA);
        })
        .then(()=>{
            return server;
        });
    }

    setup_io(server){
        return server.getPrimaryService(UUID_SERVICE_IO)
        .then(service =>{
            return Promise.all([
                this.setCharacteristic(service, UUID_CHAR_IO_DATA),
                this.setCharacteristic(service, UUID_CHAR_IO_CONFIG)
            ]);
        })
        .then(()=>{
            return server;
        });
    }

    setup_simple_keys(server){
        return server.getPrimaryService(UUID_SERVICE_SIMPLE_KEYS)
        .then(service =>{
            return Promise.all([
                this.setCharacteristic(service, UUID_CHAR_SIMPLE_KEYS_DATA)
            ]);
        })
        .then(values =>{
            return this.startNotify(UUID_CHAR_SIMPLE_KEYS_DATA);
        })
        .then(()=>{
            return server;
        });
    }
}

function wait_async(timeout){
	return new Promise((resolve, reject) =>{
		setTimeout(resolve, timeout);
	});
}
```

UUID_SERVICE_LINETHINGS は1回目では使わないので、とりあえず変更する必要はありません。

クラスの実装を一部補足します。

* open()

まず何よりこのメソッドを呼び出します。

　liff.init(data => resolve(data), error => reject(error) )

これは、LINE Thingsを使うためのLINEアプリ(LIFF)から呼ばれたかどうかを判別するためのコードです。1回目の記事ではChromeからの呼び出しなのでエラーとなり、catchの処理に移ります。

　navigator.bluetooth.requestDevice

引数に指定したサービスUUIDを持ったBLEデバイスを検索(Scan)します。素のSensorTagではバッテリーサービス(0x180f)を持っているので、それを指定しています。

* setup()

センサー情報を読み出すためのCharacteristicを検索するとともに、センサー情報のNotifyを有効にします。
CharacteristicのUUIDは決まっているので、それを各Primary Serviceで指定しています。

* set_enable()

センサー計測を有効にします。setup()ではNotifyができる状態になっただけです。センサー情報の取得の有効化・無効化はこのset_enable()で切り替えます。

* set_period()

センサー情報のNotify間隔を指定します。デフォルトは1秒間隔になっています。

* set_callback()

これは、センサー情報の取得はReadではなくNotifyで受信するようにしたいため、このメソッドに受信するコールバック関数を指定します。
SensorTagからNotifyされてくるデータを各センサーのタイプに合わせてコンバートします。
（※気圧のコンバートのための計算式がわかりませんでした。。。。MovementのEnableマスクも怪しい。。。）

あとは、このクラスを使ってブラウザに表示させます。Vueを使っています。

```start.js
'use strict';

var sensortag = new SensorTag();

var vue_options = {
    el: "#top",
    data: {
        movement: {},
        temperature: {},
        humidity: {},
        pressure: {},
        optical: {},
        temperature_enable : false,
        movement_enable : false,
        humidity_enable : false,
        pressure_enable : false,
        optical_enable : false,
        button_title: '接続',
        left_key: null,
        right_key: null,

        progress_title: '',
    },
    computed: {
    },
    methods: {
        start: async function(){
            if( !sensortag.is_opened() ){
                try{
                    this.button_title = '接続中';
                    sensortag.set_callback(this.on_receive);
                    await sensortag.open();
                    await sensortag.setup();
                    this.button_title = '切断';
                }catch(error){
                    console.log(error);
                    alert(error);
                    this.button_title = '接続';
                }
            }else{
                try{
                    this.button_title = '切断中';
                    sensortag.close();
                    this.button_title = '接続';
                }catch(error){
                    console.log(error);
                    this.button_title = '切断';
                }
            }
        },
        set_enable: async function(type){
            if( type == 'movement'){
                if( !this.movement_enable )
                    await sensortag.set_enable_movement(true, true, true, true, true, true, true, false, 3);
                else
                    await sensortag.set_enable_movement(false, false, false, false, false, false, false, false, 0);
            }else{
                var uuid;
                var value;
                switch(type){
                    case 'temperature':
                        uuid = UUID_CHAR_IR_TEMPERATURE_ENABLE;
                        value = !this.temperature_enable;
                        break;
                    case 'humidity':
                        uuid = UUID_CHAR_HUMIDITY_ENABLE;
                        value = !this.humidity_enable;
                        break;
                    case 'pressure':
                        uuid = UUID_CHAR_BAROMETRIC_PRESSURE_ENABLE;
                        value = !this.pressure_enable;
                        break;
                    case 'optical':
                        uuid = UUID_CHAR_OPTICAL_ENABLE;
                        value = !this.optical_enable;
                        break;
                }
                await sensortag.set_enable(uuid, value);
            }
        },
        on_receive: function(data){
            console.log(data);
            switch( data.type ){
                case 'temperature':
                    this.temperature = data;
                    break;
                case 'humidity':
                    this.humidity = data;
                    break;
                case 'pressure':
                    this.pressure = data;
                    break;
                case 'movement':
                    this.movement = data;
                    break;
                case 'optical':
                    this.optical = data;
                    break;
                case 'keys':
                    if( data.keys & 0x01 )
                        this.left_key = true;
                    else
                        this.left_key = false;
                    if( data.keys & 0x02 )
                        this.right_key = true;
                    else
                        this.right_key = false;
                    break;
            }
        }
    },
    created: function(){
    },
    mounted: function(){
    }
};
var vue = new Vue( vue_options );
```

以下は、HTMLファイルです。

```index.html
<!DOCTYPE html>
<html lang="ja">
<head>
  <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
  <meta http-equiv="Content-Security-Policy" content="default-src * data: gap: https://ssl.gstatic.com 'unsafe-eval' 'unsafe-inline'; style-src * 'unsafe-inline'; media-src *; img-src * data: content: blob:;">
  <meta name="format-detection" content="telephone=no">
  <meta name="msapplication-tap-highlight" content="no">
  <meta name="apple-mobile-web-app-capable" content="yes" />
  <meta name="viewport" content="user-scalable=no, initial-scale=1, maximum-scale=1, minimum-scale=1, width=device-width">

  <!-- jQuery (necessary for Bootstrap's JavaScript plugins) -->
  <script src="https://ajax.googleapis.com/ajax/libs/jquery/1.12.4/jquery.min.js"></script>
  <!-- Latest compiled and minified CSS -->
  <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u" crossorigin="anonymous">
  <!-- Optional theme -->
  <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap-theme.min.css" integrity="sha384-rHyoN1iRsVXV4nD0JutlnGaslCJuC7uwjduW9SVrLvRYooPp2bWYgmgJQIXwl/Sp" crossorigin="anonymous">
  <!-- Latest compiled and minified JavaScript -->
  <script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js" integrity="sha384-Tc5IQib027qvyjSMfHjOMaLkfuWVxZxUPnCJA7l2mCWNIpG9mGCD8wGNIcPD7Txa" crossorigin="anonymous"></script>

  <title>SensortTag</title>

  <script src="https://d.line-scdn.net/liff/1.0/sdk.js"></script>
  <script src="https://unpkg.com/vue"></script>
</head>
<body>
    <div id="top" class="container">
        <h1>SensorTag</h1>

        <button class="btn btn-default" v-on:click="start()">{{button_title}}</button><br>
        <br>
        <input type="checkbox" v-model="temperature_enable" v-on:click="set_enable('temperature')">
        IR Temperature Sensor<br>
        <label>ambient_temperature</label> {{temperature.ambient_temperature}}<br>
        <label>object_temperature</label> {{temperature.object_temperature}}<br>
        <br>
        <input type="checkbox" v-model="humidity_enable" v-on:click="set_enable('humidity')">
        Humidity Sensor<br>
        <label>temperature</label> {{humidity.temperature}}<br>
        <label>humidity</label> {{humidity.humidity}}<br>
        <br>
        <input type="checkbox" v-model="pressure_enable" v-on:click="set_enable('pressure')">
        Barometric Pressure Sensor<br>
        <label>pressure</label> {{pressure.pressure}}<br>
        <label>temperature</label> {{pressure.temperature}}<br>
        <br>
        <input type="checkbox" v-model="optical_enable" v-on:click="set_enable('optical')">
        Optical Sensor<br>
        <label>luminance</label> {{optical.luminance}}<br>
        <br>
        Keys<br>
        <label>left_key</label> {{left_key}}<br>
        <label>right_key</label> {{right_key}}<br>
        <br>
        <input type="checkbox" v-model="movement_enable" v-on:click="set_enable('movement')">
        Movement Sensor<br>
        <label>xG</label> {{movement.xG}}<br>
        <label>yG</label> {{movement.yG}}<br>
        <label>zG</label> {{movement.zG}}<br>
        <label>xA</label> {{movement.xA}}<br>
        <label>yA</label> {{movement.yA}}<br>
        <label>zA</label> {{movement.zA}}<br>
        <label>xM</label> {{movement.xM}}<br>
        <label>yM</label> {{movement.yM}}<br>
        <label>zM</label> {{movement.zM}}<br>

        <div class="modal fade" id="progress">
            <div class="modal-dialog">
                <div class="modal-content">
                    <div class="modal-header">
                        <h4 class="modal-title">{{progress_title}}</h4>
                    </div>
                    <div class="modal-body">
                        <center><progress max="100" /></center>
                    </div>
                </div>
            </div>
        </div>
    </div>

    <script src="js/sensortag.js"></script>
    <script src="js/start.js"></script>
</body>
```

#ChromeブラウザからSensorTagに接続する

以上のコンテンツをWebサーバに配置して、Chromeから開いてみましょう。
必ず、HTTPSでアクセスしてください。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/261826/38d94e2d-c6bd-b2a6-6b8a-eaf5bd382ca0.png)

SensorTagの電源ボタンを1回押して、緑色のLEDが点滅するのを確認して、「接続」ボタンを押下します。
そうすると、以下のようなデバイスの選択画面が出てきます。
以下の画面ではすでに1度接続しているときのものですが、初めて接続する場合には、不明なデバイスとして表示されるかもしれません。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/261826/4a6db943-6d7a-4eac-5426-d389c532fe18.png)

対象デバイスを選択してから「ペア設定」ボタンを押下します。
数秒してからボタンが「切断」に変われば接続成功です。
各センサ名のチェックボックスをOnにすることで、1秒ごとに表示が変わるのがわかります。
Keysも、SensorTagの両脇のボタンを押すと表示が切り替わります。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/261826/9f9d5a09-61f2-6e17-9aa2-ffedf3ef9186.png)

以上です。

（2回目はLINE Thingsに対応させます。）
[TI SensorTagをブラウザから使う(2)：with LINE Things](https://qiita.com/poruruba/items/e95d9dd2b93ff875170f)