kun432's blog

Alexaなどスマートスピーカーの話題中心に、Voiceflowの日本語情報を発信してます。たまにAWSやkubernetesなど。

〜スマートスピーカーやVoiceflowの記事は右メニューのカテゴリからどうぞ。〜

AlexaスキルのテストをBotiumでやってみた②

f:id:kun432:20210131175240p:plain

前回の続き。

テストシナリオの書き方について。

目次

前回のテストシナリオ

前回用意したテストシナリオを一つピックアップします。

tripadvisor_happypath01.convo.txt

トリップアドバイザーのテスト:Happy Path #1

#me
トリップアドバイザーを開いて

#bot
はじめまして、トリップアドバイザースキルをご利用いただきありがとうございます。このスキルでは行きたい日本の都市名をいうとおすすめの観光名所を提案します。例えば「京都に行きたい」と言ってみてください。

#me
京都 かな

#bot
京都 ですね。京都 は清水寺がおすすめです。

会話の内容がそのまま書いてあり、ユーザの発話パターンに応じて別のシナリオにしています。これはこれでいいんですが、色々バリエーションが増えてくると難しい。ということでこれを分離していきましょう。

発話のバリエーション

発話のバリエーションに対応するのがutterances.txtになります。以下のようなファイルを用意します。

happypath01_utt.utterances.txt

HAPPYPATH01_UTT
京都
京都 かな
京都 がいいな
京都 に行きたい

convo.txtの方を修正します。

トリップアドバイザーのテスト:Happy Path #1

#me
トリップアドバイザーを開いて

#bot
はじめまして、トリップアドバイザースキルをご利用いただきありがとうございます。このスキルでは行きたい日本の都市名をいうとおすすめの観光名所を提案します。例えば「京都に行きたい」と言ってみてください。

#me
HAPPYPATH01_UTT

#bot
京都 ですね。京都 は清水寺がおすすめです。

では実行してみましょう。他のテストファイルと分けるためにディレクトリを切って移動します。

$ mkdir happypath01
$ mv tripadvisor_happypath01.convo.txt happypath01/.
$ mv happypath01_utt.utterances.txt happypath01/.

ディレクトリを指定するには--convosを使います。

$ botium-cli run --convos happypath01


  Botium Test-Suite
    ✔ トリップアドバイザーのテスト:Happy Path #1/HAPPYPATH01_UTT-L1 (10810ms)
    ✔ トリップアドバイザーのテスト:Happy Path #1/HAPPYPATH01_UTT-L2 (10065ms)
    ✔ トリップアドバイザーのテスト:Happy Path #1/HAPPYPATH01_UTT-L3 (10429ms)
    ✔ トリップアドバイザーのテスト:Happy Path #1/HAPPYPATH01_UTT-L4 (10022ms)


  4 passing (42s)

utterances.txtに記載した4パターンでテストされているようですね。--verboseをつけてみるとよくわかります。

$ botium-cli run --convos happypath01 --verbose
(...snip...)
  botium-core-ScriptingProvider  HAPPYPATH01_UTT ({ convoDir: 'happypath01', filename: 'happypath01_utt.utterances.txt' }): 京都|京都 かな|京都 がいいな|京都 に行きたい +0ms
(...snip...)
  botium-core-ScriptingProvider assertBotResponse トリップアドバイザーのテスト:Happy Path #1/HAPPYPATH01_UTT-L1/Line 12 (Line 9: #me - 京都) BOT: 京都 ですね。京都 は清水寺がおすすめです。 = 京都 ですね。京都 は清水寺がおすすめです。 ... +5s
  botium-cli-run トリップアドバイザーのテスト:Happy Path #1/HAPPYPATH01_UTT-L1 ready, calling done function. +11s
(...snip...)
  botium-core-ScriptingProvider assertBotResponse トリップアドバイザーのテスト:Happy Path #1/HAPPYPATH01_UTT-L2/Line 12 (Line 9: #me - 京都 かな) BOT: 京都 ですね。京都 は清水寺がおすすめです。 = 京都 ですね。京都 は清水寺がおすすめです。 ... +5s
  botium-cli-run トリップアドバイザーのテスト:Happy Path #1/HAPPYPATH01_UTT-L2 ready, calling done function. +10s
