Inkbird IBS-TH1 PLUSの値をESP-WROOM-32でスプレッドシートへ記録

## はじめに
[Inkbird IBS-TH1 PLUSの値をBeagleBone Blackでスプレッドシートへ記録](https://qiita.com/sina_freemail/items/a5e62e10ae5a05833387)を元にBeagleBone Black+python3 からESP-WROOM-32+micropythonに変更した時の記録です。


## 使用機材
- Inkbird IBS-TH1PLUS( https://amzn.to/3P15mLc )
- ESP32-DevKitC-32E ( https://amzn.to/3Oa1I1b )
- micropython esp32 v1.20.0 ( https://micropython.org/resources/firmware/esp32-20230426-v1.20.0.bin )


## 1.ESP32に Micropython を焼きます
本家 https://docs.micropython.org/en/latest/esp32/tutorial/intro.html#esp32-intro 

```
pip insttall esptool
```
ESP32をPCに繋いだら/dev/ttyUSB0に出てくるのを確認。書き込み権限も確認。

[BOOTボタン押しながらENボタン押してFirmware downloadモードにします](https://docs.espressif.com/projects/esptool/en/latest/esp32/advanced-topics/boot-mode-selection.html#manual-bootloader)
```
esptool.py --port /dev/ttyUSB0 erase_flash　
esptool.py --chip esp32 --port /dev/ttyUSB0 write_flash -z 0x1000 esp32-20230426-v1.20.0.bin
```

### 1.5 シリアルプロンプト
```
sudo apt install picocom
picocom /dev/ttyUSB0 -b115200
```

これでmicropythonのプロンプトが見えたらOKです。


## 2. adfruit-ampy インストール
ファイル転送のためだけにインストールします。

https://github.com/adafruit/ampy

```
pip install adafruit-ampy
```

ファイル転送のコマンドは以下の通り
```
ampy -p /dev/ttyUSB0 put main.py
```

## 3. ESP32 のBLEでInkbirdのセンサ値を取得
bluepyはmicropythonにないため、[ubluetooth](https://docs.micropython.org/en/latest/library/bluetooth.html?highlight=bluetooth#module-bluetooth)で作成します。[BLE Central Role](https://docs.micropython.org/en/latest/library/bluetooth.html?highlight=bluetooth#central-role)と[GATT Client](https://docs.micropython.org/en/latest/library/bluetooth.html?highlight=bluetooth#gatt-client)を実装。[BLE advertiser](https://docs.micropython.org/en/latest/library/bluetooth.html?highlight=bluetooth#broadcaster-role-advertiser) と [BLE scanner](https://docs.micropython.org/en/latest/library/bluetooth.html?highlight=bluetooth#observer-role-scanner) ，[GATT Server](https://docs.micropython.org/en/latest/library/bluetooth.html?highlight=bluetooth#gatt-server)はしない方針です。

処理の流れは以下の通り。
1. BLE ON 
2. BLE Connect
3. BLE READ(GATT Client)
4. BLE disconnect
5. BLE OFF 

```py:main.py
# config 
PERIPHERAL_MAC_ADDRESS=b'\xFF\xFF\xFF\xFF\xFF\xFF'

##
import ubluetooth
import utime
import ustruct

## event codes は https://docs.micropython.org/en/latest/library/bluetooth.html#event-handling から必要なものだけ
_IRQ_PERIPHERAL_CONNECT = (7)
_IRQ_PERIPHERAL_DISCONNECT = (8)
_IRQ_GATTC_READ_RESULT = (15)
_IRQ_GATTC_READ_DONE = (16)


## 1.BLE ON
ble=ubluetooth.BLE()
ble.active(True)

# setting IRQ
def bt_irq(event, data):
    if event == _IRQ_PERIPHERAL_CONNECT:
        # A successful gap_connect().
        conn_handle, addr_type, addr = data
        global conn_state
        conn_state = 0
        global handle
        handle=conn_handle
    elif event == _IRQ_PERIPHERAL_DISCONNECT:
        # Connected peripheral has disconnected.
        conn_handle, addr_type, addr = data
        global conn_state
        conn_state = -2
    elif event == _IRQ_GATTC_READ_RESULT:
        # A gattc_read() has completed.
        conn_handle, value_handle, char_data = data
        global temp
        global humid
        (temp, humid, unknown1, unknown2, unknown3) = ustruct.unpack('<hhBBB', char_data)
    elif event == _IRQ_GATTC_READ_DONE:
        # A gattc_read() has completed.
        # Note: Status will be zero on success, implementation-specific value otherwise.
        conn_handle, value_handle, status = data
        global rstatus
        rstatus=status

ble.irq(bt_irq)

##  2. BLE connect
addr_type=0 
conn_time = 30
# conn_state  0:connected , -1:connecting  , -2:disconnected
conn_state=-1
ble.gap_connect(addr_type, PERIPHERAL_MAC_ADDRESS)
while( conn_state != 0 ):
    print("BLE connecting ..",conn_time)
    if( conn_state == -2):
        conn_state =-1
        ble.gap_connect(addr_type, PERIPHERAL_MAC_ADDRESS)
        
    conn_time = conn_time -1
    if(conn_time < 0):
        break
    utime.sleep_ms(1000)

if( conn_state == 0):
    ### 3. BLE read ( GATT Client )
    value_handle=0x2d 
    rstatus=-1
    ble.gattc_read(handle,value_handle)
    ### wait read 
    while(rstatus!=0):
	    utime.sleep_ms(2000)

    ### output
    print(temp/100 , humid/100)

    ## 4.BLE disconnect 
    ble.gap_disconnect(handle)

# 5. BLE OFF
ble.active(False)

```
[Inkbird IBS-TH1PLUS のvalue_handle は0x2d です。](https://qiita.com/sina_freemail/items/a5e62e10ae5a05833387#%E3%82%BB%E3%83%B3%E3%82%B5%E5%80%A4%E5%8F%96%E5%BE%97%E3%82%B9%E3%82%AF%E3%83%AA%E3%83%97%E3%83%88%E3%81%AE%E4%BD%9C%E6%88%90)

PERIPHERAL_MAC_ADDRESS へ InkbirdのMACアドレスを入力した後，ファイル転送＆実行してInkbird の温度と同じであれば動作確認完了です。
```
$ ampy -p /dev/ttyUSB0 put main.py
$ picocom /dev/ttyUSB0 -b115200
>>> (Ctrl-Dで soft reboot)
27.24 48.11
MicroPython v1.20.0 on 2023-04-26; ESP32 module with ESP32
Type "help()" for more information.
>>> 
```

## 4.ESP32 から WLAN経由でスプレッドシートにセンサ値を書き込み

### スプレッドシートにセンサ値を書き込むGASスクリプトをWebAppとして公開
[前回の記事](https://qiita.com/sina_freemail/items/a5e62e10ae5a05833387#%E3%82%B9%E3%83%97%E3%83%AC%E3%83%83%E3%83%89%E3%82%B7%E3%83%BC%E3%83%88%E3%81%AB%E3%82%BB%E3%83%B3%E3%82%B5%E5%80%A4%E3%82%92%E6%9B%B8%E3%81%8D%E8%BE%BC%E3%82%80gas%E3%82%B9%E3%82%AF%E3%83%AA%E3%83%97%E3%83%88%E3%82%92webapp%E3%81%A8%E3%81%97%E3%81%A6%E5%85%AC%E9%96%8B)のスクリプトを~~micropythonでクエリパラメータをPOSTする方法が分からなかったので~~クエリーパラメータからJSONパラメータへ変更する。

```js:postSensorData.gs
var book = SpreadsheetApp.getActiveSpreadsheet();

//PostされたJSONデータを受け取り
function doPost(e){

  var parameter = JSON.parse(e.postData.getDataAsString());

  var data = [      
    parameter.Date_Master, // マスター日時     
    parameter.Date, // 測定日時    
    parameter.Temperature, // 気温    
    parameter.Humidity, // 湿度    
    parameter.Light, // 照度    
    parameter.UV, // UV    
    parameter.Pressure, // 圧力    
    parameter.Noise, // 騒音    
    parameter.BatteryVoltage, // 電池電圧    
    new Date(), // アップロード終了時刻    
  ];
  //取得したデータをログに記載
  Logger.log(new Date());
  //スプレッドシートへのデータ行書き込み
  addData(parameter.DeviceName, data);

  //返り値
  var output = ContentService.createTextOutput();
  output.setMimeType(ContentService.MimeType.JSON);
  output.setContent(JSON.stringify({ message: "success!" }));
  return output;
}

//スプレッドシートへのデータ行書き込み
function addData(sheetName, data){
  var sheet = book.getSheetByName(sheetName);
  sheet.appendRow(data);

}

```

### WebAppへPOSTするスクリプトの作成
WLANの接続処理を追加する。[リファレンスそのままです。](https://micropython-docs-ja.readthedocs.io/ja/latest/esp32/quickref.html#wlan)


全体の処理の流れは以下の通り。

1. BLE ON 
2. BLE Connect
3. BLE READ(GATT Client)
4. BLE disconnect
5. BLE OFF 
6. WLAN connect
7. POST data
8. WLAN disconnect


```main.py
# config
PERIPHERAL_MAC_ADDRESS=b'\xFF\xFF\xFF\xFF\xFF\xFF'
WEB_APP_URL='https://script.google.com/macros/s/******/exec'
DEVICE_NAME='****'
WLAN_SSID='******'
WLAN_PASSWD='******'

##
import ubluetooth
import utime
import ustruct

## event codes は https://docs.micropython.org/en/latest/library/bluetooth.html#event-handling から必要なものだけ
_IRQ_PERIPHERAL_CONNECT = (7)
_IRQ_PERIPHERAL_DISCONNECT = (8)
_IRQ_GATTC_READ_RESULT = (15)
_IRQ_GATTC_READ_DONE = (16)

## 1.BLE ON
ble=ubluetooth.BLE()
ble.active(True)

# setting IRQ
def bt_irq(event, data):
    if event == _IRQ_PERIPHERAL_CONNECT:
        # A successful gap_connect().
        conn_handle, addr_type, addr = data
        global conn_state
        conn_state = 0
        global handle
        handle=conn_handle
    elif event == _IRQ_PERIPHERAL_DISCONNECT:
        # Connected peripheral has disconnected.
        conn_handle, addr_type, addr = data
        global conn_state
        conn_state = -2
    elif event == _IRQ_GATTC_READ_RESULT:
        # A gattc_read() has completed.
        conn_handle, value_handle, char_data = data
        global temp
        global humid
        (temp, humid, unknown1, unknown2, unknown3) = ustruct.unpack('<hhBBB', char_data)
    elif event == _IRQ_GATTC_READ_DONE:
        # A gattc_read() has completed.
        # Note: Status will be zero on success, implementation-specific value otherwise.
        conn_handle, value_handle, status = data
        global rstatus
        rstatus=status

ble.irq(bt_irq)

##  2. BLE connect
addr_type=0 
conn_time = 30
# conn_state  0:connected , -1:connecting  , -2:disconnected
conn_state=-1
ble.gap_connect(addr_type, PERIPHERAL_MAC_ADDRESS)
while( conn_state != 0 ):
    print("BLE connecting ..",conn_time)
    if( conn_state == -2):
        conn_state =-1
        ble.gap_connect(addr_type, PERIPHERAL_MAC_ADDRESS)
        
    conn_time = conn_time -1
    if(conn_time < 0):
        break
    utime.sleep_ms(1000)

if( conn_state == 0):
    ### 3. BLE read ( GATT Client )
    value_handle=0x2d 

    rstatus=-1
    ble.gattc_read(handle,value_handle)
    ### wait read 
    while(rstatus!=0):
	    utime.sleep_ms(2000)

    ### output
    print(temp/100 , humid/100)


    ## 4.BLE disconnect 
    ble.gap_disconnect(handle)

# 5. BLE OFF
ble.active(False)

###################################
# 6.WLAN connect

def do_connect():
    import network
    wlan = network.WLAN(network.STA_IF)
    wlan.active(True)
    if not wlan.isconnected():
        print('connecting to network..')
        wlan.connect( WLAN_SSID, WLAN_PASSWD)
        while not wlan.isconnected():
            pass
    print('network config:', wlan.ifconfig())


do_connect()
 
# 7. POST data
import urequests
import ujson

# upload to the spreadsheet
data = {
    'DeviceName': DEVICE_NAME,
    'Date_Master': '',
    'Date': '',
    'SensorType': '' ,
    'Temperature': str(temp/100),
    'Humidity': str(humid/100),
    'Light': '',
    'UV': '',
    'Pressure': '',
    'Noise': '',
    'BatteryVoltage': ''
 }

response = urequests.post( WEB_APP_URL, data=ujson.dumps(data))
response.close()

# 8.WLAN disconnect
wlan.disconnect()
wlan.active(False)

```
PERIPHERAL_MAC_ADDRESS へ InkbirdのMACアドレスを、
WEB_APP_URL へ　GASのWebAPPのUrl を、
deviceName へ Googleスプレッドシートのシート名を
WLAN_SSID へ　ローカルWifiのSSIDを、
WLAN_PASSWDへ　ローカルWifiのパスワードを、それぞれ入力します。

その後、以下のコマンドを実行してスプレッドシートに値が書き込まれていたら動作確認完了です。

```
$ ampy -p /dev/ttyUSB0 put main.py
$ picocom /dev/ttyUSB0 -b115200
>>> (Ctrl-Dで soft reboot)
27.75 48.71
connecting to network..
network config: ('192.168.10.115', '255.255.255.0', '192.168.10.1', '192.168.10.1')
MicroPython v1.20.0 on 2023-04-26; ESP32 module with ESP32
Type "help()" for more information.
>>> 
 

```




## 5. ESP32 でスケジュール実行
cronは無いので ~~while loop とsleep で実装~~ 
(2023-08-20 追記)[　while loop だと urequests.py がエラーでおちるため、毎回再起動させる方針へ変更 ](https://github.com/micropython/micropython/issues/8628)

1. BLE ON 
2. BLE Connect
3. BLE READ(GATT Client)
4. BLE disconnect
5. BLE OFF 
6. WLAN connect
7. POST data
8. WLAN disconnect
9. Sleep
9. reset

```main.py
# config
PERIPHERAL_MAC_ADDRESS=b'\xFF\xFF\xFF\xFF\xFF\xFF'
WEB_APP_URL='https://script.google.com/macros/s/******/exec'
DEVICE_NAME='****'
WLAN_SSID='******'
WLAN_PASSWD='******'

##
import ubluetooth
import utime
import ustruct

## event codes は https://docs.micropython.org/en/latest/library/bluetooth.html#event-handling から必要なものだけ
_IRQ_PERIPHERAL_CONNECT = (7)
_IRQ_PERIPHERAL_DISCONNECT = (8)
_IRQ_GATTC_READ_RESULT = (15)
_IRQ_GATTC_READ_DONE = (16)



## 1.BLE ON
ble=ubluetooth.BLE()
ble.active(True)

# setting IRQ
def bt_irq(event, data):
    if event == _IRQ_PERIPHERAL_CONNECT:
        # A successful gap_connect().
        conn_handle, addr_type, addr = data
        global conn_state
        conn_state = 0
        global handle
        handle=conn_handle
    elif event == _IRQ_PERIPHERAL_DISCONNECT:
        # Connected peripheral has disconnected.
        conn_handle, addr_type, addr = data
        global conn_state
        conn_state = -2
    elif event == _IRQ_GATTC_READ_RESULT:
        # A gattc_read() has completed.
        conn_handle, value_handle, char_data = data
        global temp
        global humid
        (temp, humid, unknown1, unknown2, unknown3) = ustruct.unpack('<hhBBB', char_data)
    elif event == _IRQ_GATTC_READ_DONE:
        # A gattc_read() has completed.
        # Note: Status will be zero on success, implementation-specific value otherwise.
        conn_handle, value_handle, status = data
        global rstatus
        rstatus=status

ble.irq(bt_irq)

##  2. BLE connect
addr_type=0 
conn_time = 30
# conn_state  0:connected , -1:connecting  , -2:disconnected
conn_state=-1
ble.gap_connect(addr_type, PERIPHERAL_MAC_ADDRESS)
while( conn_state != 0 ):
    print("BLE connecting ..",conn_time)
    if( conn_state == -2):
        conn_state =-1
        ble.gap_connect(addr_type, PERIPHERAL_MAC_ADDRESS)
        
    conn_time = conn_time -1
    if(conn_time < 0):
        break
    utime.sleep_ms(1000)

if( conn_state == 0):
    ### 3. BLE read ( GATT Client )
    value_handle=0x2d 

    rstatus=-1
    ble.gattc_read(handle,value_handle)
    ### wait read 
    while(rstatus!=0):
        utime.sleep_ms(2000)

    ### output
    print(temp/100 , humid/100)


    ## 4.BLE disconnect 
    ble.gap_disconnect(handle)

# 5. BLE OFF
ble.active(False)

###################################
# 6.WLAN connect

def do_connect():
    import network
    wlan = network.WLAN(network.STA_IF)
    wlan.active(True)
    if not wlan.isconnected():
        print('connecting to network..')
        wlan.connect( WLAN_SSID, WLAN_PASSWD)
        while not wlan.isconnected():
            pass
    print('network config:', wlan.ifconfig())


do_connect()

# 7. POST data
import urequests
import ujson

# upload to the spreadsheet
data = {
    'DeviceName': DEVICE_NAME,
    'Date_Master': '',
    'Date': '',
    'SensorType': '' ,
    'Temperature': str(temp/100),
    'Humidity': str(humid/100),
    'Light': '',
    'UV': '',
    'Pressure': '',
    'Noise': '',
    'BatteryVoltage': ''
 }

response = urequests.post( WEB_APP_URL, data=ujson.dumps(data))
response.close()

# 8.WLAN disconnect
wlan.disconnect()
wlan.active(False)


# 9.Sleep
sleep_time= 30*60      #30 min
while(sleep_time > 0):
    print(sleep_time)
    sleep_time=sleep_time-1
    utime.sleep(1)  # 1sec 


# 10.reset
import machine
machine.reset()

```

## その他
micropython の[example](https://github.com/micropython/micropython/tree/master/examples/bluetooth)が動かなくなっていたため、調査しながら作ったら、とてもスクリプトっぽいコードになってしまいました。


## 参考

https://qiita.com/sina_freemail/items/a5e62e10ae5a05833387

https://micropython.org/download/esp32/

https://docs.micropython.org/en/latest/index.html

https://monomonotech.jp/kurage/webbluetooth/ble_guide.html