5
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

M5Stackを使った全自動Teamsコーヒー通知システム

Last updated at Posted at 2025-12-09

はじめに

こんにちは。(株)東芝 総合研究所の向本と申します。

業務では無線センサネットワークに関する研究開発を行っています。

去年同アドベントカレンダーにて、M5Stackをアクションボタンにしてコーヒーの完成をTeamsに通知するシステムについて記事を投稿しました。

これはこれで便利だったのですが、使っているうちにいくつか課題があることが分かってきました。大きなものとして以下が挙げられます。

  • ボタンを押すのが面倒だったり、押し忘れることがある
  • M5Stackの無線接続が切れた際に復旧に時間がかかる(場合によっては再起動が必要なことも)
  • M5Stackの無線性能上、電波環境がよくない場所では接続が切れがちになる

1つ目は気を付ければ済むという話ではあるのですが、せっかく通知するのであれば自動でやってほしいなと思うのが自然なことかなと思います。
2つ目、3つ目はどちらも無線が切れなければ起きない問題ではあるのですが、一度発生してしまうと現地にM5Stackの様子を見に行った上でリセットを行う必要があるので、意外と厄介な問題となります。

今回はこれらの課題を解決したコーヒー通知システムバージョン2を作成しようと思います。

上記の課題について、今回作成するシステムでは以下のように対応したいと思います。

  • ボタンの押し忘れ
    • 重量センサを用いてコーヒーができたことを自動で判定する
  • 無線切断時の復旧に難あり
    • M5Stackに持たせる機能を単純化することで復旧を容易にする
  • 無線性能が足りず場所により接続が不安定になる
    • より無線性能が良い機器をそばに配置し、それが中継役となり外部通信を行う

上記対応を踏まえて、新通知システムは以下のような構成にしたいと思います。
詳細は以下に詳しくお話していこうと思います。

新コーヒー通知システムの構成

M5Stack実装

まずはM5Stackのファームウェア実装を考えていきます。

重量センサの追加

1つ目の課題についてはコーヒーができたことを自動で判断できれば解決できると考えました。
コーヒーができたかどうかを判定する方法は温度センサや電力計など色々な方法があると思いますが、今回は重量センサを採用することにします。
これであればセンサを取り付けた台の上にコーヒーメーカーを置くだけで測定できるため給湯室のスペースを圧迫したりすることがなく、またコーヒーが出来上がった時に加えてコーヒーがなくなりそうな時にも通知ができると考えたためです。

M5Stackには外部センサとしてはかりキットがあり、これを用いることでM5Stackを用いて簡単に重量を測定することができます。今回はこれを使うことにします。
本センサを使うには別途センサを取り付ける台が必要になります。コーヒーメーカーの重量や大きさを考慮してオーブンレンジラックなどの適切な台を選択するとよいでしょう。

本センサを台に取り付けると、以下のようなイメージになります。

はかりキットを台に取り付けた様子

次に本センサの値を読み取るファームウェアを実装します。
前回はM5Stack側のファームウェア実装をESP-IDFで行いましたが、今回は後述する機能の単純化によりそこまで複雑な動作を行わないためUIFlowにて実装しようと思います。

UIFlowはM5Stack社が提供するファームウェア作成ソフトウェアで、各機能を持つブロックを組み合わせることでファームウェアを実装することができます。
UIFlowではかりキットのような外部センサを用いる場合、まずはUIFlowにセンサを読み込ませる必要があります。
今回の場合左下画面の"UINITS"から重量センサ"Weigh"を検索すると以下のように重量センサが現れるので、これが接続されているポートを指定します(今回はポートAに接続することにします)。これで重量センサを読み取るブロックを扱えるようになり、ブロックリストの"Units"に本センサに関するブロックが追加されます。

Unitsから重量センサを指定

今回は1分おきにセンサの重量値を読み取ることにします。
上記動作をUIFlowで表現すると、以下の図のようになります。

重量センサから値を読み取るUIFlowブロック

この状態で値を読み取ると、初期値として何も載っていない場合でも数万の値が表示されます。ゼロセットを行うブロックを入れることで正しい重量値を読み取れるようになりますが、今回は本機が起動したときにコーヒーメーカーのポットが外れていたり装着されていたりと状態が変わる可能性があり、ゼロセットするタイミングでのコーヒーメーカの重さを決められないことからこれは行わないことにします。
また、読み取った値は正確にはロードセルによる出力であり重量そのものは表示していないため、これを重量に変換する必要があります。
本件ではいろいろな重さのものを測定した結果を踏まえて、上図のような値で読み取り値を割ることで重量に変換しました。