(...snip...)
  botium-core-ScriptingProvider assertBotResponse トリップアドバイザーのテスト:Happy Path #1/HAPPYPATH01_UTT-L3/Line 12 (Line 9: #me - 京都 がいいな) BOT: 京都 ですね。京都 は清水寺がおすすめです。 = 京都 ですね。京都 は清水寺がおすすめです。 ... +5s
  botium-cli-run トリップアドバイザーのテスト:Happy Path #1/HAPPYPATH01_UTT-L3 ready, calling done function. +8s
(...snip...)
  botium-core-ScriptingProvider assertBotResponse トリップアドバイザーのテスト:Happy Path #1/HAPPYPATH01_UTT-L4/Line 12 (Line 9: #me - 京都 に行きたい) BOT: 京都 ですね。京都 は清水寺がおすすめです。 = 京都 ですね。京都 は清水寺がおすすめです。 ... +5s
  botium-cli-run トリップアドバイザーのテスト:Happy Path #1/HAPPYPATH01_UTT-L4 ready, calling done function. +11s
(...snip...)

  4 passing (41s)

もちろんAlexa(というかボット側)からの発話にバリエーションがある場合もutterances.txtは使えるようです。

スロットを変数として扱う

前回作成したもう一つのテストシナリオ見てみましょう。tripadvisor_happypath02.convo.txtのほうです。

トリップアドバイザーのテスト:Happy Path #2

#me
トリップアドバイザーを開いて

#bot
はじめまして、トリップアドバイザースキルをご利用いただきありがとうございます。このスキルでは行きたい日本の都市名をいうとおすすめの観光名所を提案します。例えば「京都に行きたい」と言ってみてください。

#me
奈良 に行きたい

#bot
奈良 ですね。奈良 は東大寺がおすすめです。

ユーザが「奈良」という可変となる値、すなわち「スロット」を発話したのに対して、ボットも繰り返すように「奈良」と答えていますよね。こういう場合には変数が使えます。Botiumでは"Scripting Memory"と呼ばれる機能です。

Scripting Memoryは初期状態では有効化されていないので、まずこれを有効化しましょう。botium.jsonを開いて、botium.Capabilitiesに以下を追加します。

{
  "botium": {
    "Capabilities": {
(...snip...)
      "SCRIPTING_ENABLE_MEMORY": true
    }
  }
}

ではテストシナリオを修正していきます。最初と同じようにディレクトリを作成してconbo.txtを移動しておきましょう。

$ mkdir happypath02
$ mv tripadvisor_happypath02.convo.txt happypath02/.

tripadvisor_happypath02.convo.txtを以下のように書き換えます。

トリップアドバイザーのテスト:Happy Path #2

#begin
SET_SCRIPTING_MEMORY city|奈良

#me
トリップアドバイザーを開いて

#bot
はじめまして、トリップアドバイザースキルをご利用いただきありがとうございます。このスキルでは行きたい日本の都市名をいうとおすすめの観光名所を提案します。例えば「京都に行
きたい」と言ってみてください。

#me
$city に行きたい

#bot
$city ですね。$city は東大寺がおすすめです。

cityという変数を用意して、最初にSET_SCRIPTING_MEMORYで「奈良」を設定しています。あとのやりとりではその部分はすべて$cityで記載しています。

実行してみます。

$ botium-cli run --convos happypath02 --verbose
(...snip...)
  botium-core-SetScriptingMemoryLogicHook Set scripting memory variable "$city" from "undefined" to "奈良, isGlobal: false, type: onConvoBegin" +0ms
(...snip...)
  botium-core-Convo トリップアドバイザーのテスト:Happy Path #2/Line 12: user says (cleaned by binary and base64 data and sourceData) {
  botium-core-Convo   "sender": "me",
  botium-core-Convo   "channel": null,
  botium-core-Convo   "not": false,
  botium-core-Convo   "optional": false,
  botium-core-Convo   "messageText": "奈良 に行きたい",
  botium-core-Convo   "media": null,
  botium-core-Convo   "buttons": null,
  botium-core-Convo   "cards": null,
  botium-core-Convo   "forms": null,
  botium-core-Convo   "attachments": null,
  botium-core-Convo   "asserters": [],
  botium-core-Convo   "userInputs": [],
  botium-core-Convo   "logicHooks": []
  botium-core-Convo } +2ms
