bluetooth 温度・湿度計を読んでインターフェイスする。

##Govee H5075
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/274715/25a72769-20c3-c28c-8a7e-300a66454555.png)
###![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/274715/58f40493-2797-1f5f-217f-3ea4451108e2.png)
価格　￥1800
〖高精度センサー〗　最新版スイス製SHT30センサーを内蔵し、温度は最大±0.3°C、湿度は最大±3％RHの誤差で、高精度で信頼性の高い温湿度測定を実現できます。また、2秒ごとにデータを更新し、精度と感度両方とも優れています 。安心にご使用できるデジタル温湿度計です。

## bluetooth温度・湿度計を読み込んでAppSheetアプリで使えるようにします。
### EV/HVに乗っていると外気温度が気になる。１リッターあたり5Kmの車に乗っていた人間がいきなり1リッターあたり25km走るハイブリッド乗り始めるとクーラーも消して走る状態になる。大体プログラマは、冷凍室みたいな環境にいるので車に乗ると一番低温設定にしてしまう。自動的に燃費も１５km/lぐらいになってしまう。とても悔しい。とても損した気になる

###大まかなデータの流れ
+ Goveeのアドバタイズフレームの受信
+ データの変換
+ 携帯電話のデザリングを仲介してGoogle sheetsへ書き込む
+ 携帯電話等からApp Sheet経由でデータを表示する。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/274715/aae449c5-aad1-f57f-0770-3a961dae0e55.png)

##どのようにデータを受け取るか
Buletoothのアドバータイズ機能は、Advertising Eventごとに自分の機器の情報が入ったアドバータイズフレームを送信します。

##BlueTooth用ライブラリの選択
windowsとraspberry pi ともに動作するライブラリを選びます、
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/274715/0453d3a1-5928-399b-29a1-8f032eadd597.png)

```sh
pip install bleak
```

## 基本ルーチン

* デバイスを探す

```py
import asyncio
from bleak import BleakScanner
from bleak.backends.scanner import AdvertisementData
async def run():
    async with BleakScanner() as scanner:
        await asyncio.sleep(5.0)
        devices = await scanner.get_discovered_devices()
    for d in devices:
        print(d)
loop = asyncio.get_event_loop()
loop.run_until_complete(run())
```

*実際のデータを受け取る

```py
import asyncio
from bleak import BleakScanner
#データを受信
def detection_callback(device, a):
    if "A4:C1:38" in device.address:
        v=a.manufacturer_data.get(60552)      
        if v != None:
            d=v[0:4]
            vv=int.from_bytes(d, byteorder='big', signed=False)
            t=vv/10000
            h=(vv%1000)/10
            print(device.address, "RSSI:", device.rssi, a.local_name,v,t,h,v[4])
       
async def run():
    scanner = BleakScanner()
    scanner.register_detection_callback(detection_callback)
    await scanner.start()
    await asyncio.sleep(5.0)
    await scanner.stop()
    devices = await scanner.get_discovered_devices()
```
解説
A4:C1:38で始まるMACアドレスは、GoVee社の製品です。
manufacturer_dataの60552には、goveeのデータが書かれています。

```py
if "A4:C1:38" in device.address:
```

* raspiのbluetoothctlツールでハッキングすると以下のようになります。
 0xec88は、60552にあたります。

```sh
root@raspberrypi:/home/pi/ble# bluetoothctl
[NEW] Controller 33:03:30:0B:50:C3 raspberrypi [default]
[NEW] Device A4:C1:38:27:1C:9F GVH5075_1C9F
[bluetooth]# info A4:C1:38:27:1C:9F
Device A4:C1:38:27:1C:9F
	Name: GVH5075_1C9F
	Alias: GVH5075_1C9F
	Paired: no
	Trusted: no
	Blocked: no
	Connected: no
	LegacyPairing: no
	Modalias: usb:v248Ap8266d0001
	ManufacturerData Key: 0xec88
	ManufacturerData Value: 0x00
	ManufacturerData Value: 0x03
	ManufacturerData Value: 0xbb
	ManufacturerData Value: 0x15
	ManufacturerData Value: 0x64
	ManufacturerData Value: 0x00

[bluetooth]#
```
データの0x00,0x03,0xbb,0x15を整数にします。244501これを１００００で割ると24℃ともめられます。1000で割ったあまりを10で割った値が湿度です。

```py
   t=vv/10000
   h=(vv%1000)/10
```
4 byte目の0x64は、バッテリーの残量パーセンテージです。

##Google sheets側のscripts

