Bluetooth通信でパソコンへcsvデータ保存、遠隔操作する方法

Bluetoothシリアルでデータ記録機アイキャッチ

Bluetooth通信を使用して、デバイスで測定したデータをパソコンに送信して記録したり、パソコンからデバイスを遠隔操作する方法を、Pythonで簡単に自作できるシリアルモニタを使用して詳しく紹介します。

Bluetooth通信デバイスには「M5StickC Plus」を、開発環境には「ArduinoIDE」を使用します。
パソコンは「Python」がインストールされていれば「Windows」でも「Mac」でも大丈夫です。

パソコンと「M5StickC Plus」との通信データの確認にはシリアルモニタを使用します。
「ArduinoIDE」のシリアルモニタでもデータの送受信は確認できますが、データの記録(CSVファイルに保存)機能はないため、データ記録機能を持ったシリアルモニタアプリを「Python」で自作して動作確認します。
「Python」アプリの自作には「生成AI」を使用して日本語で作成します。
「Copilot」や「Gemini」等何でも良いですが、今回は「ChatGPT」を使用しました。
日本語で作成した「プロンプト」も公開してますので、作成して動作確認してみてください。
シリアルモニタの機能だけなら1発で動作確認できるものが作成できると思いますが、記録機能を追加したものは少し修正が必要だったので、完成した「Python」プログラムも最後に公開しています。
スポンサーリンク

1.動作紹介

まずは今回、どんなものを作って、どんなことができるのかを、実際に動作確認しながら紹介します。

・Bluetooth通信デバイスとのペアリング

Bluetooth通信デバイスには「M5StickC Plus」を使用します。

「M5StickC Plus2」でも同じように動作確認できますが、初期設定やLEDの出力ON/OFFが逆だったりするので、PlusとPlus2両方のプログラムを公開しています。

「M5StickC Plus」にサンプルプログラムを書き込んで実行すると液晶画面に以下のように表示され、BluetoothのMACアドレスが表示されます。
送受信データの動作確認用に温湿度センサ「ENV.Ⅳ」を接続しています。

Bluetooth通信デバイスM5StickC Plus

次にパソコンとのペアリングを行います。ペアリング方法は他のBluetooth機器と同じです。
ペアリングを実行するとデバイス名「M5StickC_Plus」が見つかるため、選択して登録します。

Bluetoothデバイスとのペアリング

まずは上画像のように[スタートメニューアイコン]を右クリックして[設定]をクリックします。

Bluetoothデバイスとのペアリング

設定画面で[Bluetoothとデバイス]をクリックして[デバイスの追加]をクリックします。

Bluetoothデバイスとのペアリング

上画像のようなウインドウが表示されたら[Bluetooth]をクリックします。

Bluetoothデバイスとのペアリング

しばらくすると上画像のように「M5StickC_Plus」が認識されて表示されるので、これをクリックします。

Bluetoothデバイスとのペアリング

上画像のように「接続済み」という表示が確認できれば、ペアリング完了です。

デバイスが表示されない場合はデバイスの電源を入れ直してみてください。
ペアリング済みでも、プログラムの書き換え等で通信できなくなることがあります。
この場合は、一度ペアリングを解除(デバイスの削除)を行なって、再度ペアリングしてみてください。

・自作シリアルモニタとの接続、使い方

デバイスのペアリングが完了したら、自作のシリアルモニタを「python」で実行して起動します。pythonのインストール方法は以下のリンクで詳しく紹介しています。

pythonのダウンロードからインストール方法の紹介
人気のプログラミング言語 python のインストール方法の紹介です。python はアプリ開発やWebサイト構築、ディープラーニングにも使用されますが、マイコンボードの統合開発環境「Platform IO」のインストールにも必要です。

「python」を実行するには下画像のように「検索ウインドウ」に「cmd」と入力して、表示されるメニューから[コマンドプロンプト]をクリックします。

コマンドプロンプトの起動

pythonの実行環境によって起動方法は異なるかもしれませんが、私の環境では以下のように「コマンドプロンプト」から、実行するファイル「logikara_serial_monitor.py」を保存したフォルダで、以下のようにコマンドを実行すると起動します。

python logikra_serial_monitor.py

実際に実行している様子は以下のようになります。
実行ファイルは[Documents]→[python]フォルダに保存した場合の例です。
保存場所の指定は、ご自身の環境に合わせて変更してください。

コマンドプロンプトの起動

実行すると下画像のようなアプリ画面が起動します。
起動したら[ポート選択:]横の[ドロップダウンリスト]部をクリックします。

シリアルモニタ動作確認
シリアルモニタ動作確認

上画像のように通信可能なポートのリストが表示されるため、ペアリングした「M5StickC Plus」のポートを選択します。

シリアルモニタ動作確認

「M5StickC Plus」へのデータ送信の区切り(デリミタ)に「CR」を使用するため[Add CR]をチェックして[接続]ボタンをクリックします。

シリアルモニタ動作確認

上画像のように[送信テキストエリア]に「LED ON」と入力して[Enterキー]を押す([送信ボタン]でも良い)と「M5StickC Plus」へデータが送信されます。

Bluetoothシリアル通信動作確認

「M5StickC Plus」がデータを受信すると、液晶画面に「B:LED ON」と表示され、本体LEDが点灯します。

シリアルモニタ動作確認

同様に「LED OFF」と入力して[Enterキー]を押す([送信ボタン]でも良い)とデータが送信されます。

Bluetoothシリアル通信動作確認

同様に、液晶画面に「B:LED OFF」と表示され、本体LEDは消灯します。

「M5StickC Plus」をUSBケーブルでパソコンと接続して、通信ポートを選択すれば有線でも同様に動作確認ができます。
有線接続の場合は液晶画面に「S:LED ON」のように「S:〜」で表示されます。