(...snip...)
  botium-core-ScriptingMemory fill start: {} +5s
  botium-core-ScriptingMemory fill end: { '$city': '奈良' } +1ms
  botium-core-ScriptingProvider assertBotResponse トリップアドバイザーのテスト:Happy Path #2/Line 15 (Line 12: #me - $city に行きたい) BOT: 奈良 ですね。奈良 は東大寺がおすすめです。 = 奈良 ですね。奈良 は東大寺がおすすめです。 ... +5s
(...snip...)

  1 passing (12s)

対話のやり取りで$cityに当たる部分が、最初に設定した「奈良」で置き換えられて会話が行われているのがわかりますでしょうか。これを使えば、スロットのような可変の値もテストができますね。

上記ではconbo.txtに直接記載して変数を設定しています。

#begin
SET_SCRIPTING_MEMORY city|奈良

シナリオごとにこれを書くのはちょっと意味がないですよね。そして、ユーザの発話に応じておすすめされる「清水寺」や「東大寺」の部分も可変なので変数にしたいですね。ではこれを別のファイルに切り出して、happypath01/02のテストを一つにしてしまいましょう。

新しくディレクトリを用意して、conbo.txtをこんな感じで作ります。

$ mkdir happypath
$ vi happypath/tripadvisor_happypath.conbo.txt
トリップアドバイザーのテスト:Happy Path

#me
トリップアドバイザーを開いて

#bot
はじめまして、トリップアドバイザースキルをご利用いただきありがとうございます。このスキルでは行きたい日本の都市名をいうとおすすめの観光名所を提案します。例えば「京都に行きたい」と言ってみてください。

#me
$city に行きたい

#bot
$city ですね。$city は$placeがおすすめです。

以下の内容でhappypath/tripadvisor_happypath.scriptingmemory.txt を作成します。

      |$city |$place
Case1 |京都  |清水寺
Case2 |奈良  |東大寺

実行してみましょう。scriptingmemory.txtを使う場合は--expandscriptingmemoryが必要なようです。

