LoginSignup
10
13

More than 3 years have passed since last update.

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

Posted at

Govee H5075

image.png

image.png

価格 ¥1800
〖高精度センサー〗 最新版スイス製SHT30センサーを内蔵し、温度は最大±0.3°C、湿度は最大±3%RHの誤差で、高精度で信頼性の高い温湿度測定を実現できます。また、2秒ごとにデータを更新し、精度と感度両方とも優れています 。安心にご使用できるデジタル温湿度計です。

bluetooth温度・湿度計を読み込んでAppSheetアプリで使えるようにします。

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

大まかなデータの流れ

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

image.png

どのようにデータを受け取るか

Buletoothのアドバータイズ機能は、Advertising Eventごとに自分の機器の情報が入ったアドバータイズフレームを送信します。

BlueTooth用ライブラリの選択

windowsとraspberry pi ともに動作するライブラリを選びます、
image.png

pip install bleak

基本ルーチン

  • デバイスを探す
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())

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

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のデータが書かれています。

if "A4:C1:38" in device.address:
  • raspiのbluetoothctlツールでハッキングすると以下のようになります。 0xec88は、60552にあたります。
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これを10000で割ると24℃ともめられます。1000で割ったあまりを10で割った値が湿度です。

   t=vv/10000
   h=(vv%1000)/10

4 byte目の0x64は、バッテリーの残量パーセンテージです。

Google sheets側のscripts

//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テンプレート

<!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

  • AppSheetによる表示
    image.png

  • AppSheetによるグラフ化
    image.png

  • App Sheetによる現在値の表示
    image.png

  • ノーコード開発

image.png

image.png

image.png

10
13
0

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
10
13