次にデータの受信動作について確認します。
シリアルモニタがデータを受信すると、以下のように「M5StickC Plus」で測定した温度と湿度のデータが1秒ごとに表示されます。

シリアルモニタ動作確認

「M5StickC Plus」本体正面の「Aボタン」を押すとデータの送信が開始され、1秒ごとに受信データが「シリアルモニタ」に表示されていきます。
もう一度「Aボタン」を押すとデータの受信は終了します。

シリアルモニタ動作確認

「M5StickC Plus」本体右側面の「Bボタン」を押すと「シリアルモニタ」には「B ON!」と表示されます。
こちらは動作確認用のため、必要に応じて違うデータを送信する等「M5StickC Plus」側で設定して使用できます。

シリアルモニタのデータ受信は常に待機状態のため、受信するごとに表示されます。
データの受信サイクルは「M5StickC Plus」側のデータ送信サイクルで調整します。

・受信データの記録(CSVデータ)

今回の1番やりたかったデータ記録の動作については以下のようになります。

シリアルモニタでCSVデータ記録

まずは[受信データ削除]ボタンを押すと「受信エリア」が消去できるため、受信データがわかりやすくなるように消去しておきます。

[記録ボタン]を押すと上画像のように表示され、データを記録する「csvファイル」がタイムスタンプをファイル名として作成され、記録待機状態になります。

シリアルモニタでCSVデータ記録

「csvファイル」はpythonの実行ファイルがあるフォルダに上画像のように作成されています。

シリアルモニタでCSVデータ記録

「M5StickC Plus」本体正面の「Aボタン」を押してデータの送信が開始されると、シリアルモニタの受信エリアに上画像のように受信データが、タイムスタンプに続いて温度(℃)、湿度(%)の順に表示され、同時に「csvファイル」に追記して保存されていきます。

シリアルモニタでCSVデータ記録

再度「Aボタン」を押すと、データの送信は終了します。データの記録を終了するには[停止ボタン]を押します。

記録された「csvファイル」はエクセルを起動して、ファイル名を指定して開くと以下のようにセルごとに分かれて表示されるため、グラフ表示やデータ分析に使用することができます。

記録時の受信データは「改行(LF)」区切り(デリミタ)単位で記録処理されるため、デバイスから送信するデータの末尾には「改行(LF)」が必要です。
スポンサーリンク

2.Bluetooth通信デバイスの準備

ここからはデータを送信するBluetooth通信デバイスの「M5StickC Plus」の製作方法を詳しく紹介します。

・M5StickC Plusとは

「M5StickC Plus」とは「M5Stackテクノロジー社」のマイコンボードで、1.14インチTFT液晶画面や入出力端子、操作ボタン、LED、赤外線送信、ブザー、3軸ジャイロ+3軸加速度センサ、マイク、RTC(リアルタイムクロック)、バッテリーが内蔵されています。

M5StickC Plus本体機能、端子配列

他にもシリアル通信はもちろんWiFiやBluetoothにも対応していて、これらの機能をプログラムで自由に使用することができできます。

プログラムは「C言語」ベースの「Arduino IDE」「PlatformIO」や「ビジュアルプログラミング(UiFlow)」「Python(MicroPython)」で作成できます。

「M5StickC Plus」の使い方は以下のリンクで詳しく紹介しています。

M5StickC Plusの使い方、初期設定、サンプルプログラム、M5StickCとの違い等を詳しく紹介
M5StickC PlusをArduino IDEやPlatformIOで使うための初期設定やサンプルプログラムでの動作確認の方法です。ビジュアルプログラミングのUiFlowの初期設定についても紹介します。

・開発環境(ArduinoIDE)、ライブラリの準備

・開発環境(ArduinoIDE)

開発環境「ArduinoIDE」のインストールやライブラリの準備方法は以下のリンクで詳しく紹介しています。

Arduino IDE 2のインストール方法、初期設定、使い方
バージョンアップして使いやすくなったArduino IDE 2のインストールから使い方まで詳しく紹介、便利な機能やM5Stack、ラズパイPicoでの使用方法も紹介します。

「M5StickC Plus」を含む「M5Stackシリーズ」を使用するにはUSBドライバのインストール等の初期設定が必要です。これについては以下のリンクで詳しく紹介しています。

M5StackシリーズのためのArduino IDEのインストール方法と初期設定、使い方紹介
ArduinoIDEバージョン2のインストール方法から初期設定、スケッチ例の書き込み、コピペでの使い方まで詳しく紹介します。インストールはArduinoでも同じです。

・ライブラリ

使用するライブラリは以下表のようになります。
「ArduinoIDE」で事前にインストールしておいてください。

ライブラリ名用途バージョン検索名
M5StickCPlusM5StickC Plus制御用0.1.0stickcplus
M5GFX液晶表示制御用0.2.5m5gfx
「M5StickC Plus2」を使用する場合は「M5StickCPlus2」ライブラリをインストールしてください。

・Bluetooth通信の使い方

「M5StickC Plus」で「Bluetooth通信」を使用するには「BluetoothSerial」ライブラリを使用します。「BluetoothSerial」ライブラリは個別にインストールする必要はありません。

「BluetoothSerial」ライブラリの使用方法は以下のリンクで詳しく紹介しています。

M5StickC Plusで簡単Bluetooth相互通信、遠隔操作する方法
M5StickC PlusとM5StickC(両方M5StickC Plusやパソコンでも可)を使用して、Bluetooth(ブルートゥース)相互通信、遠隔操作(Arduinoコマンド使用)する方法を紹介します。

・温湿度センサ「ENV.Ⅳ」の使い方

「M5StickC Plus」には温湿度センサーを接続してシリアルモニタへの送信データとして使用します。

温湿度センサーは「M5Stack社」の「ENV.Ⅳ」を使用します。
気圧の測定もできますが、今回は温度と湿度のみを使用します。