M5Stackの機能を単純化および無線中継機の設置

2つ目の課題に対してはM5Stackの機能を単純化することで対応します。
現システムではM5Stack上のボタンが押された際にメールを送信するように設定していましたが、その際無線接続が切れているとM5Stackの状態がおかしくなり機器を再起動する必要が出てくる状況でした(単にエラーハンドリングが不十分なだけの可能性もありますが…)。
そこで今回はM5Stack側の動作を単純に上記重量センサの値を読み取りデータ送信を行うのみとし、メール送信や重量によるコーヒーポットの状態判定は別途サーバを用意しそちらに実行させることにします。

また、3つ目の課題として、設置位置の関係上M5Stackの無線性能では単独での無線通信が難しいという問題がありました。
こちらに対してはRaspberryPiとM5StackをUSBケーブルで接続し、M5StackはUART通信で重量データを送信しRaspberryPiが代わりにWi-Fiでデータ送信を行うことにします。そのためここではM5StackにUART通信を行わせることを考えます。

UARTによるデータ送信もUIFlowで実現できます。
ブロックリストから"Hardware"->"UART"と開くことでUART通信関連のブロックが表示されます。
今回M5Stackは送信のみ行うので、起動時に初期化を行い重量測定後に書き込みブロックで読み取った値を送ればOKです。
初期化では送受信ピンおよびボーレートを設定する必要があります。M5Stack Core2のUSB端子を用いる場合、ピン番号は送信が1、受信が3となります。ボーレートに関しては受信側となるRaspberryPiと値を合わせていればなんでもよいですが、今回は9600にします。

以上の対応を踏まえると、UIFlowのブロック構成は以下のようになります。

M5Stackで動作するファームウェアブロック構成

次はM5Stack以外の機器で動くプログラムを考えていきます。

中継機器、サーバの実装

今回のシステムでは新たに無線通信の中継機器とデータを受信するサーバを追加することになります。
次にこれらの動作を考えていきます。

中継機実装

中継機はM5Stackの代わりに無線通信を担います。
やることは簡単で、UARTでM5Stackから測定値を受け取り、これをMQTTで送信するだけです。
実際にはわざわざMQTTを仲介することなく中継機が直接重量を判定してメール送信を行ってもよいのですが、オフィスで音を鳴らすなど今後他の機器とも連携することを考慮して、拡張性を持たせるためにMQTTを利用する方式をとりました。

今回はPythonスクリプトで本動作を実装します。
UART通信もMQTT通信もそこまで特別なことはしていないので、説明は割愛しスクリプトのみを掲載します。
ボーレートのみM5Stackで設定した値と合わせることに気を付けてください。


import serial
import time
import os
import subprocess
import paho.mqtt.client as mqtt 

client = mqtt.Client()
client.connect('mqtt.broker', 1883, 60)

ser = serial.Serial('/dev/ttyUSB0', 9600, timeout=0.1)
time.sleep(2) 

try:
    while True: 
        if ser.in_waiting > 0: # シリアル通信 受信待ち
            bytes = ser.readline()
            result = bytes.decode('utf-8')
            client.publish('coffee/weight', result)
            
        time.sleep(0.2)

except KeyboardInterrupt:
    pass

print('end')
ser.close()

サーバ実装

次にサーバ側の動作を考えます。
主なやることとしてはMQTTで重量データを受信し、これをもとにコーヒーメーカーの状態を判断、それをMS Teamsに通知する、といったところになります。
Teamsへの通知に関しては以前の投稿と同様の方法、つまりメール送信とPowerAutomateを活用した方法をとります。
つまりサーバとしてはメール送信まで行えばOKになります。

ここではポットが満杯の時と空の時の重量を測定し、それをもとにした値を閾値として受信した重量を比較することにします。
つまり満杯の閾値を測定値が上回った際にコーヒーができた旨のメールを送信し、空の閾値を下回った際に空になった旨のメールを送信します。
ただし、空状態の判定に関しては誰かがポットを外してコーヒーを注いでいるときに測定が行われるとコーヒーがあるにもかかわらず軽い値が送信されてしまうので、1度の受信で判定せず2回連続で閾値を下回る値を受信した際に空であると判定することにします。

後述するPowerAutomateではメールの件名をトリガとしてフローを起動し、フロー内では本文を見て空か満杯かを判断します。
そのためサーバ側では判定に応じて本文の内容を変えてメール送信することになります。

以上を踏まえると、サーバで動作するプログラムは以下のようになります。


import paho.mqtt.client as mqtt

from smtplib import SMTP
from email.mime.text import MIMEText
from email.mime.multipart import MIMEMultipart
from email.utils import formatdate

