micro:bitのジャイロセンサーをBLEで読み取り、C#へ渡す

# 処理フロー
[MakeCode]
micro:bitに内蔵されているジャイロセンサーの値を、BLE(Bluetooth Low Energy)を使用してPCへ送信します．

[JavaScript]
接続処理をし、受け取ったセンサー値をクリップボードに書き込みます．

[C#]
クリップボードからセンサー値を読み取り、画面上のキャラクターを操作します．

言語間の値の受け渡し方法に悩んだのですが、"クリップボードを経由する"という無理やりな方法で実行しています． 

# MakeCode
センサーの値をBluetoothで送信するだけですので、非常にシンプルなコードになっています．

<img width="700" alt="screenshot" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3087800/b95db8cf-5c70-d1c6-2416-3677d7d4e0f8.png">

MakeCodeのJavaScript版のコードは以下です．
```JavaScript
bluetooth.onBluetoothConnected(function () {
    basic.showIcon(IconNames.Yes)
})
bluetooth.onBluetoothDisconnected(function () {
    basic.showIcon(IconNames.No)
})
input.onButtonPressed(Button.A, function () {
    basic.showString("　　")
    basic.showString(control.deviceName())
    basic.showIcon(IconNames.Square)
})
basic.showIcon(IconNames.Square)
bluetooth.startTemperatureService()
bluetooth.startLEDService()
bluetooth.startUartService()
basic.forever(function () {
    bluetooth.uartWriteNumber(input.rotation(Rotation.Roll))
})

```

# JavaScript
BLE接続・センサー値読み取り・クリップボードへの書き込み処理．
ネットにはmicro:bitとPCが1対1のサンプルしかなかったため、4対1で対応させるのに苦労しました．

```javascript
<script>
const microbit1 = new BlueJelly();
const microbit2 = new BlueJelly();
const microbit3 = new BlueJelly();
const microbit4 = new BlueJelly();

window.onload = function () {
  microbit1.setUUID("micro:bit1", BlueJelly.MICROBIT_UART_SERVICE, BlueJelly.MICROBIT_TX_CHARACTERISTIC);
  microbit2.setUUID("micro:bit2", BlueJelly.MICROBIT_UART_SERVICE, BlueJelly.MICROBIT_TX_CHARACTERISTIC);
  microbit3.setUUID("micro:bit3", BlueJelly.MICROBIT_UART_SERVICE, BlueJelly.MICROBIT_TX_CHARACTERISTIC);
  microbit4.setUUID("micro:bit4", BlueJelly.MICROBIT_UART_SERVICE, BlueJelly.MICROBIT_TX_CHARACTERISTIC);
}

microbit1.onScan = function (deviceName) {
  document.getElementById('status').innerHTML = deviceName;
}
microbit1.onRead = function (data){
  let value = "";
  for(let i = 0; i < data.byteLength; i++){
    value = value + String.fromCharCode(data.getInt8(i));
  }
  value = Number(value);
  console.log(value);
  document.getElementById('data_text').innerHTML = value;
}

microbit2.onScan = function (deviceName) {
  document.getElementById('status2').innerHTML = deviceName;
}
microbit2.onRead = function (data){
  let value = "";
  for(let i = 0; i < data.byteLength; i++){
    value = value + String.fromCharCode(data.getInt8(i));
  }
  value = Number(value);
  console.log(value);
  document.getElementById('data_text2').innerHTML = value;
}

microbit3.onScan = function (deviceName) {
  document.getElementById('status3').innerHTML = deviceName;
}
microbit3.onRead = function (data, uuid){
  let value = "";
  for(let i = 0; i < data.byteLength; i++){
    value = value + String.fromCharCode(data.getInt8(i));
  }
  value = Number(value);
  console.log(value);
  document.getElementById('data_text3').innerHTML = value;
}

microbit4.onScan = function (deviceName) {
  document.getElementById('status4').innerHTML = deviceName;
}
microbit4.onRead = function (data, uuid){
  let value = "";
  for(let i = 0; i < data.byteLength; i++){
    value = value + String.fromCharCode(data.getInt8(i));
  }
  value = Number(value);
  console.log(value);
  document.getElementById('data_text4').innerHTML = value;
}

document.getElementById('startNotifications').addEventListener('click', function() {
      microbit1.startNotify('micro:bit1');
});

document.getElementById('startNotifications2').addEventListener('click', function() {
      microbit2.startNotify('micro:bit2');
});

document.getElementById('startNotifications3').addEventListener('click', function() {
      microbit3.startNotify('micro:bit3');
});

document.getElementById('startNotifications4').addEventListener('click', function() {
      microbit4.startNotify('micro:bit4');
});

var observer = new MutationObserver(function(){
  var MB1 = document.getElementById('data_text');
  var MB2 = document.getElementById('data_text2');
  var MB3 = document.getElementById('data_text3');
  var MB4 = document.getElementById('data_text4');
  var content = MB1.innerHTML + "\n" + MB2.innerHTML + "\n" + MB3.innerHTML + "\n" + MB4.innerHTML
  navigator.clipboard.writeText(content)
  });
const elem = document.getElementById('data_text4');
const config = { 
  attributes: true, 
  childList: true, 
  characterData: true 
};
observer.observe(elem, config);
</script>
```

ライブラリ"BlueJelly.js"を少し改変して、使用しています．

```bluejelly.js
var BlueJelly = function(){
  this.bluetoothDevice = null;
  this.dataCharacteristic = null;
  this.hashUUID ={};
  this.hashUUID_lastConnected;

  this.onScan = function(deviceName){console.log("onScan");};
  this.onConnectGATT = function(uuid){console.log("onConnectGATT");};
  this.onRead = function(data, uuid){console.log("onRead");};
  this.onWrite = function(uuid){console.log("onWrite");};
  this.onStartNotify = function(uuid){console.log("onStartNotify");};
  this.onStopNotify = function(uuid){console.log("onStopNotify");};
  this.onDisconnect = function(){console.log("onDisconnect");};
  this.onClear = function(){console.log("onClear");};
  this.onReset = function(){console.log("onReset");};
  this.onError = function(error){console.log("onError");};
}

BlueJelly.prototype.setUUID = function(name, serviceUUID, characteristicUUID){
  console.log('Execute : setUUID');
  console.log(this.hashUUID);

  this.hashUUID[name] = {'serviceUUID':serviceUUID, 'characteristicUUID':characteristicUUID};
}

BlueJelly.prototype.scan = function(uuid){
  return (this.bluetoothDevice ? Promise.resolve() : this.requestDevice(uuid))
  .catch(error => {
    console.log('Error : ' + error);
    this.onError(error);
  });
}

BlueJelly.prototype.requestDevice = function(uuid) {
  console.log('Execute : requestDevice');
  return navigator.bluetooth.requestDevice({
	  filters: [{
        namePrefix: 'BBC micro:bit',
      }],
      optionalServices: [this.hashUUID[uuid].serviceUUID]})
  .then(device => {
    this.bluetoothDevice = device;
    this.bluetoothDevice.addEventListener('gattserverdisconnected', this.onDisconnect);
    this.onScan(this.bluetoothDevice.name);
  });
}

BlueJelly.prototype.connectGATT = function(uuid) {
  if(!this.bluetoothDevice)
  {
    var error = "No Bluetooth Device";
    console.log('Error : ' + error);
    this.onError(error);
    return;
  }
  if (this.bluetoothDevice.gatt.connected && this.dataCharacteristic) {
    if(this.hashUUID_lastConnected == uuid)
      return Promise.resolve();
  }
  this.hashUUID_lastConnected = uuid;

  console.log('Execute : connect');
  return this.bluetoothDevice.gatt.connect()
  .then(server => {
    console.log('Execute : getPrimaryService');
    return server.getPrimaryService(this.hashUUID[uuid].serviceUUID);
  })
  .then(service => {
    console.log('Execute : getCharacteristic');
    return service.getCharacteristic(this.hashUUID[uuid].characteristicUUID);
  })
  .then(characteristic => {
    this.dataCharacteristic = characteristic;
    this.dataCharacteristic.addEventListener('characteristicvaluechanged',this.dataChanged(this, uuid));
    this.onConnectGATT(uuid);
  })
  .catch(error => {
      console.log('Error : ' + error);
      this.onError(error);
    });
}

BlueJelly.prototype.dataChanged = function(self, uuid) {
  return function(event) {
    self.onRead(event.target.value, uuid);
  }
}

BlueJelly.prototype.read= function(uuid) {
  return (this.scan(uuid))
  .then( () => {
    return this.connectGATT(uuid);
  })
  .then( () => {
    console.log('Execute : readValue');
    return this.dataCharacteristic.readValue();
  })
  .catch(error => {
    console.log('Error : ' + error);
    this.onError(error);
  });
}

BlueJelly.prototype.write = function(uuid, array_value) {
  return (this.scan(uuid))
  .then( () => {
    return this.connectGATT(uuid);
  })
  .then( () => {
    console.log('Execute : writeValue');
    data = Uint8Array.from(array_value);
    return this.dataCharacteristic.writeValue(data);
  })
  .then( () => {
    this.onWrite(uuid);
  })
  .catch(error => {
    console.log('Error : ' + error);
    this.onError(error);
  });
}

BlueJelly.prototype.startNotify = function(uuid) {
  return (this.scan(uuid))
  .then( () => {
    return this.connectGATT(uuid);
  })
  .then( () => {
    console.log('Execute : startNotifications');
    this.dataCharacteristic.startNotifications()
  })
  .then( () => {
    this.onStartNotify(uuid);
  })
  .catch(error => {
    console.log('Error : ' + error);
    this.onError(error);
  });
}

BlueJelly.prototype.disconnect= function() {
  if (!this.bluetoothDevice) {
    var error = "No Bluetooth Device";
    console.log('Error : ' + error);
    this.onError(error);
    return;
  }

  if (this.bluetoothDevice.gatt.connected) {
    console.log('Execute : disconnect');
    this.bluetoothDevice.gatt.disconnect();
  } else {
   var error = "Bluetooth Device is already disconnected";
   console.log('Error : ' + error);
   this.onError(error);
   return;
  }
}

BlueJelly.prototype.clear= function() {
   console.log('Excute : Clear Device and Characteristic');
   this.bluetoothDevice = null;
   this.dataCharacteristic = null;
   this.onClear();
}

BlueJelly.prototype.reset= function() {
  console.log('Excute : reset');
  this.disconnect();
  this.clear();
  this.onReset();
}


Object.defineProperty(BlueJelly, 'MICROBIT_BASE_UUID', {value: "e95d0000-251d-470a-a062-fa1922dfa9a8", writable: false});
Object.defineProperty(BlueJelly, 'MICROBIT_SERVICESGENERIC_ACCESS', {value: "00001800-0000-1000-8000-00805f9b34fb", writable: false});
Object.defineProperty(BlueJelly, 'MICROBIT_DEVICE_NAME', {value: "00002a00-0000-1000-8000-00805f9b34fb", writable: false});
Object.defineProperty(BlueJelly, 'MICROBIT_APPEARANCE', {value: "00002a01-0000-1000-8000-00805f9b34fb", writable: false});
Object.defineProperty(BlueJelly, 'MICROBIT_PERIPHERAL_PREFERRED_CONNECTION_PARAMETERS', {value: "00002a04-0000-1000-8000-00805f9b34fb", writable: false});
Object.defineProperty(BlueJelly, 'MICROBIT_GENERIC_ATTRIBUTE', {value: "00001801-0000-1000-8000-00805f9b34fb", writable: false});
Object.defineProperty(BlueJelly, 'MICROBIT_SERVICE_CHANGED', {value: "2a05", writable: false});
Object.defineProperty(BlueJelly, 'MICROBIT_DEVICE_INFORMATION', {value: "0000180a-0000-1000-8000-00805f9b34fb", writable: false});
Object.defineProperty(BlueJelly, 'MICROBIT_MODEL_NUMBER_STRING', {value: "00002a24-0000-1000-8000-00805f9b34fb", writable: false});
Object.defineProperty(BlueJelly, 'MICROBIT_SERIAL_NUMBER_STRING', {value: "00002a25-0000-1000-8000-00805f9b34fb", writable: false});
Object.defineProperty(BlueJelly, 'MICROBIT_HARDWARE_REVISION_STRING', {value: "00002a27-0000-1000-8000-00805f9b34fb", writable: false});
Object.defineProperty(BlueJelly, 'MICROBIT_FIRMWARE_REVISION_STRING', {value: "00002a26-0000-1000-8000-00805f9b34fb", writable: false});
Object.defineProperty(BlueJelly, 'MICROBIT_MANUFACTURER_NAME_STRING', {value: "00002a29-0000-1000-8000-00805f9b34fb", writable: false});
Object.defineProperty(BlueJelly, 'MICROBIT_ACCELEROMETER_SERVICE', {value: "e95d0753-251d-470a-a062-fa1922dfa9a8", writable: false});
Object.defineProperty(BlueJelly, 'MICROBIT_ACCELEROMETER_DATA', {value: "e95dca4b-251d-470a-a062-fa1922dfa9a8", writable: false});
Object.defineProperty(BlueJelly, 'MICROBIT_ACCELEROMETER_PERIOD', {value: "e95dfb24-251d-470a-a062-fa1922dfa9a8", writable: false});
Object.defineProperty(BlueJelly, 'MICROBIT_MAGNETOMETER_SERVICE', {value: "e95df2d8-251d-470a-a062-fa1922dfa9a8", writable: false});
Object.defineProperty(BlueJelly, 'MICROBIT_MAGNETOMETER_DATA', {value: "e95dfb11-251d-470a-a062-fa1922dfa9a8", writable: false});
Object.defineProperty(BlueJelly, 'MICROBIT_MAGNETOMETER_PERIOD', {value: "e95d386c-251d-470a-a062-fa1922dfa9a8", writable: false});
Object.defineProperty(BlueJelly, 'MICROBIT_MAGNETOMETER_BEARING', {value: "e95d9715-251d-470a-a062-fa1922dfa9a8", writable: false});
Object.defineProperty(BlueJelly, 'MICROBIT_MAGNETOMETER_CALIBRATION', {value: "e95db358-251d-470a-a062-fa1922dfa9a8", writable: false});
Object.defineProperty(BlueJelly, 'MICROBIT_BUTTON_SERVICE', {value: "e95d9882-251d-470a-a062-fa1922dfa9a8", writable: false});
Object.defineProperty(BlueJelly, 'MICROBIT_BUTTON_A_STATE', {value: "e95dda90-251d-470a-a062-fa1922dfa9a8", writable: false});
Object.defineProperty(BlueJelly, 'MICROBIT_BUTTON_B_STATE', {value: "e95dda91-251d-470a-a062-fa1922dfa9a8", writable: false});
Object.defineProperty(BlueJelly, 'MICROBIT_IO_PIN_SERVICE', {value: "e95d127b-251d-470a-a062-fa1922dfa9a8", writable: false});
Object.defineProperty(BlueJelly, 'MICROBIT_PIN_DATA', {value: "e95d8d00-251d-470a-a062-fa1922dfa9a8", writable: false});
Object.defineProperty(BlueJelly, 'MICROBIT_PIN_AD_CONFIGURATION', {value: "e95d5899-251d-470a-a062-fa1922dfa9a8", writable: false});
Object.defineProperty(BlueJelly, 'MICROBIT_PIN_IO_CONFIGURATION', {value: "e95db9fe-251d-470a-a062-fa1922dfa9a8", writable: false});
Object.defineProperty(BlueJelly, 'MICROBIT_PWM_CONTROL', {value: "e95dd822-251d-470a-a062-fa1922dfa9a8", writable: false});
Object.defineProperty(BlueJelly, 'MICROBIT_LED_SERVICE', {value: "e95dd91d-251d-470a-a062-fa1922dfa9a8", writable: false});
Object.defineProperty(BlueJelly, 'MICROBIT_LED_MATRIX_STATE', {value: "e95d7b77-251d-470a-a062-fa1922dfa9a8", writable: false});
Object.defineProperty(BlueJelly, 'MICROBIT_LED_TEXT', {value: "e95d93ee-251d-470a-a062-fa1922dfa9a8", writable: false});
Object.defineProperty(BlueJelly, 'MICROBIT_SCROLLING_DELAY', {value: "e95d0d2d-251d-470a-a062-fa1922dfa9a8", writable: false});
Object.defineProperty(BlueJelly, 'MICROBIT_EVENT_SERVICE', {value: "e95d93af-251d-470a-a062-fa1922dfa9a8", writable: false});
Object.defineProperty(BlueJelly, 'MICROBIT_MICROBIT_REQUIREMENTS', {value: "e95db84c-251d-470a-a062-fa1922dfa9a8", writable: false});
Object.defineProperty(BlueJelly, 'MICROBIT_MICROBIT_EVENT', {value: "e95d9775-251d-470a-a062-fa1922dfa9a8", writable: false});
Object.defineProperty(BlueJelly, 'MICROBIT_CLIENT_REQUIREMENTS', {value: "e95d23c4-251d-470a-a062-fa1922dfa9a8", writable: false});
Object.defineProperty(BlueJelly, 'MICROBIT_CLIENT_EVENT', {value: "e95d5404-251d-470a-a062-fa1922dfa9a8", writable: false});
Object.defineProperty(BlueJelly, 'MICROBIT_DFU_CONTROL_SERVICE', {value: "e95d93b0-251d-470a-a062-fa1922dfa9a8", writable: false});
Object.defineProperty(BlueJelly, 'MICROBIT_DFU_CONTROL', {value: "e95d93b1-251d-470a-a062-fa1922dfa9a8", writable: false});
Object.defineProperty(BlueJelly, 'MICROBIT_TEMPERATURE_SERVICE', {value: "e95d6100-251d-470a-a062-fa1922dfa9a8", writable: false});
Object.defineProperty(BlueJelly, 'MICROBIT_TEMPERATURE', {value: "e95d9250-251d-470a-a062-fa1922dfa9a8", writable: false});
Object.defineProperty(BlueJelly, 'MICROBIT_TEMPERATURE_PERIOD', {value: "e95d1b25-251d-470a-a062-fa1922dfa9a8", writable: false});
Object.defineProperty(BlueJelly, 'MICROBIT_UART_SERVICE', {value: "6e400001-b5a3-f393-e0a9-e50e24dcca9e", writable: false});
Object.defineProperty(BlueJelly, 'MICROBIT_TX_CHARACTERISTIC', {value: "6e400002-b5a3-f393-e0a9-e50e24dcca9e", writable: false});
Object.defineProperty(BlueJelly, 'MICROBIT_RX_CHARACTERISTIC', {value: "6e400003-b5a3-f393-e0a9-e50e24dcca9e", writable: false});
```

# HTML/CSS

[JavaScript(画像は次のC#のところにあります．)]
④左足のボタンがトリガーとなっており、④の接続が完了すると、4つのセンサーの値をクリップボードへコピーし続けます．

```index.html
<!doctype html>

<html>
  <head>
    <meta charset="utf-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="description" content="BlueJelly">
    <meta name="viewport" content="width=640, maximum-scale=1.0, user-scalable=yes">
    <title>micro:bit接続</title>
    <link href="https://fonts.googleapis.com/css?family=Lato:100,300,400,700,900" rel="stylesheet" type="text/css">
    <link rel="stylesheet" href="style.css">
    <script type="text/javascript" src="bluejelly.js"></script>
  </head>

<body>
    <div id="a">
    <div class="contents margin"><b>①右手</b><div id="status"> </div><br>
        <button id="startNotifications" class="button">接続開始</button>
        <div id="data_text"> </div>
    </div>
    </div>

    <div id="b">
      <div class="contents margin"><b>②右足</b><div id="status3"> </div><br>
          <button id="startNotifications3" class="button">接続開始</button>
          <div id="data_text3"> </div>
      </div>
      </div>

    <div id="c">
    <div class="contents margin"><b>③左手</b><div id="status2"> </div><br>
        <button id="startNotifications2" class="button">接続開始</button>
        <div id="data_text2"> </div>
    </div>
    </div>
	
    <div id="d">
	  <div class="contents margin"><b>④左足</b><div id="status4"> </div><br>
        <button id="startNotifications4" class="button">接続開始</button>
        <div id="data_text4"> </div>
    </div>
  </div>
</div>
</body>
</html>
```

```style.css

@charset "utf-8";
/* CSS Document */

/*************************** RESET ***************************/

html, body, div, span, applet, object, iframe, h1, h2, h3, h4, h5, h6, p, blockquote, pre, a, abbr, acronym, address, big, cite, code, del, dfn, em, font, img, ins, kbd, q, s, samp, small, strike, strong, sub, sup, tt, var, b, u, i, center, dl, dt, dd, ol, ul, li, fieldset, form, label, legend, table, caption, tbody, tfoot, thead, tr, th, td {
	margin: 0;
	padding: 0;
	border: 0;
	outline: 0;
	font-size: 100%;
	vertical-align: baseline;
	background: transparent;
}
/*************************** RESET ***************************/


html, body {
	height: 100%;
	width: 100%;
	font-family: 'Lato', Verdana, "Hiragino Kaku Gothic ProN", Meiryo, sans-serif;
	min-width: 640px;
	position: relative;
  background-color: #c6dfc6;
}

hr{
  border-width: 1px;
  border-color: #fff;
  border-style: solid;
  margin: 20px 0px 20px 0px;
}

.title{
  width: 100%;
  background-color: #fff;
  padding: 20px 0px 20px 0px;
}

.contents{
  padding: 20px 0px 50px 0px;
}

.footer{
  width: 100%;
  font-size:12px;
  position: absolute;
  bottom: 0px;
  height: 60px;
  color: #9a9a9a;
}

.margin{
  border-left:  70px;
  border-right: 70px;
  border-style: solid;
  box-sizing: border-box;
  border-color: rgba(0, 0, 0, 0);
}

.button{
  border-width: 1px;
  border-color: #e6e6e6;
  border-style: solid;
  background-color: #fff;
  padding: 5px 20px 5px 20px;
  color: #4d4d4d;
  font-size: 14px;
  font-weight: bold;
  margin: 0px;
}

.button:hover{
  border-color: #ccc;
}
.button:active{
  background-color: #f0f0f0;
}

#chart{
	margin-bottom: 20px;
}

#data_text, #data_text2, #data_text3, #data_text4{
  color:#0000ff;
  font-size:50px;
  font-weight: 300;
  line-height: normal;
}

#status, #status2, #status3, #status4{
	color:#0000ff;
	font-size:14px;
  font-weight: 300;
}

a { text-decoration: none; }
a:link { color: #9a9a9a; }
a:visited { color: #9a9a9a; }

#a, #c {
  float: left;
  margin:auto;
  text-align:center;
  width: 50%;
  }

#b, #d {
  float: left;
  margin:auto;
  text-align:center;
  width: 50%;
  }
```

# C#
Windowsフォームアプリケーションで開発します．
WebViewで接続画面を埋め込んでいます．

<img width="700" alt="screenshot" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3087800/7e20b4e0-04d4-01ce-96c8-676c48d47b07.png">

```Form1.cs
private void timer1_Tick(object sender, EventArgs e)
{
    webView21.Focus();
    Data = Clipboard.GetText();

    string[] Get_Data;
    Get_Data = Data.Split('\n');

    string right_hand = Get_Data[0];
    string left_hand = Get_Data[1];
    string right_foot = Get_Data[2];
    string left_foot = Get_Data[3];
}
```

以上のコードで変数にセンサー値が格納されます．