「ENV.Ⅳ」には専用のライブラリもありますが、ライブラリを使用しなくても「I2C通信」で比較的簡単にデータの取得ができるため、シンプルに直接データを取得しています。

「M5StickC Plus」との接続は「Groveコネクタ配線」で下画像のように接続するだけです。

Bluetooth通信デバイスM5StickC Plus
M5Stack用温湿度気圧センサユニット Ver.4 (ENV Ⅳ)
共立電子産業株式会社 KYOHRITSU ELECTRONIC INDUSTRY CO.,LTD.

温湿度センサー「ENV.Ⅳ」の使い方は、以下のリンク(ENV.Ⅲを使用していますが使い方は同じです)で詳しく紹介しています。

I2C通信の使い方をサンプルプログラムで詳しく紹介(Arduinoコマンド)
温度と湿度の測定できるセンサ(ENV Ⅲ)からライブラリ使用せずにデータを取得する方法を例にI2C通信の使い方を紹介します。データはM5StickC Plusの液晶表示器に表示します。

・サンプルプログラム(M5StickC Plus/Plus2)

今回、Bluetooth通信デバイスには「M5StickC Plus」を使用していますが、「M5StickC Plus2」とどちらを使用するか迷ったので両方のプログラムを公開しておきます。
それぞれは初期設定等が微妙に異なるだけですが、「コピペ」で動作できるように全文公開しています。

M5StickC Plus用

「M5StickC Plus」のサンプルプログラムは以下になります。
コピペで貼り付けて書き込んでください。コピーは下の黒塗り部右上のアイコンクリックでもできます。

#include <M5StickCPlus.h>    // M5StickCPlusライブラリをインクルード
#include <M5GFX.h>           // M5GFXライブラリをインクルード (グラフィックス描画用)
#include "BluetoothSerial.h" // Bluetoothシリアル通信ライブラリをインクルード

#define DEVICE_NAME "M5StickC_Plus" // 接続名を設定
#define LED_PIN 10                  // LEDピン番号
#define CYCLE_TIME 1000             // 送信サイクル時間(ms)
#define SHT3X_ADDR 0x44 // SHT3X(温度、湿度センサ)I2C通信アドレス

static M5GFX lcd;             // M5GFXのインスタンス作成
static M5Canvas canvas(&lcd); // メモリ描画領域をcanvasという名前で準備
BluetoothSerial SerialBT;     // BluetoothSerialのインスタンスを作成

// 変数宣言
bool btnPwFlag = 0;       // 電源ボタン状態取得用
String data = "";         // 受信データ格納用
unsigned long beforTime;  // 送信サイクルカウント前時間格納用
bool cycleOutputFlag;     // 送信サイクル動作実行フラグ
float temperature = 0.0;  // 温度データ格納用
float humidity = 0.0;     // 湿度データ格納用

/************************* 温湿度取得関数 ************************/
void getEnv() {
  // 変数宣言
  uint8_t data[6];  // 温度、湿度受信データ格納用

  // I2Cデータ送信
  Wire.beginTransmission(SHT3X_ADDR); // デバイスアドレスを指定して I2C通信開始
  Wire.write(0xFD);           // 要求コマンド送信(高精度測定を指定)
  Wire.endTransmission(true); // 書き込み完了
  delay(10);                  // データ受信待ち(時間はデータシートによる:0.01s)

  // I2Cデータ受信(温度、湿度それぞれ 測定データ:8bit ×2 + CRC:8bit で合計6byte)
  Wire.requestFrom(SHT3X_ADDR, 6); // 受信データリクエスト(6byte)
  for (int i = 0; i < 6; i++) {    // 6byte分繰り返し
    data[i] = Wire.read();         // 受信データ格納
  }
  // 受信データを温度、湿度に換算
  int t_ticks = (data[0] << 8) | data[1];  // 温度計算用
  int rh_ticks = (data[3] << 8) | data[4]; // 湿度計算用
  temperature = -45 + 175 * ((float)t_ticks / 65535); // 温度換算
  humidity = constrain(-6 + 125 * ((float)rh_ticks / 65535), 0, 100); // 湿度換算
}

// 初期設定 -----------------------------------------------
void setup() {
  M5.begin();         // 本体初期化
  Serial.begin(9600); // シリアル通信初期化
  Wire.begin(32, 33); // I2C通信端子指定(SDA, SCL)

  // Bluetooth通信初期化
  SerialBT.begin(DEVICE_NAME); // 接続名を指定して初期化。第2引数にtrueを書くとマスター、省略かfalseでスレーブ
  SerialBT.begin(9600);        // 通信速度(ボーレート)

  // 出力設定
  pinMode(LED_PIN, OUTPUT);    //本体LED赤
  digitalWrite(LED_PIN, HIGH); //本体LED初期値OFF(HIGH)

  // ベース画面の初期設定
  lcd.init();            // 初期化
  lcd.fillScreen(BLACK); // 背景色
  lcd.setRotation(0);    // 画面向き設定(USB位置基準 0:下/ 1:右/ 2:上/ 3:左)
  lcd.setTextSize(1);    // 文字サイズ(整数倍率)

  canvas.setTextWrap(false);  // 改行をしない(画面をはみ出す時自動改行する場合はtrue。書かないとtrue)
  canvas.createSprite(lcd.width(), lcd.height()); // canvasサイズ(メモリ描画領域)設定(画面サイズに設定)

  // LCD表示設定
  canvas.setTextFont(2);
  canvas.println(DEVICE_NAME); // 接続名表示

  // MACアドレスの取得と表示
  uint8_t macBT[6];
  esp_read_mac(macBT, ESP_MAC_BT);  // MACアドレス取得
  canvas.printf("%02X:%02X:%02X:%02X:%02X:%02X\r\n", macBT[0], macBT[1], macBT[2], macBT[3], macBT[4], macBT[5]);
}