# ポットの重量を測定したところ、空で800g、水を入れた状態で2300gと測定された。
# 測定誤差を考慮して閾値を以下のように設定
WEIGHT_BORDER_FULL  = 2000
WEIGHT_BORDER_EMPTY = 900

# はかりセンサの初期状態の取得値。ポットを含まないコーヒーメーカーを置いた際の測定値
BASE_VALUE = 335770

# ポットが空だと判定するのに必要な受信数
# 送信時に偶然ポットが取り出されているだけの可能性があるため、空判定は複数回の受信で判定する
EMPTY_COUNT = 2 
empty_count = EMPTY_COUNT

pot_state = "empty" # empty or full

def on_connect(client, userdata, flag, rc):
  client.subscribe("coffee/weight")  # M5Stack側で決めたトピックをサブスクライブ 

# ブローカーが切断したときの処理
def on_disconnect(client, userdata, rc):
  if  rc != 0:
    print("Unexpected disconnection.")

# メッセージが届いたときの処理
def on_message(client, userdata, msg):
  adjusted_value = float(msg.payload) - BASE_VALUE # 初期値を引いた値がポット部分の重量
  judge_weight(adjusted_value)

def judge_weight(weight_value):
# 測定された重量から状態を判定
  global pot_state
  global empty_count

  if (weight_value > WEIGHT_BORDER_FULL) & (pot_state == "empty"):
    # ポットに水が溜まっている状態に変化した:コーヒーができあがることを通知
    pot_state = "full"
    send_email("making")
    empty_count = EMPTY_COUNT # empty判定が途切れたのでカウントリセット
  elif (weight_value < WEIGHT_BORDER_EMPTY) & (pot_state =="full"):
    # ポットの重量が閾値よりも軽くなった:empty判定でカウントを1減らす
    empty_count = empty_count - 1
    if empty_count == 0: 
      # 2回連続empty判定になった:ポットが空だと判断
      pot_state = "empty"
      send_email("empty")
      empty_count = EMPTY_COUNT
  else:
    # ポットが空でも満杯でもない状態:emptyカウントをリセット
    empty_count = EMPTY_COUNT

def send_email(state):
    # 状態に応じた文面のメールを送信
    host = "mailhost.jp"
    port = 25
    message = "coffee " + state

    msg = MIMEText(message, "plain", 'utf-8')
    msg["Subject"] = "coffee test"
    msg["From"] = "from_address@mail.co.jp"
    msg["To"] = "to_address@mail.co.jp"

    server = SMTP(host, port)
    server.send_message(msg)
    server.quit()


# MQTTの接続設定
client = mqtt.Client()
client.on_connect = on_connect
client.on_disconnect = on_disconnect
client.on_message = on_message

client.connect("mqtt_broker", 1883, 60)
client.loop_forever()

PowerAutomateのフロー実装

最後にPowerAutomateのフローを実装していきます。
基本的には前回作成したものをベースとして使いまわせばよいですが、以下の2点を付け加える必要があります。

  1. コーヒーができたことだけでなく、なくなったことも通知する(受信メールの本文で判断)
  2. 水を入れたタイミングでメールを送るため、コーヒーを作る時間を考慮して通知タイミングを少し遅らせる

1つ目の項目への対応として、メール本文を参照する条件分岐を追加し、本文に"making"と書かれているか否かでTeamsに通知する文面を変更します。具体的には条件ブロックを追加し、ブロック内に以下のように記載します。

PowerAutomate条件分岐ブロック

2つ目の項目については、通知アクションの前に遅延時間を入れることで対応します。おおよそコーヒーができるのに10~15分前後かかるため、今回は15分の遅延を入れることにします。

以上の2点を追加すると、フローは以下のようになります。
今回紹介しなかったメールを受信する部分やTeamsへの投稿については、以前の投稿をご参照ください。

PowerAutomateフロー構成

これですべてのプログラムを実装できました。すべてを起動してコーヒーメーカーに水を入れてみると、少し経った後に以下のような通知が来ました

Teams通知

これで、自動でコーヒーメーカーの状態を通知できるようになりました。

今回はM5Stackと重量センサを用いて、給湯室にあるコーヒーメーカーの状態を自動で通知するシステムを作成しました。
前回のものよりも登場人物が増えてやや複雑になってしまいましたが、人の手を介する部分が減ってより便利になったのではないかなと思います。
重量センサで測るものを変えることで他のシステムにも応用できると思うので、気になった方はぜひお試しください。

※本記事に掲載の商品、機能等の名称は、それぞれ各社が商標として使用している場合があります。

5
0
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
5
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?