Bluetooth通信を使用して、デバイスで測定したデータをパソコンに送信して記録したり、パソコンからデバイスを遠隔操作する方法を、Pythonで簡単に自作できるシリアルモニタを使用して詳しく紹介します。
Bluetooth通信デバイスには「M5StickC Plus」を、開発環境には「ArduinoIDE」を使用します。
パソコンは「Python」がインストールされていれば「Windows」でも「Mac」でも大丈夫です。
「ArduinoIDE」のシリアルモニタでもデータの送受信は確認できますが、データの記録(CSVファイルに保存)機能はないため、データ記録機能を持ったシリアルモニタアプリを「Python」で自作して動作確認します。
1.動作紹介
・Bluetooth通信デバイスとのペアリング
・自作シリアルモニタとの接続、使い方
・受信データの記録(CSVデータ)
2.Bluetooth通信デバイスの準備
・M5StickC Plusとは
・開発環境(ArduinoIDE)、ライブラリの準備
・Bluetooth通信の使い方
・温湿度センサ「ENV.Ⅳ」の使い方
・サンプルプログラム(M5StickC Plus/Plus2)
・ArduinoIDEのシリアルモニタで動作確認
3.Pythonアプリ(シリアルモニタ)の製作
・生成AIでアプリ製作
・プロンプトの紹介
・完成したアプリ(サンプルプログラム)紹介
4.まとめ
1.動作紹介
まずは今回、どんなものを作って、どんなことができるのかを、実際に動作確認しながら紹介します。
・Bluetooth通信デバイスとのペアリング
Bluetooth通信デバイスには「M5StickC Plus」を使用します。
「M5StickC Plus」にサンプルプログラムを書き込んで実行すると液晶画面に以下のように表示され、BluetoothのMACアドレスが表示されます。
送受信データの動作確認用に温湿度センサ「ENV.Ⅳ」を接続しています。

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

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

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

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

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

上画像のように「接続済み」という表示が確認できれば、ペアリング完了です。
・自作シリアルモニタとの接続、使い方
デバイスのペアリングが完了したら、自作のシリアルモニタを「python」で実行して起動します。pythonのインストール方法は以下のリンクで詳しく紹介しています。

「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」へデータが送信されます。

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

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

同様に、液晶画面に「B:LED OFF」と表示され、本体LEDは消灯します。
次にデータの受信動作について確認します。
シリアルモニタがデータを受信すると、以下のように「M5StickC Plus」で測定した温度と湿度のデータが1秒ごとに表示されます。

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

「M5StickC Plus」本体右側面の「Bボタン」を押すと「シリアルモニタ」には「B ON!」と表示されます。
こちらは動作確認用のため、必要に応じて違うデータを送信する等「M5StickC Plus」側で設定して使用できます。
・受信データの記録(CSVデータ)
今回の1番やりたかったデータ記録の動作については以下のようになります。

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

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

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

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

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

2.Bluetooth通信デバイスの準備
ここからはデータを送信するBluetooth通信デバイスの「M5StickC Plus」の製作方法を詳しく紹介します。
・M5StickC Plusとは
「M5StickC Plus」とは「M5Stackテクノロジー社」のマイコンボードで、1.14インチTFT液晶画面や入出力端子、操作ボタン、LED、赤外線送信、ブザー、3軸ジャイロ+3軸加速度センサ、マイク、RTC(リアルタイムクロック)、バッテリーが内蔵されています。

他にもシリアル通信はもちろんWiFiやBluetoothにも対応していて、これらの機能をプログラムで自由に使用することができできます。
プログラムは「C言語」ベースの「Arduino IDE」「PlatformIO」や「ビジュアルプログラミング(UiFlow)」「Python(MicroPython)」で作成できます。
「M5StickC Plus」の使い方は以下のリンクで詳しく紹介しています。

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

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

・ライブラリ
使用するライブラリは以下表のようになります。
「ArduinoIDE」で事前にインストールしておいてください。
ライブラリ名 | 用途 | バージョン | 検索名 |
---|---|---|---|
M5StickCPlus | M5StickC Plus制御用 | 0.1.0 | stickcplus |
M5GFX | 液晶表示制御用 | 0.2.5 | m5gfx |
・Bluetooth通信の使い方
「M5StickC Plus」で「Bluetooth通信」を使用するには「BluetoothSerial」ライブラリを使用します。「BluetoothSerial」ライブラリは個別にインストールする必要はありません。
「BluetoothSerial」ライブラリの使用方法は以下のリンクで詳しく紹介しています。

・温湿度センサ「ENV.Ⅳ」の使い方
「M5StickC Plus」には温湿度センサーを接続してシリアルモニタへの送信データとして使用します。
「ENV.Ⅳ」には専用のライブラリもありますが、ライブラリを使用しなくても「I2C通信」で比較的簡単にデータの取得ができるため、シンプルに直接データを取得しています。
「M5StickC Plus」との接続は「Groveコネクタ配線」で下画像のように接続するだけです。

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

・サンプルプログラム(M5StickC Plus/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)座標に一括表示実行
}
「PlatformIO」のインストール方法は以下のリンクで詳しく紹介しています。

・ArduinoIDEのシリアルモニタで動作確認
自作のシリアルモニタで動作確認する前に、デバイス側の「M5StickC Plus」が正しく動作するかどうかを「ArduinoIDE」のシリアルモニタで動作確認しておきましょう。
下画像のように「ArduinoIDE」の開発画面右上の[シリアルモニタアイコン]をクリックすると画面下にシリアルモニタが表示されます。

送信データの末尾につけるデリミタは「CRのみ」を選択、通信速度は「9600 baud」に設定してください。
次に通信ポートの確認を行います。
下画像のように[ツール]→[ポート:”COM12″(COM番号は環境によります)]をクリックすると、選択可能なCOMポートのリストが表示されます。

私の環境では「COM12」が有線接続ポートで「COM11」がBluetooth通信ポートです。
次にデータ送信の確認を行います。
下画像のように送信テキストエリアに「LED ON」と入力して「Enterキー」を押すと、「M5StickC Plus」本体LEDが点灯し、「LED OFF」と入力して「Enterキー」を押すと、「M5StickC Plus」本体LEDが消灯します。


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

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

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

以上の動作が確認できたらデバイスの準備は問題ありません。
引き続き自作シリアルモニタアプリでの動作確認をしてみましょう。
3.Pythonアプリ(シリアルモニタ)の製作
シリアルモニタアプリの製作には「python」の「tkinter」で操作画面を作成し、「pyserial」でシリアル通信機能を実装します。
・生成AIでアプリ製作
主な生成AIアプリのリンクは以下になります。
一回で動くものができるとは限らないので、何度か試してみましょう。
ChatGPT(OpenAI)
Gemini(Google)

Copilot(Microsoft)

・プロンプトの紹介
プロンプトの書き方はまだよくわかってないので箇条書きです^^;
アプリのボタンやテキストボックスの配置は「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」だけでもそれなりに動くものを作ってくれるので、今後も活用して便利なものができたら、公開していきたいと思います。

コメント