// メイン -------------------------------------------------
void loop() {
  M5.update();  // 本体のボタン状態更新

  // 送信サイクル動作処理
  unsigned long currentTime = millis(); // 現在の経過時間を取得
  if (currentTime - beforTime >= CYCLE_TIME) {  // 前回の時間から送信サイクル時間経過していたら
    if (cycleOutputFlag == true) {  // 送信サイクル動作実行フラグON
      getEnv();               // 温湿度取得関数呼び出し
      Serial.printf("%.1f,%.1f\n", temperature, humidity);
      SerialBT.printf("%.1f,%.1f\n", temperature, humidity);
    }
    beforTime = millis(); //送信サイクルカウント用の前回時間を初期化
  }

  // ボタン操作処理(マスター側へデータ送信)
  if (M5.BtnA.wasPressed()) {   // ボタンAが押されていれば
    SerialBT.print("A ON!\n");  // 「A ON!」送信(「CR」区切り文字)
    cycleOutputFlag = !cycleOutputFlag; // 送信サイクル動作実行フラグ反転
  }
  if (M5.BtnB.wasPressed()) {   // ボタンBが押されていれば
    SerialBT.print("B ON!\n");  // 「B ON!」送信(「CR」区切り文字)
  }

  // Bluetooth通信データ受信処理
  if (SerialBT.available()) {               // Bluetoothデータ受信で
    data = SerialBT.readStringUntil('\r');  // 区切り文字「CR」の手前までデータ取得
    canvas.print("B: ");                    // Bluetoothデータ液晶表示
    canvas.println(data);                   // 液晶表示
    Serial.print(data);                     // シリアル出力
  }
  // シリアル通信データ受信処理
  if (Serial.available()) {               // シリアルデータ受信で
    data = Serial.readStringUntil('\r');  // 「CR」の手前までデータ取得
    canvas.print("S: ");                  // シリアルデータ液晶表示
    canvas.println(data);                 // 液晶表示
    Serial.print(data);                   // シリアル出力
    SerialBT.print(data);          // Bluetooth出力(区切り文字「CR」をつけてマスター側へ送信)
  }

  // 受信コマンドの処理
  if (data == "LED ON") {        // 受信データが「A ON!」なら
    digitalWrite(LED_PIN, LOW);  // 本体LED点灯
  }
  if (data == "LED OFF") {       // 受信データが「B ON!」なら
    digitalWrite(LED_PIN, HIGH); // 本体LED消灯
  }

  // メモリ描画領域を座標を指定して一括表示(スプライト)
  canvas.pushSprite(&lcd, 0, 0);  // (0, 0)座標に一括表示実行
}

M5StickC Plus2用

「M5StickC Plus」のサンプルプログラムは以下になります。
コピペで貼り付けて書き込んでください。コピーは下の黒塗り部右上のアイコンクリックでもできます。

#include <M5StickCPlus2.h>   // M5StickCPlusライブラリをインクルード
#include <M5GFX.h>           // M5GFXライブラリをインクルード (グラフィックス描画用)
#include "BluetoothSerial.h" // Bluetoothシリアル通信ライブラリをインクルード

#define DEVICE_NAME "M5StickC_Plus2" // 接続名を設定
#define LED_PIN 19                   // LEDピン番号
#define CYCLE_TIME 1000              // 送信サイクル時間(ms)
#define SHT3X_ADDR 0x44  // SHT3X(温度、湿度センサ)I2C通信アドレス

// メモリ描画領域表示(スプライト)のインスタンスを作成(必要に応じて複数作成)
M5Canvas canvas(&M5.Lcd);  // &M5.Lcd を &StickCP2.Display と書いても同じ
BluetoothSerial SerialBT;  // BluetoothSerialのインスタンスを作成

// 変数宣言
bool btnPwFlag = 0;       // 電源ボタン状態取得用
String data = "";         // 受信データ格納用
unsigned long beforTime;  // 送信サイクルカウント前時間格納用
bool cycleOutputFlag;     // 送信サイクル動作実行フラグ
float temperature = 0.0;  // 温度データ格納用
float humidity = 0.0;     // 湿度データ格納用

/************************* 温湿度取得関数 ************************/
void getEnv() {
  // 変数宣言
  uint8_t data[6];  // 温度、湿度受信データ格納用

  // I2Cデータ送信
  Wire.beginTransmission(SHT3X_ADDR); // デバイスアドレスを指定して I2C通信開始
  Wire.write(0xFD);           // 要求コマンド送信(高精度測定を指定)
  Wire.endTransmission(true); // 書き込み完了
  delay(10);                  // データ受信待ち(時間はデータシートによる:0.01s)

  // I2Cデータ受信(温度、湿度それぞれ 測定データ:8bit ×2 + CRC:8bit で合計6byte)
  Wire.requestFrom(SHT3X_ADDR, 6); // 受信データリクエスト(6byte)
  for (int i = 0; i < 6; i++) {    // 6byte分繰り返し
    data[i] = Wire.read();         // 受信データ格納
  }
  // 受信データを温度、湿度に換算
  int t_ticks = (data[0] << 8) | data[1];  // 温度計算用
  int rh_ticks = (data[3] << 8) | data[4]; // 湿度計算用
  temperature = -45 + 175 * ((float)t_ticks / 65535); // 温度換算
  humidity = constrain(-6 + 125 * ((float)rh_ticks / 65535), 0, 100); // 湿度換算
}