```js
//pythonからデータを受け取る
function doGet(e) {

  if (e.parameter.d != "disp"){　//dに"disp"と入れられた場合、最終データを表示する
    var d = e.parameter.d; //時刻データの受け取り
    var t = e.parameter.t; //温度データの受け取り
    var h = e.parameter.h; //湿度データの受け取り
    var b = e.parameter.b; //バッテリデータの受け取り
    var m = e.parameter.m; //Nameの受け取り
    //現状Activeになっているsheetを取得
    var sheet = SpreadsheetApp.getActiveSheet();
    
    //送信されたデータ値　を追記
    sheet.appendRow([m,d,t,h,b]);
    // response for debug    
    var ok="OK "+m+" "+d+" "+t+"℃ "+h+"% "+b+"% Bat"
    var output = ContentService.createTextOutput(ok);
    output.setMimeType(ContentService.MimeType.TEXT);
    return output;
  

  } else {

    console.log("doGet disp");
    var sheet = SpreadsheetApp.getActiveSheet();
    var l=sheet.getLastRow();
    var d = sheet.getRange(l,2,1).getValue();
    var t = sheet.getRange(l,3,1).getValue();
    var h = sheet.getRange(l,4,1).getValue();    
    var b = sheet.getRange(l,5,1).getValue();
    console.log(d,t,h,b);
    // var s="<h1>日付:"+d+"</h1><h1> 温度:"+t+"</h1><h1> 湿度:"+h +"</h1><h1> 電池:"+b+"</h1>";
    // webの生成
    var tx=HtmlService.createTemplateFromFile("index");
    //index pageに変数をセット
    tx.d=Utilities.formatDate(d,"JST", "yyyy/MM/dd HH:mm");
    tx.t=t.toFixed(1);
    tx.h=h.toFixed(1);
    tx.b=b;
    //タイトルの設定
    return tx.evaluate().setTitle("GoVee H5075");
　}
}
```

##表示用HTMLテンプレート
+ https://script.google.com/macros/s/AKfycbykJNNMPwirzkl_〇〇〇〇〇〇〇XC1dyZfXZmMw7Q3GHB3S2H1ZtM/exec?d=disp のようにdispを渡してあげると表示します。

```html
<!DOCTYPE html>
<html>
  <head>
    <base target="_top">
	<meta http-equiv="content-type" charset="utf-8">
	<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
    <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.5.0/css/bootstrap.min.css" integrity="sha384-9aIt2nRpC12Uk9gS9baDl411NQApFmC26EwAOH8WgZl5MYYxFfc+NcPb1dKGj7Sk" crossorigin="anonymous">
    <script src="https://code.jquery.com/jquery-3.5.1.slim.min.js" integrity="sha384-DfXdz2htPH0lsSSs5nCTpuj/zy4C+OGpamoFVy38MVBnE+IbbVYUew+OrCXaRkfj" crossorigin="anonymous"></script>
    <script src="https://cdn.jsdelivr.net/npm/popper.js@1.16.0/dist/umd/popper.min.js" integrity="sha384-Q6E9RHvbIyZFJoft+2mJbHaEWldlvI9IOYy5n3zV9zzTtmI3UksdQRVvoxMfooAo" crossorigin="anonymous"></script>
    <script src="https://stackpath.bootstrapcdn.com/bootstrap/4.5.0/js/bootstrap.min.js" integrity="sha384-OgVRvuATP1z7JjHLkuOU7Xw704+h835Lr+6QL9UvYjZE3Ipu6Tp75j7Bh/kR0JKI" crossorigin="anonymous"></script>    
  </head>
  <body>
    <content>
    <div class="row m-3">
            <div class="col m-1 badge badge-success"><h4>時刻</h4><h5><?= d ?></h5></div>
            <div class="col m-1 badge badge-success"><h4>温度</h4><h5><?= t ?>℃</h5></div>
            <div class="col m-1 badge badge-success"><h4>湿度</h4><h5><?= h ?>％</h5></div>
            <div class="col m-1 badge badge-success"><h4>電池</h4><h5><?= b ?>％</h5></div>
    </div>
    </content>
  </body>
</html>
```
+ 実際の表示
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/274715/87a8e5d5-9b2a-f75d-3795-dfaf7add1c1b.png)

+ AppSheetによる表示
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/274715/020ac04f-45a8-5540-aecc-b6921b0e26a6.png)

+ AppSheetによるグラフ化
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/274715/5da9d939-2aa7-e722-4659-b3179f6f9570.png)

+ App Sheetによる現在値の表示
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/274715/19c23c4d-ca38-34aa-6467-d64e3b01dede.png)

+ ノーコード開発

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/274715/56e20f04-b0fc-ef12-1ce4-fc31ed7437d9.png)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/274715/8ed1678c-a598-f4ff-040f-0fc4ff1c3a86.png)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/274715/5245edfe-5ce8-e5af-0359-0694711301b6.png)