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/
'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を使っています。
'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ファイルです。
<!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でアクセスしてください。
SensorTagの電源ボタンを1回押して、緑色のLEDが点滅するのを確認して、「接続」ボタンを押下します。
そうすると、以下のようなデバイスの選択画面が出てきます。
以下の画面ではすでに1度接続しているときのものですが、初めて接続する場合には、不明なデバイスとして表示されるかもしれません。
対象デバイスを選択してから「ペア設定」ボタンを押下します。
数秒してからボタンが「切断」に変われば接続成功です。
各センサ名のチェックボックスをOnにすることで、1秒ごとに表示が変わるのがわかります。
Keysも、SensorTagの両脇のボタンを押すと表示が切り替わります。
以上です。
(2回目はLINE Thingsに対応させます。)
TI SensorTagをブラウザから使う(2):with LINE Things