// 初期設定 -----------------------------------------------
void setup() {
  auto cfg = M5.config();  // 本体初期設定
  StickCP2.begin(cfg);
  Serial.begin(9600); // シリアル通信初期化
  Wire.begin(32, 33); // I2C通信端子指定(SDA, SCL)

  // Bluetooth通信初期化
  SerialBT.begin(DEVICE_NAME); // 接続名を指定して初期化。第2引数にtrueを書くとマスター、省略かfalseでスレーブ
  SerialBT.begin(9600);        // 通信速度(ボーレート)

  // 出力設定
  pinMode(LED_PIN, OUTPUT);    //本体LED赤
  digitalWrite(LED_PIN, LOW); //本体LED初期値OFF(LOW)

  // ベース画面の初期設定
  M5.Lcd.init();            // 初期化
  M5.Lcd.fillScreen(BLACK); // 背景色
  M5.Lcd.setRotation(0);    // 画面向き設定(USB位置基準 0:下/ 1:右/ 2:上/ 3:左)
  M5.Lcd.setTextSize(1);    // 文字サイズ(整数倍率)

  canvas.setTextWrap(false);  // 改行をしない(画面をはみ出す時自動改行する場合はtrue。書かないとtrue)
  canvas.createSprite(M5.Lcd.width(), M5.Lcd.height()); // canvasサイズ(メモリ描画領域)設定(画面サイズに設定)

  // LCD表示設定
  canvas.setTextFont(2);
  canvas.println(DEVICE_NAME); // 接続名表示

  // MACアドレスの取得と表示
  uint8_t macBT[6];
  esp_read_mac(macBT, ESP_MAC_BT);  // MACアドレス取得
  canvas.printf("%02X:%02X:%02X:%02X:%02X:%02X\r\n", macBT[0], macBT[1], macBT[2], macBT[3], macBT[4], macBT[5]);
}

// メイン -------------------------------------------------
void loop() {
  M5.update();  // 本体のボタン状態更新

  // 送信サイクル動作処理
  unsigned long currentTime = millis(); // 現在の経過時間を取得
  if (currentTime - beforTime >= CYCLE_TIME) {  // 前回の時間から送信サイクル時間経過していたら
    if (cycleOutputFlag == true) {  // 送信サイクル動作実行フラグON
      getEnv();               // 温湿度取得関数呼び出し
      Serial.printf("%.1f,%.1f\n", temperature, humidity);
      SerialBT.printf("%.1f,%.1f\n", temperature, humidity);
    }
    beforTime = millis(); //送信サイクルカウント用の前回時間を初期化
  }

  // ボタン操作処理(マスター側へデータ送信)
  if (M5.BtnA.wasPressed()) {   // ボタンAが押されていれば
    SerialBT.print("A ON!\n");  // 「A ON!」送信(「CR」区切り文字)
    cycleOutputFlag = !cycleOutputFlag; // 送信サイクル動作実行フラグ反転
  }
  if (M5.BtnB.wasPressed()) {   // ボタンBが押されていれば
    SerialBT.print("B ON!\n");  // 「B ON!」送信(「CR」区切り文字)
  }

  // Bluetooth通信データ受信処理
  if (SerialBT.available()) {               // Bluetoothデータ受信で
    data = SerialBT.readStringUntil('\r');  // 区切り文字「CR」の手前までデータ取得
    canvas.print("B: ");                    // Bluetoothデータ液晶表示
    canvas.println(data);                   // 液晶表示
    Serial.print(data);                     // シリアル出力
  }
  // シリアル通信データ受信処理
  if (Serial.available()) {               // シリアルデータ受信で
    data = Serial.readStringUntil('\r');  // 「CR」の手前までデータ取得
    canvas.print("S: ");                  // シリアルデータ液晶表示
    canvas.println(data);                 // 液晶表示
    Serial.print(data);                   // シリアル出力
    SerialBT.print(data);                 // Bluetooth出力(区切り文字「CR」をつけてマスター側へ送信)
  }

  // 受信コマンドの処理
  if (data == "LED ON") {        // 受信データが「A ON!」なら
    digitalWrite(LED_PIN, HIGH); // 本体LED点灯
  }
  if (data == "LED OFF") {       // 受信データが「B ON!」なら
    digitalWrite(LED_PIN, LOW);  // 本体LED消灯
  }

  // メモリ描画領域を座標を指定して一括表示(スプライト)
  canvas.pushSprite(&M5.Lcd, 0, 0); // (0, 0)座標に一括表示実行
}
最近ArduinoIDEで「M5StickC Plus2」の書き込みがエラーになります。
VSCode上で動作する「PlatformIO」なら書き込めるので、同じ症状の場合はお試しください。

「PlatformIO」のインストール方法は以下のリンクで詳しく紹介しています。

PlatformIO のダウンロードからインストールの紹介。Arduino IDEより速い!高性能!
Platform IOとは VSCode(エディタ)で動作する、IDEと呼ばれる統合開発環境です。 Arduino IDEでも開発できますが、見やすさ、編集のしやすさ、書込みスピード、どれをとってもPlatform IOの方がおすすめです
M5StickC Plus2
共立電子産業株式会社 KYOHRITSU ELECTRONIC INDUSTRY CO.,LTD.

・ArduinoIDEのシリアルモニタで動作確認

自作のシリアルモニタで動作確認する前に、デバイス側の「M5StickC Plus」が正しく動作するかどうかを「ArduinoIDE」のシリアルモニタで動作確認しておきましょう。

下画像のように「ArduinoIDE」の開発画面右上の[シリアルモニタアイコン]をクリックすると画面下にシリアルモニタが表示されます。

ArduinoIDEシリアルモニタで動作確認

送信データの末尾につけるデリミタは「CRのみ」を選択、通信速度は「9600 baud」に設定してください。

次に通信ポートの確認を行います。
下画像のように[ツール]→[ポート:”COM12″(COM番号は環境によります)]をクリックすると、選択可能なCOMポートのリストが表示されます。

ArduinoIDEシリアルモニタで動作確認

私の環境では「COM12」が有線接続ポートで「COM11」がBluetooth通信ポートです。