$ botium-cli run --convos happypath --expandscriptingmemory true --verbose
(...snip...)
  botium-core-ScriptingProvider ReadConvosFromDirectory(happypath) scripting memories:
  botium-core-ScriptingProvider  {
  botium-core-ScriptingProvider   header: { name: 'Case1' },
  botium-core-ScriptingProvider   values: { '$city': '京都', '$place': '清水寺' },
  botium-core-ScriptingProvider   sourceTag: { filename: 'tripadvisor_happypath.scriptingmemory.txt' }
  botium-core-ScriptingProvider }
  botium-core-ScriptingProvider {
  botium-core-ScriptingProvider   header: { name: 'Case2' },
  botium-core-ScriptingProvider   values: { '$city': '奈良', '$place': '東大寺' },
  botium-core-ScriptingProvider   sourceTag: { filename: 'tripadvisor_happypath.scriptingmemory.txt' }
(...snip...)
  botium-core-Convo トリップアドバイザーのテスト:Happy Path.Case1/Line 9: user says (cleaned by binary and base64 data and sourceData) {
  botium-core-Convo   "sender": "me",
  botium-core-Convo   "channel": null,
  botium-core-Convo   "not": false,
  botium-core-Convo   "optional": false,
  botium-core-Convo   "messageText": "京都 に行きたい",
  botium-core-Convo   "media": null,
  botium-core-Convo   "buttons": null,
  botium-core-Convo   "cards": null,
  botium-core-Convo   "forms": null,
  botium-core-Convo   "attachments": null,
  botium-core-Convo   "asserters": [],
  botium-core-Convo   "userInputs": [],
  botium-core-Convo   "logicHooks": []
  botium-core-Convo } +2ms
(...snip...)
  botium-core-ScriptingMemory fill start: {} +5s
  botium-core-ScriptingMemory fill end: { '$city': '京都', '$place': '清水寺' } +0ms
  botium-core-ScriptingProvider assertBotResponse トリップアドバイザーのテスト:Happy Path.Case1/Line 12 (Line 9: #me - $city に行きたい) BOT: 京都 ですね。京都 は清水寺がおすすめです。 = 京都 ですね。京都 は清水寺がおすすめです。 ... +5s
  botium-cli-run トリップアドバイザーのテスト:Happy Path.Case1 ready, calling done function. +10s
    ✔ トリップアドバイザーのテスト:Happy Path.Case1 (10032ms)
(...snip...)
  botium-core-Convo トリップアドバイザーのテスト:Happy Path.Case2/Line 9: user says (cleaned by binary and base64 data and sourceData) {
  botium-core-Convo   "sender": "me",
  botium-core-Convo   "channel": null,
  botium-core-Convo   "not": false,
  botium-core-Convo   "optional": false,
  botium-core-Convo   "messageText": "奈良 に行きたい",
  botium-core-Convo   "media": null,
  botium-core-Convo   "buttons": null,
  botium-core-Convo   "cards": null,
  botium-core-Convo   "forms": null,
  botium-core-Convo   "attachments": null,
  botium-core-Convo   "asserters": [],
  botium-core-Convo   "userInputs": [],
  botium-core-Convo   "logicHooks": []
  botium-core-Convo } +1ms
(...snip...)
  botium-core-ScriptingMemory fill start: {} +5s
  botium-core-ScriptingMemory fill end: { '$city': '奈良', '$place': '東大寺' } +1ms
  botium-core-ScriptingProvider assertBotResponse トリップアドバイザーのテスト:Happy Path.Case2/Line 12 (Line 9: #me - $city に行きたい) BOT: 奈良 ですね。奈良 は東大寺がおすすめです。 = 奈良 ですね。奈良 は東大寺がおすすめです。 ... +5s
  botium-cli-run トリップアドバイザーのテスト:Happy Path.Case2 ready, calling done function. +11s
    ✔ トリップアドバイザーのテスト:Happy Path.Case2 (10646ms)

  2 passing (22s)

はい、それぞれが展開されて2回テストが行われているのがわかりました。

まとめ

まだまだ触りの部分しか触れていませんが、他にも

  • テストシナリオとなるconvoは、上記の書き方以外にもExcel、CSV、YAML、JSON、Markdownなどでも書ける。
  • シナリオの分割(シナリオから別のシナリオを呼び出す)もできる。
  • Scripting Memoryでは関数も使える。
  • マルチモーダルでのボタンアクションなどにも対応できる

などなど機能はかなり豊富にあるようですので、柔軟なテストシナリオを書くことができそうです。興味があればドキュメントをご覧ください。

botium-docs.readthedocs.io

次回はちょっとBotium Boxを見てみたいと思います。

Raspberry Piで観葉植物の監視をする②

f:id:kun432:20220219171625p:plain

前回の続き。

土壌水分センサーがなんとなくうまく動いていないさそう。ということで、ちょっとコードを追っかけてみた。

公式サンプルコード

Python全然わからないけど、

  • get_adcで各センサの電圧値を取得
  • valmapに電圧値を渡してパーセンテージ変換

してるように見える。

def valmap(value, istart, istop, ostart, ostop):
    value = ostart + (ostop - ostart) * ((value - istart) / (istop - istart))
    if value > ostop:
       value = ostop
    return value
                moisture1 = round(valmap(sensor1, 5, 3.5, 0, 100), 0)

5Vなら0%、3.5Vなら100%、というように考えて、センサー値のパーセンテージを算出してるみたいなんだけど、この53.5はどこから算出された数字なのかな?

soil-moistore-sensors.pyから余計なものを取っ払ってセンサーの電圧値だけを表示するコードを書いてみた。

import signal
import sys
import time
import spidev

spi_ch = 0

# Enable SPI
spi = spidev.SpiDev(0, spi_ch)
spi.max_speed_hz = 1200000

def close(signal, frame):
    sys.exit(0)

signal.signal(signal.SIGINT, close)

def get_adc(channel):

    # Make sure ADC channel is 0 or 1
    if channel != 0:
        channel = 1

    # Construct SPI message
    #  First bit (Start): Logic high (1)
    #  Second bit (SGL/DIFF): 1 to select single mode
    #  Third bit (ODD/SIGN): Select channel (0 or 1)
    #  Fourth bit (MSFB): 0 for LSB first
    #  Next 12 bits: 0 (don't care)
    msg = 0b11
    msg = ((msg << 1) + channel) << 5
    msg = [msg, 0b00000000]
    reply = spi.xfer2(msg)

    # Construct single integer out of the reply (2 bytes)
    adc = 0
    for n in reply:
        adc = (adc << 8) + n

    # Last bit (0) is not part of ADC value, shift to remove it
    adc = adc >> 1

    # Calculate voltage form ADC value
    # considering the soil moisture sensor is working at 5V
    voltage = (5 * adc) / 1024

    return voltage

if __name__ == '__main__':
    # Report the channel 0 and channel 1 voltages to the terminal
    try:
        while True:
            adc_0 = get_adc(0)
            adc_1 = get_adc(1)
            sensor1 = round(adc_0, 2)
            sensor2 = round(adc_1, 2)
            print("Soil Moisture Sensor 1:", sensor1, " Soil Moisture Sensor 2:", sensor2)
            time.sleep(0.5)
    except: KeyboardInterrupt

実行してみた。Sensor 1の方。まず水につけない場合。

$ python test-soil-moistore-sensors.py
Soil Moisture Sensor 1: 3.58  Soil Moisture Sensor 2: 0.0
Soil Moisture Sensor 1: 3.57  Soil Moisture Sensor 2: 0.01
Soil Moisture Sensor 1: 3.57  Soil Moisture Sensor 2: 0.01
Soil Moisture Sensor 1: 3.57  Soil Moisture Sensor 2: 0.01
Soil Moisture Sensor 1: 3.57  Soil Moisture Sensor 2: 0.01
Soil Moisture Sensor 1: 3.57  Soil Moisture Sensor 2: 0.01
Soil Moisture Sensor 1: 3.57  Soil Moisture Sensor 2: 0.01
Soil Moisture Sensor 1: 3.58  Soil Moisture Sensor 2: 0.01
Soil Moisture Sensor 1: 3.58  Soil Moisture Sensor 2: 0.01
Soil Moisture Sensor 1: 3.57  Soil Moisture Sensor 2: 0.01
(...snip...)

水につけた場合。

$ python test-soil-moistore-sensors.py
Soil Moisture Sensor 1: 1.9  Soil Moisture Sensor 2: 0.0
Soil Moisture Sensor 1: 1.9  Soil Moisture Sensor 2: 0.01
Soil Moisture Sensor 1: 1.9  Soil Moisture Sensor 2: 0.01
Soil Moisture Sensor 1: 1.9  Soil Moisture Sensor 2: 0.01
Soil Moisture Sensor 1: 1.9  Soil Moisture Sensor 2: 0.01
Soil Moisture Sensor 1: 1.89  Soil Moisture Sensor 2: 0.02
Soil Moisture Sensor 1: 1.9  Soil Moisture Sensor 2: 0.01
Soil Moisture Sensor 1: 1.9  Soil Moisture Sensor 2: 0.01
Soil Moisture Sensor 1: 1.9  Soil Moisture Sensor 2: 0.01
Soil Moisture Sensor 1: 1.89  Soil Moisture Sensor 2: 0.01
Soil Moisture Sensor 1: 1.9  Soil Moisture Sensor 2: 0.0
Soil Moisture Sensor 1: 1.9  Soil Moisture Sensor 2: 0.0
(...snip...)

REPLでvalmapを実行してみる。

$ python
Python 3.9.2 (default, Mar 12 2021, 04:06:34)
[GCC 10.2.1 20210110] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> def valmap(value, istart, istop, ostart, ostop):
...     value = ostart + (ostop - ostart) * ((value - istart) / (istop - istart))
...     if value > ostop:
...        value = ostop
...     return value
...
>>> print(round(valmap(3.67, 5, 3.5, 0, 100),0))
95.0
>>> print(round(valmap(1.9, 5, 3.5, 0, 100),0))
100

うーん、この計算だと、水分0のときでも95%ぐらいになってしまうよね・・・

さすがにget_adcの中を読み解くのは電子工作初心者には厳しいし、何が間違っているのかはわからないけど、とりあえず実際の値を踏まえて算出するように書き換えてみる。

import signal
import sys
import time
import spidev
import RPi.GPIO as GPIO

# Pin 15 on Raspberry Pi corresponds to GPIO 22
LED1 = 15
# Pin 16 on Raspberry Pi corresponds to GPIO 23
LED2 = 16

MAX=3.57
MIN=1.91

spi_ch = 0

# Enable SPI
spi = spidev.SpiDev(0, spi_ch)
spi.max_speed_hz = 1200000

# to use Raspberry Pi board pin numbers
GPIO.setmode(GPIO.BOARD)
GPIO.setwarnings(False)

# set up GPIO output channel
GPIO.setup(LED1, GPIO.OUT)
GPIO.setup(LED2, GPIO.OUT)

def close(signal, frame):
    GPIO.output(LED1, 0)
    GPIO.output(LED2, 0)
    sys.exit(0)

signal.signal(signal.SIGINT, close)

def valmap(value, istart, istop, ostart, ostop):
    value = ostart + (ostop - ostart) * ((value - istart) / (istop - istart))
    if value > ostop:
       value = ostop
    if value < ostart:
       value = ostart
    return value

def get_adc(channel):

    # Make sure ADC channel is 0 or 1
    if channel != 0:
        channel = 1

    # Construct SPI message
    #  First bit (Start): Logic high (1)
    #  Second bit (SGL/DIFF): 1 to select single mode
    #  Third bit (ODD/SIGN): Select channel (0 or 1)
    #  Fourth bit (MSFB): 0 for LSB first
    #  Next 12 bits: 0 (don't care)
    msg = 0b11
    msg = ((msg << 1) + channel) << 5
    msg = [msg, 0b00000000]
    reply = spi.xfer2(msg)

    # Construct single integer out of the reply (2 bytes)
    adc = 0
    for n in reply:
        adc = (adc << 8) + n

    # Last bit (0) is not part of ADC value, shift to remove it
    adc = adc >> 1

    # Calculate voltage form ADC value
    # considering the soil moisture sensor is working at 5V
    voltage = (5 * adc) / 1024

    return voltage

if __name__ == '__main__':
    # Report the channel 0 and channel 1 voltages to the terminal
    try:
        while True:
            adc_0 = get_adc(0)
            adc_1 = get_adc(1)
            sensor1 = round(adc_0, 2)
            if sensor1 < 0.5:
                moisture1 = 0
            else:
                moisture1 = round(valmap(sensor1, MAX, MIN, 0, 100), 0)
            sensor2 = round(adc_1, 2)
            if sensor2 < 0.5:
                moisture2 = 0
            else:
                moisture2 = round(valmap(sensor2, 5, 3.5, 0, 100), 0)
            print(f"Soil Moisture Sensor 1: {moisture1}% ({sensor1}) Soil Moisture Sensor 2: {moisture2}% ({sensor2})")
            if moisture1 < 40 or moisture2 < 40:
                GPIO.output(LED1, 1)
                GPIO.output(LED2, 0)
            else:
                GPIO.output(LED1, 0)
                GPIO.output(LED2, 1)
            time.sleep(0.5)
    finally:
        GPIO.cleanup()

テストで算出した電圧の最大値・最小値を定義しておいて、それに比例したパーセンテージを出すようにしてみた。最大値・最小値の範囲を超えるものはカットしてる。ついでに電圧も表示するようにした。

水につけていない状態。

$ python soil-moistore-sensors.py
Soil Moisture Sensor 1: 1.0% (3.56) Soil Moisture Sensor 2: 0% (0.0)
Soil Moisture Sensor 1: 1.0% (3.56) Soil Moisture Sensor 2: 0% (0.01)
Soil Moisture Sensor 1: 1.0% (3.56) Soil Moisture Sensor 2: 0% (0.01)
Soil Moisture Sensor 1: 1.0% (3.56) Soil Moisture Sensor 2: 0% (0.01)
Soil Moisture Sensor 1: 1.0% (3.56) Soil Moisture Sensor 2: 0% (0.01)
Soil Moisture Sensor 1: 1.0% (3.56) Soil Moisture Sensor 2: 0% (0.0)
Soil Moisture Sensor 1: 1.0% (3.56) Soil Moisture Sensor 2: 0% (0.0)
Soil Moisture Sensor 1: 1.0% (3.56) Soil Moisture Sensor 2: 0% (0.01)
Soil Moisture Sensor 1: 1.0% (3.56) Soil Moisture Sensor 2: 0% (0.01)
Soil Moisture Sensor 1: 1.0% (3.56) Soil Moisture Sensor 2: 0% (0.01)
Soil Moisture Sensor 1: 1.0% (3.56) Soil Moisture Sensor 2: 0% (0.01)
$ python soil-moistore-sensors.py
Soil Moisture Sensor 1: 99.0% (1.93) Soil Moisture Sensor 2: 0% (0.01)
Soil Moisture Sensor 1: 99.0% (1.93) Soil Moisture Sensor 2: 0% (0.01)
Soil Moisture Sensor 1: 99.0% (1.93) Soil Moisture Sensor 2: 0% (0.01)
Soil Moisture Sensor 1: 99.0% (1.93) Soil Moisture Sensor 2: 0% (0.01)
Soil Moisture Sensor 1: 99.0% (1.93) Soil Moisture Sensor 2: 0% (0.0)
Soil Moisture Sensor 1: 99.0% (1.93) Soil Moisture Sensor 2: 0% (0.0)
Soil Moisture Sensor 1: 99.0% (1.93) Soil Moisture Sensor 2: 0% (0.01)
Soil Moisture Sensor 1: 98.0% (1.94) Soil Moisture Sensor 2: 0% (0.01)
Soil Moisture Sensor 1: 99.0% (1.93) Soil Moisture Sensor 2: 0% (0.01)
Soil Moisture Sensor 1: 99.0% (1.93) Soil Moisture Sensor 2: 0% (0.01)
Soil Moisture Sensor 1: 99.0% (1.93) Soil Moisture Sensor 2: 0% (0.01)

もうちょっと細かく詰めないといけないと思うけど、とりあえずそれっぽい数字にはなったんじゃないかなー。センサーの値があんまり安定しないけど、まあそんなシビアなものでもないのでこれで十分かなという気がしてる。

Pythonほとんど書かないけど、見様見真似でなんとかなるもんだ。

参考

とても参考になりました。

Raspberry Piで観葉植物の監視をする①

f:id:kun432:20220219171625p:plain

以前に空気コンディションモニタを作った際に使った「Anavi」のuHAT、他にも色々あるみたいです。

その中で、最近ちょっと個人的にハマっている観葉植物向けに、必要なセンサーを組み合わせた「Gardening uHAT」があったので購入してみました。

今回はとりあえず各センサーの使い方まで。

目次

必要なもの

Raspberry Pi

f:id:kun432:20220326185807j:plain

実は在庫がどこにもなくて、これが一番ハードルが高いかも。以前買ったままで使ってなかったRaspberry Pi Zero Wがたまたまあって、uHATをつなげるにはGPIOピンが必要、ということで自分でハンダ付けしました。ちなみに人生ほぼ初のハンダ付けで、いろいろわかってなくてちょっと苦労しました。

今にして思えばなぜWHにしなかったのかとほんと後悔・・・でもおかげで学べたし、なんとか動いてよかった。

Anavi Gardening uHAT

f:id:kun432:20220326190032j:plain

今回のキモ。3パターンのパッケージがありますが、Developer Kitにしました。Developer Kitだと以下のセンサーが入ってます。

  • 土壌水分センサー x 2
  • 温湿度センサー(HTU21D)
  • 光センサー(BH1750)
  • 防水温度センサー(DS18B20)

全部つなぐとこんな感じです。

f:id:kun432:20220326202343j:plain

あと、本体にLEDが2つあります。

f:id:kun432:20220326203910j:plain

観葉植物

f:id:kun432:20220326181902j:plain

近所で見つけて買ってきた緋牡丹サボテンです。オレンジでほんとかわいい。いつか株分けとかしてみたい。

手順

OSイメージの書き込み

ZERO WなのでOSは"Raspberry Pi OS 32-bit"です。あと、今まで全然気づかなかったのだけど、Raspberry Pi ImagerでOS初期設定とか出来ちゃうんですね。

f:id:kun432:20220326203211j:plain

f:id:kun432:20220326203238j:plain

とりあえず、

  • ホスト名
  • SSH有効化
    • ユーザ名・パスワードの設定
  • WiFi設定
  • ロケール設定

あたりを設定してSDメモリに書き込みます。これならモニタもキーボードもいらないし、ホント便利ですね。

セットアップ

RPiを起動して、あとはマニュアルに従ってやっていくだけですが、一応一通りやってみます。

OSアップデート

$ sudo apt update
$ sudo apt upgrade

パッケージをインストール。マニュアルの下の方にもパッケージ追加する箇所がありますがここでまとめて。wiringpiはとりあえず不要。wiringpiもまだ必要ですね

$ sudo apt install -y git i2c-tools vim python3-rpi.gpio python3-dev
$ cd /tmp
$ wget https://project-downloads.drogon.net/wiringpi-latest.deb
$sudo dpkg -i wiringpi-latest.deb

raspi-configで各種インタフェースの有効化

$ sudo raspi-config

"Interfacing Options"を選択

f:id:kun432:20220326204459p:plain

以下のインタフェースを選択して、それぞれ有効化していきます。

  • SPI
  • I2C
  • 1-Wire

f:id:kun432:20220326204558p:plain

f:id:kun432:20220326204614p:plain

f:id:kun432:20220326204720p:plain

全部のインタフェースを有効にしたら、raspi-configを終了して、一旦再起動します。

f:id:kun432:20220326204730p:plain

各センサーの使い方

ここから各センサーの使い方を見ていきます。

公式のサンプルがあるのでまずはそちらをgit cloneします。

$ cd ~
$ git clone https://github.com/AnaviTechnology/anavi-examples.git
土壌水分センサー

公式のサンプルにPythonのコードが含まれています。

$ cd ~/anavi-examples/anavi-gardening-uhat/soil-moistore-sensors/python/
$ python3 soil-moistore-sensors.py
Soil Moisture Sensor 1: 95.0 % Soil Moisture Sensor 2: 0 %
Soil Moisture Sensor 1: 95.0 % Soil Moisture Sensor 2: 0 %
Soil Moisture Sensor 1: 95.0 % Soil Moisture Sensor 2: 0 %
...snip...

今回はセンサー1つだけなので、Sensor 1のほうですね。水につけてなくても95%もあるの?とりあえず水につけてみる。

...snip...
Soil Moisture Sensor 1: 100 % Soil Moisture Sensor 2: 0 %
Soil Moisture Sensor 1: 100 % Soil Moisture Sensor 2: 0 %
Soil Moisture Sensor 1: 100 % Soil Moisture Sensor 2: 0 %
...snip...

一応100%にはなる。これはちょっとコードを読んでみないといけないかも。とりあえずセンサーとしては動いていることは確認できたので良しとする。

温湿度センサー(HTU21D)

こちらも公式のサンプルを。Infrared pHATと同じですね。

$ cd ~/anavi-examples/sensors/HTU21D/c/
$ make
gcc -c -o HTU21D.o HTU21D.c -I.
gcc -c -o HTU21D-example.o HTU21D-example.c -I.
gcc -o HTU21D HTU21D.o HTU21D-example.o -I. -lwiringPi
$ ./HTU21D
HTU21D Sensor Module
25.29C
40.23%rh

取れていますね。

光センサー(BH1750)

こちらも公式のサンプルを。Infrared pHATと同じ。

$ cd ~/anavi-examples/sensors/BH1750/c/
$ make
$ ./BH1750
BH1750 Sensor Module
Light: 76 Lux
防水温度センサー(DS18B20)

こちらはサンプル不要です。

$ ls /sys/bus/w1/devices/
28-03089779830a  w1_bus_master1

$ cat /sys/bus/w1/devices/*/w1_slave
81 01 55 05 7f a5 a5 66 a3 : crc=a3 YES
81 01 55 05 7f a5 a5 66 a3 t=24062

t=の後の数字が温度です。1000で割ると摂氏温度になるようなので、上の例だと24℃ってことですね。

LED

最後にセンサーではないですが、LEDの操作。サンプルがあります。

$ cd ~/anavi-examples/anavi-gardening-uhat/led/python
$ python ./blink.py

f:id:kun432:20220326213801g:plain

まとめ

これで各センサーやLEDが操作できるところまできました。次回は実際に植物の監視的なことをやっていきます。