有線接続ポートは「M5StickC Plus」のUSBケーブルを抜き差しすると、消えたり表示されたりするCOM番号です。
Bluetooth通信ポートはデバイスの電源がOFFしていても表示されています。
複数表示されている場合は順番に選択して、正しく送受信できるポートを探してみてください。

次にデータ送信の確認を行います。

下画像のように送信テキストエリアに「LED ON」と入力して「Enterキー」を押すと、「M5StickC Plus」本体LEDが点灯し、「LED OFF」と入力して「Enterキー」を押すと、「M5StickC Plus」本体LEDが消灯します。

ArduinoIDEシリアルモニタで動作確認
Bluetoothシリアル通信動作確認

「LED ON」を受信で本体LED点灯

Bluetoothシリアル通信動作確認

「LED OFF」を受信で本体LED消灯

これを有線接続ポートのCOM番号と、Bluetooth通信ポートのCOM番号それぞれで確認します。
通信に問題がなければ「M5StickC Plus」の液晶画面には受信データに応じて下画像のように表示されます。

ArduinoIDEシリアルモニタで動作確認
有線接続ポートでの受信データは「S:〜」、Bluetooth通信ポートでの受信データは「B:〜」で表示されます。

最後に受信データの確認を行います。
「M5StickC Plus」本体正面のボタンAを押すと、下画像のように温度、湿度のデータが送信され、もう一度ボタンAを押すとデータの受信は終了します。

ArduinoIDEシリアルモニタで動作確認

以上の動作が確認できたらデバイスの準備は問題ありません。
引き続き自作シリアルモニタアプリでの動作確認をしてみましょう。

スポンサーリンク

3.Pythonアプリ(シリアルモニタ)の製作

シリアルモニタアプリの製作には「python」の「tkinter」で操作画面を作成し、「pyserial」でシリアル通信機能を実装します。

私は「python」をあまり使わず苦手というか・・・生成AI任せで、1から自分で書くことはできないので、今回も生成AIで作成しています^^;

・生成AIでアプリ製作

主な生成AIアプリのリンクは以下になります。
一回で動くものができるとは限らないので、何度か試してみましょう。

各生成AIの使用にはアカウントの作成が必要になります。

ChatGPT(OpenAI)

Just a moment...

Gemini(Google)

‎Gemini と話してアイデアを広げよう
Bard が Gemini になりました。Google AI で文章やリストの作成、計画の立案、新しい知識の習得など、さまざまなことができます。

Copilot(Microsoft)

Microsoft Copilot: あなたの AI アシスタントです
Microsoft Copilot は、情報、娯楽、アイデアを提供するあなたのアシスタントです。アドバイス、フィードバック、簡単な回答を得ることができます。今すぐ Copilot をお試しください。
生成された「python」コードをエディタに貼り付けてファイル名を「〜.py(〜部は任意でlogikara_serial_monitor.pyのように指定)」で保存して実行してください。

・プロンプトの紹介

プロンプトの書き方はまだよくわかってないので箇条書きです^^;

アプリのボタンやテキストボックスの配置は「grid」で、行や列を細かく指定した方が間違いないですし、幅は「画面いっぱい」とだけ書いてもなかなか思い通りにできてこないので「sticky=”ew”」で指定しています。

日本語だけで生成する方法もあるのでしょうが、ある程度プログラムも理解して直接コードで指定した方が効率よく、安定して生成できるように感じました。

作成したプロンプトは以下になります。
コピペでChatGPTやCopilotのチャット画面に貼り付けて生成してください。
コピーは下の黒塗り部右上のアイコンクリックでもできます。

pythonでtkinterとpyserialを使用して、以下の要件を考慮したシリアルモニターのコードを書いてください。


①GUI画面は以下のように作成してください。
・画面サイズは600x350でgridで配置
・通信可能なポートを取得してリストから選択、ラベルは「通信ポート」0行目0列目に配置、ドロップダウンリストはsticky="ew"
で列幅いっぱいに0行1列目に配置
・通信速度をリストから選択、初期値は9600bps、ラベルは「通信速度」、1行目0列目に配置、ドロップダウンリストはsticky="ew"
で列幅いっぱいに1行1列目に配置
・送信データの末尾にCRかLFをつけることをチェックボックスで選択、CR用は0行目2列目に配置、LF用は1行目2列目に配置
・送信データの入力用テキストエリアを1行でcolumnspan=3, sticky="ew"で画面いっぱいの幅で設置
・受信データの出力用テキストエリアを15行でcolumnspan=3, sticky="ew"画面いっぱいの幅で設置
・次の1〜4のように操作用ボタンを設置
 1.通信ポートと接続を開始する「接続」ボタン、最終行0列目に配置
 2.入力用テキストエリアのデータ「送信」ボタン、最終行1列目に配置
 3.出力用テキストエリアの内容を消去するデータ「削除」ボタン、最終行2列目に配置
 4.各ボタンの幅はwidth=10


②動作は以下のように作成してください。
・通信ポートと通信速度を設定後「接続」ボタンで通信ポートとの接続開始
・入力用テキストエリアに入力したデータは「送信」ボタンを押すかEnterキーで送信
・受信データは常に受信できるように待機し、受信次第そのまま出力用テキストエリアに表示
・出力用テキストエリアには、常に最新のデータが表示されるように、Y軸のスクロールバーを追加
・通信ポートや通信速度を変更して再接続するときには、既に開いているポートは閉じる。
・アプリ終了時には受信を停止し、通信ポートを閉じる。

③さらに以下のように受信データをCSVデータとして記録する機能を追加してください
・保存するCSVファイル名にはタイムスタンプを使用してyymmdd_hhmmss.csvとする
・受信するデータはデリミタとして末尾に「\n」付
・受信データをデリミタ「\n」ごとに改行して、先頭データにタイムスタンプ(hh:mm:ss)を追加してCSVデータとしてファイルに保存
・受信データに複数のカンマが含まれる場合はそれぞれカンマで区切って保存
・文字は文字として数値は数値として保存
・アプリ終了時には記録も停止し適切な例外処理を行なってから修了する。
・次の1,2の操作用ボタンを追加
 1.CSVデータへの記録を開始する「記録」ボタンを①の最終行の下0列目に追加
 2.CSVデータへの記録を停止する「停止」ボタンを①の最終行の下1列目に追加

・完成したアプリ(サンプルプログラム)紹介

実際に生成すると、①、②だけでシリアルモニタのみの生成はほぼ使用可能なものを生成してくれましたが、③の記録機能を追加すると、受信データの表示やタイムスタンプの追加がうまくいきませんでした。

プロンプトの指定が悪いのでしょうが・・・ほぼ動くものができれば、コードを修正したほうが早いと感じたので微調整してそれなりに動くものを以下で公開しておきます。
以下のサンプルプログラムは、とりあえず動くというものです。今回の目的はアプリ製作ではないので、例外処理等が不十分で思わぬエラーが発生するかもしれません。
コードの内容をご確認いただき、自己責任でのご使用をお願いします。

サンプルプログラムは以下になります。
コピペでエディタ等に貼り付けてpythonファイル「〜.py(〜部は任意でlogikra_serial_monitor.pyのように指定)」として保存して、実行してください。
コピーは下の黒塗り部右上のアイコンクリックでもできます。

import tkinter as tk
from tkinter import messagebox, StringVar, Text, END, Scrollbar, VERTICAL
import serial
import serial.tools.list_ports
import threading
import csv
import datetime
import queue

class SerialMonitorApp:
    def __init__(self, master):
        self.master = master
        self.master.title("ロジカラ シリアルモニタ")

        # ポートの選択
        self.port_var = StringVar()
        self.baudrate_var = StringVar(value="9600")
        
        self.serial_port = None
        self.recording = False
        self.file_name = None
        self.data_queue = queue.Queue()
        self.keep_receiving = True  # データ受信を続けるフラグ
        
        self.create_widgets()
        self.get_ports()

        # GUIが閉じられる時の処理
        self.master.protocol("WM_DELETE_WINDOW", self.on_closing)
        self.check_data()

    def create_widgets(self):
        # 列の重みを設定
        self.master.grid_columnconfigure(0, weight=1)
        self.master.grid_columnconfigure(1, weight=1)
        self.master.grid_columnconfigure(2, weight=1)

        tk.Label(self.master, text="ポート選択:").grid(row=0, column=0)  # 通信ポート選択リスト
        self.port_menu = tk.OptionMenu(self.master, self.port_var, [])
        self.port_menu.grid(row=0, column=1, sticky="ew")  # 'ew' 指定で横方向に広がる

        tk.Label(self.master, text="通信速度:").grid(row=1, column=0)  # 通信速度選択リスト
        self.baudrate_menu = tk.OptionMenu(self.master, self.baudrate_var, "4800", "9600", "14400", "19200", "38400", "57600", "115200")
        self.baudrate_menu.grid(row=1, column=1, sticky="ew")

        self.check_var_cr = tk.BooleanVar()  # CR追加チェックボックス
        tk.Checkbutton(self.master, text="Add CR", variable=self.check_var_cr).grid(row=0, column=2)

        self.check_var_lf = tk.BooleanVar()  # LF追加チェックボックス
        tk.Checkbutton(self.master, text="Add LF", variable=self.check_var_lf).grid(row=1, column=2)
        
        self.send_text_area = tk.Entry(self.master)  # 送信用テキストエリア
        self.send_text_area.grid(row=2, column=0, columnspan=3, sticky="ew")
        self.send_text_area.bind("<Return>", self.send_data)  # リスナーをセットアップ(Enterキーで送信)
        
        self.text_area = Text(self.master, height=15)  # 受信データ表示用テキストエリア
        self.text_area.grid(row=3, column=0, columnspan=3, sticky="ew")
        
        self.scrollbar = Scrollbar(self.master, command=self.text_area.yview, orient=VERTICAL)  # スクロールバーを追加
        self.scrollbar.grid(row=3, column=3, sticky='ns')
        self.text_area['yscrollcommand'] = self.scrollbar.set

        # 操作ボタン
        tk.Button(self.master, text="接続", command=self.connect_serial, width=10).grid(row=5, column=0)
        tk.Button(self.master, text="送信", command=self.send_data, width=10).grid(row=5, column=1)
        tk.Button(self.master, text="受信データ削除", command=self.clear_received_data, width=10).grid(row=5, column=2)
        tk.Button(self.master, text="記録", command=self.start_recording, width=10).grid(row=6, column=0)
        tk.Button(self.master, text="停止", command=self.stop_recording, width=10).grid(row=6, column=1)

    def get_ports(self):
        ports = serial.tools.list_ports.comports()
        for port in ports:
            self.port_menu['menu'].add_command(label=port.device, command=lambda value=port.device: self.port_var.set(value))

    def connect_serial(self):
        if not self.port_var.get() or not self.baudrate_var.get():
            messagebox.showwarning("Warning", "Please select a port and baud rate.")
            return
        
        # 既存のポートが開いている場合、受信を停止し、クローズします
        if self.serial_port and self.serial_port.is_open:
            self.keep_receiving = False  # データ受信フラグをオフにして受信スレッドを終了させる
            self.serial_port.close()
            self.text_area.insert(END, "Closed previous serial port.\n")

        try:
            self.serial_port = serial.Serial(self.port_var.get(), int(self.baudrate_var.get()), timeout=1)
            self.text_area.insert(END, f"Connected to {self.port_var.get()} at {self.baudrate_var.get()} baud\n")
            self.keep_receiving = True  # データ受信を続けるフラグを立てる
            threading.Thread(target=self.receive_data, daemon=True).start()
        except Exception as e:
            messagebox.showerror("Error", str(e))

    def send_data(self, event=None):
        data_to_send = self.send_text_area.get().strip()  # 1行のテキストを取得

        if self.check_var_cr.get():
            data_to_send += '\r'
        if self.check_var_lf.get():
            data_to_send += '\n'

        if self.serial_port and self.serial_port.is_open:
            self.serial_port.write(data_to_send.encode())
            self.send_text_area.delete(0, END)  # 送信後にテキストエリアをクリア
        else:
            messagebox.showwarning("Warning", "Please connect to the serial port first.")

    # データ受信
    def receive_data(self):
        while self.keep_receiving:  # 受信フラグがオンの間ループ
            if self.serial_port.is_open:  # ポートが開いていることを確認
                try:
                    if self.serial_port.in_waiting > 0:  # 受信待機中のデータがあれば処理を実行
                        data = self.serial_port.read(self.serial_port.in_waiting)  # 受信データ読み取り
                        # データをキューに追加
                        self.data_queue.put(data.decode('utf-8', errors='ignore'))  # バイナリデータをUTF-8でデコード
                except Exception as e:
                    self.keep_receiving = False  # エラーが発生したら受信フラグをオフしてループを停止
                    self.text_area.insert(END, f"Error in receiving data: {str(e)}\n")  # エラーメッセージをテキストエリアに表示

    # 受信データ確認、出力
    def check_data(self):
        try:
            received_data_buffer = ""  # データを保存するバッファ
            while True:
                received_data = self.data_queue.get_nowait()  # キューからデータを取得
                if '\n' in received_data:  # データが'\n'であるかどうかを確認
                    received_data_buffer += received_data  # バッファにデータを追加
                    lines = received_data_buffer.split('\n')  # バッファ内のデータを行単位で分割
                    if lines[-1] == '':  # 最後の要素が空なら除外
                        lines.pop()
                    # 各行をCSVに書き込み
                    if self.recording:
                        with open(self.file_name, mode='a', newline='') as f:
                            writer = csv.writer(f)
                            for line in lines:
                                values = line.split(',')  # 行をカンマで分割
                                if len(values) >= 2:  # 少なくとも2つの値が必要
                                    timestamp = datetime.datetime.now().strftime('%H:%M:%S')
                                    writer.writerow([timestamp] + values)  # タイムスタンプと値を結合して書き込む
                                    self.text_area.insert(END, f"{timestamp},{','.join(values)}\n")               
                    else:
                        for line in lines:
                            self.text_area.insert(END, f"{line}\n")
                    received_data_buffer = ""  # バッファをクリア
                else:
                    received_data_buffer += received_data  # データが完全でない場合はバッファに追加
                self.text_area.see(END)
        except queue.Empty:
            pass
        finally:
            self.master.after(100, self.check_data)

    def clear_received_data(self):
        self.text_area.delete(1.0, END)

    def start_recording(self):
        self.recording = True
        timestamp = datetime.datetime.now().strftime('%y%m%d_%H%M%S')
        self.file_name = f"{timestamp}.csv"
        with open(self.file_name, mode='w', newline='') as f:
            writer = csv.writer(f)
            writer.writerow(["Timestamp", "Received Data"])
        self.text_area.insert(END, f"Recording started: {self.file_name}\n")

    def stop_recording(self):
        self.recording = False
        self.text_area.insert(END, "Recording stopped.\n")

    def on_closing(self):
        self.keep_receiving = False  # データ受信を停止
        if self.serial_port and self.serial_port.is_open:
            self.serial_port.close()  # シリアルポートを閉じる
        self.master.destroy()  # アプリケーションを終了する

if __name__ == "__main__":
    root = tk.Tk()
    app = SerialMonitorApp(root)
    root.mainloop()

4.まとめ

Bluetooth通信デバイスで測定したデータをパソコンに送信して記録したり、パソコンからデバイスを遠隔操作する方法を詳しく紹介しました。

Bluetooth通信と言っても送受信データは有線のシリアル通信と同じため、データの送受信にはシリアルモニタに記録機能を追加したものを自作して使用しました。

シリアルモニタは「Python」の「tkinter」で操作画面を作成し、「pyserial」でシリアルデータの送受信機能を実装しました。

受信して記録したデータは「csvデータ」として記録できるため、「Excel」で開いて、グラフ表示したり、データ分析に使用することができます。

「Python」アプリは「生成AI」を使用して日本語で作成しました。プロンプトはまだまだ不十分ですが書き方を工夫すれば日本語だけでもそれなりに動作するプログラムは作成できそうです。

使用する「生成AI」は「ChatGPT」や「Copilot」、「Gemini」等いろいろありますが、どれを使っても1回で完璧に動作できるものができるとは限らないので、日本語でのプロンプトを調整しながら何度か生成して動作確認を行う必要があると思います。

日本語だけで希望の動作は実現できるかもしれませんが、結果は安定しないように思います。
ある程度プログラムも理解して直接コードで指定する等、プロンプトを書くにもプログラミングの知識は必要だと感じました。

「生成AI」の登場で「python」をあまり使わない私でも、それなりに動作するアプリが作成できました。

マイコンデバイスのプログラムは情報量が少なく、機種ごとの癖があり中々うまくいかないですが、パソコン上で動作するアプリは情報量も多いせいか「生成AI」だけでもそれなりに動くものを作ってくれるので、今後も活用して便利なものができたら、公開していきたいと思います。

M5StickC Plusの使い方、初期設定、サンプルプログラム、M5StickCとの違い等を詳しく紹介
M5StickC PlusをArduino IDEやPlatformIOで使うための初期設定やサンプルプログラムでの動作確認の方法です。ビジュアルプログラミングのUiFlowの初期設定についても紹介します。

コメント

タイトルとURLをコピーしました