MIT App InventorでArduinoをBluetooth操作:HC-05用のAndroidアプリを作る
MIT App InventorでArduinoをBluetooth操作します。HC-05の配線、ListPicker、ボタンとスライダーのブロック、LED、リレー、サーボ、センサーデータ、APK、Android 12の対策まで解説します。
App InventorでArduinoをBluetooth操作するのは、JavaやKotlinを1行も書かずに自分のAndroidアプリを作る最も手早い方法です。MITが提供する無料のブラウザベースのMIT App Inventorでは、ボタン、スライダー、テキストラベルを画面にドラッグし、パズルのピースのようにはまるブロックでそれぞれの動作を決めます。スマホはArduinoにつないだHC-05 Bluetoothモジュールに接続します。アプリでのタップはすべて短いテキストのコマンドとしてArduinoに届き、Arduinoも同じ方法でセンサーの値をスマホに送り返します。
このガイドを終えるころには、実際に動くリモコンアプリができあがります。リストからHC-05に接続するListPicker、LEDとリレーをオン・オフするボタン、LEDの明るさとサーボの角度を決める2つのスライダー、そしてDHT11の温度と湿度をリアルタイムに表示するラベルです。Arduinoのコード、App Inventorのすべてのブロック、APKのビルド、Android 12で生じたBluetoothの権限の問題の対策を、ステップごとに見ていきます。HC-05を使ったことがなければ、先にHC-05 Bluetoothモジュールのガイドにざっと目を通してください。

App Inventorとは?Arduinoとどう通信する?
MIT App Inventorは、マサチューセッツ工科大学(MIT)が作った無料のアプリ開発環境です。Googleアカウントでログインし、ブラウザで使います。画面は2つあります。Designer(デザイナー)ではコンポーネントをドラッグしてアプリの画面を組み立て、Blocks(ブロック)エディターでは「ボタンがクリックされたらこうする」といったルールを色とりどりのブロックで書きます。インストールせずにアプリを試すには、PlayストアのMIT AI2 Companionアプリを使います。完成したら、1クリックでインストール可能なAPKファイルができます。
Bluetoothの部分はBluetoothClientコンポーネントが担当します。これはクラシックBluetoothのSPP(シリアルポートプロファイル)を使い、HC-05が話すのもまさにこれです。その結果、スマホとArduinoの間に目に見えないシリアルケーブルができます。アプリがSendTextブロックで「L1」を送ると、Arduinoはその2文字をbt.read()で読みます。シリアルモニタに打ち込んだのと同じです。Arduinoがbt.print()で送ったものは、すべてスマホのReceiveTextブロックに届きます。Arduino側にBluetooth専用のライブラリは必要ありません。
| 層 | 役割 | このプロジェクトでは |
|---|---|---|
| インターフェース(Designer) | 画面に表示するコンポーネント | ListPicker、Button 5個、Slider 2個、Label |
| ロジック(Blocks) | イベントに反応するブロック | Click、PositionChanged、Timerのイベント |
| BluetoothClient | SPP接続、データの送受信 | Connect、SendText、ReceiveText |
| HC-05 | BluetoothとUARTの橋渡し | 9600ボー、PIN 1234 |
| Arduino | コマンドを解釈し、出力を動かし、センサーを読む | LED、リレー、サーボ、DHT11 |
必要なもの
| 部品 | 数量 | 備考 |
|---|---|---|
| Arduino Uno(またはNano) | 1 | どちらも同じコードとピン |
| HC-05またはHC-06 Bluetoothモジュール | 1 | クラシックBluetooth。HM-10(BLE)はこの方法では使えない |
| 1 kΩと2 kΩの抵抗 | 各1 | HC-05のRXDピン用の分圧回路(2.2 kΩでも可) |
| LEDと220 Ωの抵抗 | 各1 | 明るさを変えられるLED(D5、PWM) |
| 5 Vリレーモジュール | 1 | ランプやファンなどの負荷を切り替える |
| SG90サーボモーター | 1 | スライダーで0〜180°を制御 |
| DHT11温湿度センサー | 1 | スマホに表示するデータ |
| Androidスマホ | 1 | HC-05はiPhoneでは使えない |
| ブレッドボードとジャンパーワイヤー | — | 共通GNDは必須 |





配線:HC-05、LED、リレー、サーボ、DHT11
HC-05の基板にはレギュレーターがあるのでVCCピンには5 Vを入れられますが、データピンは3.3 Vロジックです。モジュールのTXDピンからの3.3 Vの信号はArduinoで問題なく読めます。しかしArduinoの5 VのTX信号は、分圧回路を通してRXDピンに届けるべきです。D11 → 1 kΩ → RXD、RXD → 2 kΩ → GNDとつなぎます。これでRXDには5 × 2 / 3 ≈ 3.3 Vがかかります。HC-05はUnoのハードウェアシリアルのピン(0と1)ではなく、SoftwareSerialでD10とD11につなぎます。こうすればモジュールをつないだままコードを書き込め、デバッグ用にシリアルモニタも使えます。LEDの抵抗はLED抵抗計算ツールで確認できます。
| 接続 | Arduino Uno | ESP32 | Raspberry Pi Pico |
|---|---|---|---|
| HC-05 VCC | 5V | 不要(Bluetooth内蔵) | VBUS(5 V) |
| HC-05 GND | GND | — | GND |
| HC-05 TXD | D10(SoftwareSerialのRX) | — | GP1(UART0のRX) |
| HC-05 RXD | D11、1 kΩ / 2 kΩの分圧回路を通す | — | GP0(UART0のTX)、分圧回路なし |
| LED(+ 220 Ω) | D5(PWM) | GPIO 2(オンボードLED) | GP15 |
| リレーモジュールのIN | D7 | GPIO 26 | GP14 |
| サーボの信号(オレンジ) | D9 | —(ESP32Servoで追加可能) | GP13 |
| DHT11のデータ | D2 | GPIO 4 | GP16 |
通信プロトコル:スマホは何を送る?
コードを書く前に、スマホとArduinoが話す「言葉」を決めます。このプロジェクトでは、どのコマンドも文字、続いて数字、最後に改行文字(\n、ASCII 10)でできています。改行はとても重要です。Bluetoothのデータは分割されて届くことがあり、「S1」と「35」が別々に届いても、Arduinoは改行を待ってから「S135」を1つのコマンドとして処理します。コマンドは読めるテキストなので、どんなBluetoothターミナルアプリでも手で試せます。
| コマンド | 方向 | 意味 |
|---|---|---|
| L1 / L0 | スマホ → Arduino | LEDを全点灯 / 消灯 |
| P0 … P255 | スマホ → Arduino | LEDの明るさ(analogWriteの値) |
| R1 / R0 | スマホ → Arduino | リレーをオン / オフ |
| S0 … S180 | スマホ → Arduino | サーボの角度(度) |
| 24.5;41 | Arduino → スマホ | 温度(°C)と湿度(%)、セミコロンで区切る |
App Inventorはテキストの代わりに、Send1ByteNumberブロックで1バイト(0〜255)を送ることもでき、Arduinoはそれを直接数値として読めます。短くて速いのですが、スライダー1つだけの場合にしか向きません。スライダー2つとボタン4つがすべてバイトを送ると、届いた90がサーボの角度なのか明るさなのかArduinoには判別できないからです。文字 + 数字 + 改行の形式は数バイト多くかかりますが、9600ボーなら1秒に約960文字を送れるので、違いに気づくことはありません。
Arduinoのコード
ServoライブラリとSoftwareSerialライブラリはArduino IDEに付属しています。DHT11用には、ライブラリマネージャからDHT sensor library(Adafruit)をインストールします。依存ライブラリについて聞かれたら「全てをインストール」を選び、Adafruit Unified Sensorも追加します。0番と1番ピンを使わないので、書き込みの際にHC-05を外す必要はありません。
1#include <SoftwareSerial.h>
2#include <Servo.h>
3#include <DHT.h>
4
5SoftwareSerial bt(10, 11); // RX, TX → HC-05 TXD, RXD (bölücü üzerinden / via divider)
6Servo servo;
7DHT dht(2, DHT11); // DHT11 veri pini D2 / data pin D2
8
9const int LED_PIN = 5; // PWM destekli pin / PWM-capable pin
10const int ROLE_PIN = 7;
11const int SERVO_PIN = 9;
12const int ROLE_ACIK = LOW; // çoğu röle modülü LOW ile çeker / most relay modules are active LOW
13const int ROLE_KAPALI = HIGH;
14
15String komut = ""; // gelen satırın tamponu / buffer for the incoming line
16unsigned long sonGonderim = 0;
17
18void komutIsle(String k) {
19 k.trim(); // boşluk ve \r temizle / strip spaces and \r
20 if (k.length() == 0) return;
21 Serial.println(k); // hata ayıklama / debugging
22 char tur = k.charAt(0); // komut harfi / command letter
23 int deger = k.substring(1).toInt(); // harften sonraki sayı / number after the letter
24
25 switch (tur) {
26 case 'L': // L1 / L0 → LED aç, kapat / LED on, off
27 analogWrite(LED_PIN, deger == 1 ? 255 : 0);
28 break;
29 case 'P': // P0..P255 → parlaklık / brightness
30 analogWrite(LED_PIN, constrain(deger, 0, 255));
31 break;
32 case 'R': // R1 / R0 → röle / relay
33 digitalWrite(ROLE_PIN, deger == 1 ? ROLE_ACIK : ROLE_KAPALI);
34 break;
35 case 'S': // S0..S180 → servo açısı / servo angle
36 servo.write(constrain(deger, 0, 180));
37 break;
38 }
39}
40
41void setup() {
42 pinMode(LED_PIN, OUTPUT);
43 pinMode(ROLE_PIN, OUTPUT);
44 digitalWrite(ROLE_PIN, ROLE_KAPALI); // açılışta röle kapalı / relay off at start
45 servo.attach(SERVO_PIN);
46 servo.write(90); // orta konum / centre position
47 dht.begin();
48 Serial.begin(9600); // Seri Monitör / Serial Monitor
49 bt.begin(9600); // HC-05 veri modu hızı / HC-05 data mode baud rate
50}
51
52void loop() {
53 // 1) Telefondan gelen komutları satır satır oku / read commands from the phone line by line
54 while (bt.available()) {
55 char c = bt.read();
56 if (c == '\n') { // satır bitti / line complete
57 komutIsle(komut);
58 komut = "";
59 } else if (komut.length() < 16) { // aşırı uzun satırı kes / cap overlong lines
60 komut += c;
61 }
62 }
63
64 // 2) 2 saniyede bir sensör verisini gönder / send sensor data every 2 seconds
65 if (millis() - sonGonderim >= 2000) {
66 sonGonderim = millis();
67 float t = dht.readTemperature();
68 float h = dht.readHumidity();
69 if (!isnan(t) && !isnan(h)) {
70 bt.print(t, 1); // örnek / example: 24.5
71 bt.print(';');
72 bt.print(h, 0); // örnek / example: 41
73 bt.print('\n'); // println değil, \r gitmesin / not println, no \r
74 }
75 }
76}- 行バッファ:loop()は届いた文字を1つずつkomut(コマンド)変数に追加し、\nが届くとその行をkomutIsle()に渡してバッファをクリアします。16文字を超えた分は捨てるので、ゴミのデータでメモリがいっぱいになることはありません。
- trim():ターミナルアプリの中には行末に\rも付けるものがあります。trim()はそれを空白と一緒に取り除くので、「L1\r」も「L1」として認識されます。
- 文字と数字:charAt(0)でコマンドの文字を、substring(1).toInt()でその後ろの数字を取り出します。constrain()は値を安全な範囲に収めます。スマホが誤って300を送っても、サーボが180°を超えて動かされることはありません。
- 処理を止めない送信:センサーはmillis()を使って2秒ごとに読みます。delay()がないので、その間に届いたコマンドも遅れずに処理されます。詳しくはmillisとdelayの違いを参照してください。
- printlnではなくprint('\n'):println()は行を\r\nで終えます。App Inventorは\nだけを区切りに使うので、\rが湿度の値の末尾に残ってしまいます。\nだけを送れば、スマホでの解析がきれいに保たれます。
- アクティブLOWのリレー:多くのリレーモジュールはINがLOWになるとオンになります。HIGHでオンになるモジュールなら、ROLE_ACIK(オン)とROLE_KAPALI(オフ)の定数の値を入れ替えるだけです。
App Inventorで画面を設計する(Designer)
- ブラウザでai2.appinventor.mit.eduを開き、Googleアカウントでログインして、Projects > Start new project(新しいプロジェクトを始める)でBlueGraysRemoteという名前のプロジェクトを作ります。
- パレットのLayout(レイアウト)グループからHorizontalArrangementをドラッグし、その中にListPicker(Text: Connect)、Button(Text: Disconnect)、Label(Text: Not connected)を入れます。
- その下にHorizontalArrangementをさらに2つ追加します。1つ目にはLED OnとLED Offのボタンを、2つ目にはRelay OnとRelay Offのボタンを入れます。ボタンのWidthをFill parentにすると押しやすくなります。
- Sliderを2つ追加します。明るさ用はMinValue 0、MaxValue 255、ThumbPosition 0、サーボ用はMinValue 0、MaxValue 180、ThumbPosition 90にします。それぞれの上に、何をするものかを書いたLabelを置きます。
- 温度と湿度用にLabelをさらに2つ追加し、FontSizeを20ほどに大きくします。
- Connectivity(接続)グループからBluetoothClientを追加し、PropertiesパネルでDelimiterByteを10にして、NoLocationNeededにチェックを入れます。
- Sensors(センサー)グループからClockを追加し、TimerIntervalを200にして、TimerEnabledのチェックを外します。タイマーは接続ができてからブロックでオンにします。
| コンポーネント | 名前(Rename) | 主な設定 |
|---|---|---|
| ListPicker | LP_Connect | Text: Connect |
| Button × 5 | BTN_Disconnect、BTN_LedOn、BTN_LedOff、BTN_RelayOn、BTN_RelayOff | Width: Fill parent |
| Slider | SLD_Brightness | MinValue 0、MaxValue 255 |
| Slider | SLD_Servo | MinValue 0、MaxValue 180、ThumbPosition 90 |
| Label | LBL_Status、LBL_Temp、LBL_Hum | FontSize 20(値の表示用) |
| BluetoothClient | BluetoothClient1 | DelimiterByte 10、NoLocationNeededにチェック |
| Clock | Clock1 | TimerInterval 200、TimerEnabledはオフ |
ListPickerでHC-05に接続する
まずスマホのBluetooth設定でHC-05とペアリングします(PINは通常1234、だめなら0000)。この手順は欠かせません。BluetoothClient1.AddressesAndNamesはペアリング済みのデバイスしか一覧に出さず、近くの新しいデバイスは検索しないからです。各項目は「98:D3:31:F5:2A:10 HC-05」のように、先頭にMACアドレス、続いてデバイス名が並ぶテキストです。このテキストをそのままConnectブロックに渡せます。App Inventorは最初の空白より後ろを自動で無視します。ブロックは接続に成功するとtrue、失敗するとfalseを返します。
- when LP_Connect.BeforePickingイベントに、set LP_Connect.Elements to BluetoothClient1.AddressesAndNamesを置きます。リストを開くたびに、ペアリング済みのデバイスで一覧が更新されます。
- when LP_Connect.AfterPickingイベントにifブロックを追加し、条件にcall BluetoothClient1.Connect address: LP_Connect.Selectionをはめ込みます。
- ifのthenの部分でLBL_Status.Textを「接続済み」にし、set Clock1.TimerEnabled to trueでデータの読み取りを始めます。elseの部分には「接続に失敗しました」と書きます。
- when BTN_Disconnect.Clickイベントでは、まずset Clock1.TimerEnabled to false、次にcall BluetoothClient1.Disconnectを呼び、最後にLBL_Status.Textを「未接続」にします。先にタイマーを止めておくと、接続を閉じたあとに読み取りが走ってエラーメッセージが出るのを防げます。
データを送る:ボタンとスライダー
送信に必要なブロックはcall BluetoothClient1.SendText textの1つだけです。テキストブロックに「L1\n」と入力すると、App Inventorは末尾の\nを改行文字に変えます。未接続のまま送るとエラー515になるので、送信はすべてif BluetoothClient1.IsConnectedの確認で囲むのがよい習慣です。スライダーの場合は、joinブロックで文字と数字と改行をつなげます。join("S", round(thumbPosition), "\n")です。
| イベント | 送るテキスト | Arduinoでの結果 |
|---|---|---|
| BTN_LedOn.Click | L1\n | LEDが最大の明るさで点く |
| BTN_LedOff.Click | L0\n | LEDが消える |
| BTN_RelayOn.Click | R1\n | リレーがカチッとオンになり、つないだランプが点く |
| BTN_RelayOff.Click | R0\n | リレーがオフになる |
| SLD_Brightness.PositionChanged | join("P", round(thumbPosition), "\n") | analogWrite(5, 値) |
| SLD_Servo.PositionChanged | join("S", round(thumbPosition), "\n") | servo.write(角度) |
指でスライダーをドラッグしている間、PositionChangedイベントは1秒に何十回も発生し、thumbPositionは87.4のような小数を返します。イベントのたびに送ると、同じ角度が何度も送られ、HC-05のバッファがいっぱいになり、サーボがカクカクと遅れて動きます。解決法は簡単です。global lastAngleという変数を作り、round(thumbPosition)で丸めた値がそれと違うときだけ送り、そのあと変数を更新します。こうすれば角度が本当に変わったときだけデータが送られます。明るさのスライダーにもglobal lastBrightnessで同じ工夫をします。
データを受け取る:センサーの値をスマホに表示する
Arduinoは2秒ごとに「24.5;41」という行を送ります。App Inventorにはデータが届いたときに自動で発生するイベントがないので、Clockコンポーネントを使って一定間隔で「新しいデータはある?」と確認します。DesignerでDelimiterByteを10にしてあるので、ReceiveTextブロックのnumberOfBytesに-1を渡すと、改行文字まで読み、\nを除いた行を返します。200 msのタイマーはデータが届く間隔(2000 msごと)よりずっと頻繁に確認するので、バッファに何もたまりません。
- when Clock1.Timerの中にif BluetoothClient1.IsConnectedの条件を置きます。
- その中に2つ目の条件call BluetoothClient1.BytesAvailableToReceive > 0を追加します。
- initialize local line to call BluetoothClient1.ReceiveText numberOfBytes: -1で、届いた行をローカル変数に入れます。
- split text line at ";"ブロックで行をリストに変えます。「24.5;41」は2項目のリスト[24.5, 41]になります。結果はpartsというローカル変数に入れます。
- if length of list parts = 2なら、LBL_Temp.Textをjoin("温度: ", select list item parts index 1, " °C")に、LBL_Hum.Textをjoin("湿度: ", select list item parts index 2, " %")にします。項目数を確認すると、途中までしか届かなかった行や壊れた行でエラーが出るのを防げます。
Android 12以降のBluetoothの権限の問題
Android 12(API 31)でBluetoothの権限が変わりました。インストール時に自動で許可されていた古いBLUETOOTH権限は、アプリの実行中にユーザーが承認しなければならないBLUETOOTH_CONNECTとBLUETOOTH_SCANに置き換えられました。スマホの設定では付近のデバイスの権限として表示されます。許可されていないと、App Inventorは最初の試行で権限のダイアログを開きますが、実行中だったブロックは止まってしまいます。その結果、よくある症状が出ます。初めてConnectをタップしたときにリストが空で開く、「Error 908: The permission ... has been denied」(権限が拒否された)というメッセージが出る(BLUETOOTH_CONNECTかBLUETOOTH_SCANの名前が入る)、あるいはユーザーが一度拒否すると二度と聞かれなくなる、といった症状です。
- when Screen1.InitializeイベントでAndroidのバージョンを確認します。select list item (split Screen1.PlatformVersion at ".") index 1 ≥ 12なら、call Screen1.AskForPermissionにPermissionリストのBluetoothConnectを渡します。これらの権限はAndroid 11以前には存在せず、そこで要求するとやはりエラー908になるので、バージョンの確認が重要です。
- when Screen1.PermissionGrantedイベントでは、許可された権限がBLUETOOTH_CONNECTなら(contains text: permissionName, piece: "CONNECT")、続けてBluetoothScanを要求します。AskForPermissionは一度に1つの権限しか受け取らないので、このようにつなげていきます。
- when Screen1.PermissionDeniedイベントでは、Notifierを使って「設定 > アプリ > BlueGraysRemote > 権限 > 付近のデバイス > 許可」という手順を示すメッセージを表示します。このイベントを処理すれば、生のエラーボックスの代わりにわかりやすい説明をユーザーに見せられます。
- DesignerでBluetoothClientのNoLocationNeededにはチェックを入れたままにします。これでアプリがユーザーの位置を知るためにBluetoothを使わないことをAndroidに伝えます。
- 必ずAPKでもテストしてください。AI2 Companionの権限はCompanionアプリのものであり、自分のAPKはインストール後に独自に権限を要求します。
| エラー | 意味 | 解決法 |
|---|---|---|
| Error 507: Unable to connect. Is the device turned on?(接続できない) | モジュールに接続できなかった | HC-05の電源、ほかのスマホに接続されていないか、ペアリングを確認する。それでもだめならBluetoothClientのSecureのチェックを外して再試行する |
| Error 515: Not connected to a Bluetooth device.(未接続) | 接続していないのに送信や読み取りをした | 送信とClockのブロックをIsConnectedの確認で囲む |
| Error 516: Unable to write: Broken pipe(書き込みできない) | 途中で接続が切れた(電源断、範囲外) | Clockを止め、Disconnectを呼んで再接続する。サーボとリレーには専用の電源を |
| Error 908: The permission … has been denied(権限が拒否された) | Android 12以降で付近のデバイスの権限がない | ブロックで要求するか、設定から手動で許可する |
| リストが空で開く | モジュールがペアリングされていない、または権限を要求した直後 | スマホの設定でHC-05とペアリングする。権限を許可したら、もう一度Connectをタップする |
アプリのテストとAPKのビルド
開発中は、変更のたびにAPKをインストールする必要はありません。PlayストアからMIT AI2 Companionアプリをインストールし、パソコンとスマホを同じWi-Fiネットワークにつないで、App InventorでConnect > AI Companionを選び、QRコードを読み取るか画面に表示された6文字のコードを入力します。Designerやブロックでの変更がすべて、すぐにスマホに反映されます。アプリが完成したら、次の手順でインストール用のファイルを作ります。
- DesignerでScreen1を選び、AppName(スマホに表示される名前)、Icon(正方形のPNG)、VersionCode、VersionNameを入力します。
- 上のメニューからBuild > Android App (.apk)を選びます。ビルドには通常1〜2分かかります。
- 終わると、QRコードとダウンロードボタンのあるウィンドウが表示されます。スマホでQRコードを読み取って直接ダウンロードするか、APKをパソコンに保存してUSBでスマホにコピーします。QRのリンクは短時間(約2時間)しか有効ではありません。
- スマホでAPKを開くと、Androidはブラウザやファイルマネージャーに不明なアプリのインストールを許可するよう求めます。Playプロテクトが警告を出した場合は、詳細から「インストールする」を選んで続行できます。この警告は、Playストア以外から入手した見慣れない署名のアプリではよく出ます。
- アプリを初めて起動したら、付近のデバイスの権限を承認し、Connectをタップして、リストからHC-05を選びます。
HC-05不要、ESP32で同じアプリを使う

初代ESP32はクラシックBluetoothとBLEの両方を内蔵しています。BluetoothSerialライブラリはESP32をSPPデバイスとしてスマホに見せるので、今作ったアプリをそのまま使えます。スマホのBluetooth設定で「BlueGrays-ESP32」というデバイスとペアリングし、アプリのリストから選べば完了です。注意点として、ESP32-S3、ESP32-C3、ESP32-C6はBLEにしか対応していません。BluetoothSerialはなく、App Inventor側ではBluetoothLE拡張機能が必要です。BLEの使い方はESP32のBluetoothとBLEのガイドを参照してください。
1#include "BluetoothSerial.h"
2#include <DHT.h>
3
4BluetoothSerial SerialBT;
5DHT dht(4, DHT11); // DHT11 veri pini GPIO 4 / data pin GPIO 4
6
7const int LED_PIN = 2; // çoğu DevKit'te kart üstü LED / onboard LED on most DevKits
8const int ROLE_PIN = 26;
9
10String komut = "";
11unsigned long sonGonderim = 0;
12
13void setup() {
14 pinMode(ROLE_PIN, OUTPUT);
15 digitalWrite(ROLE_PIN, HIGH); // aktif LOW röle kapalı / active-LOW relay off
16 dht.begin();
17 SerialBT.begin("BlueGrays-ESP32"); // telefonda görünecek ad / name shown on the phone
18}
19
20void loop() {
21 while (SerialBT.available()) {
22 char c = SerialBT.read();
23 if (c == '\n') {
24 komut.trim();
25 int deger = komut.substring(1).toInt();
26 if (komut.startsWith("L")) {
27 analogWrite(LED_PIN, deger == 1 ? 255 : 0); // LED aç, kapat / LED on, off
28 } else if (komut.startsWith("P")) {
29 analogWrite(LED_PIN, constrain(deger, 0, 255)); // parlaklık / brightness
30 } else if (komut.startsWith("R")) {
31 digitalWrite(ROLE_PIN, deger == 1 ? LOW : HIGH); // röle / relay
32 }
33 komut = "";
34 } else if (komut.length() < 16) {
35 komut += c;
36 }
37 }
38
39 if (millis() - sonGonderim >= 2000) { // Uno koduyla aynı biçim / same format as the Uno
40 sonGonderim = millis();
41 float t = dht.readTemperature();
42 float h = dht.readHumidity();
43 if (!isnan(t) && !isnan(h)) {
44 SerialBT.print(t, 1);
45 SerialBT.print(';');
46 SerialBT.print(h, 0);
47 SerialBT.print('\n');
48 }
49 }
50}このコードはUno版と同じコマンドを理解し、同じ「温度;湿度」の形式でデータを送ります。サーボはつないでいないので、Sのコマンドは無視します。ArduinoのServoライブラリはESP32では動きません。サーボを追加するにはESP32Servoライブラリをインストールし、たとえばGPIO 13で同じattach()とwrite()を使います。リレーモジュールはVIN(5 V)ピンから給電します。ESP32の3.3 Vの信号でINが確実に切り替わらない場合は、3.3 V対応のリレーモジュールを選んでください。詳しくはリレーモジュールとは?を参照してください。
Raspberry Pi PicoとMicroPythonで使う

標準のRaspberry Pi PicoにはBluetoothがないので、HC-05をUART0のピン(GP0とGP1)につなぎます。Picoのピンは3.3 Vで動くので、今回は分圧回路は不要です。モジュールは、USBからの5 Vが来ているVBUSピンから給電します。Pico WとPico 2 WはBluetoothを内蔵していますが、MicroPythonではBLEにしか対応していません。App InventorのクラシックなBluetoothClientと通信するには、やはりHC-05が最も簡単です。下のコードは同じコマンドを理解し、50 HzのPWMでサーボを動かし、同じ形式でデータを送ります。dhtモジュールはPicoのMicroPythonファームウェアに組み込まれています。
1from machine import UART, Pin, PWM
2import dht
3import time
4
5bt = UART(0, baudrate=9600, tx=Pin(0), rx=Pin(1)) # GP0 → HC-05 RXD, GP1 ← HC-05 TXD
6led = PWM(Pin(15))
7led.freq(1000)
8role = Pin(14, Pin.OUT, value=1) # aktif LOW röle kapalı / active-LOW relay off
9servo = PWM(Pin(13))
10servo.freq(50) # servo 50 Hz ister / servos need 50 Hz
11sensor = dht.DHT11(Pin(16))
12
13def komut_isle(k):
14 if not k:
15 return
16 tur, deger = k[0], int(k[1:] or 0) # harf + sayı / letter + number
17 if tur == 'L':
18 led.duty_u16(65535 if deger == 1 else 0)
19 elif tur == 'P':
20 led.duty_u16(min(max(deger, 0), 255) * 257) # 0-255 → 0-65535
21 elif tur == 'R':
22 role.value(0 if deger == 1 else 1)
23 elif tur == 'S':
24 aci = min(max(deger, 0), 180)
25 servo.duty_ns(544000 + aci * 1856000 // 180) # 544-2400 µs darbe / pulse
26
27tampon = b''
28son = time.ticks_ms()
29while True:
30 if bt.any():
31 tampon += bt.read()
32 while b'\n' in tampon: # tam satır geldi mi? / full line received?
33 satir, tampon = tampon.split(b'\n', 1)
34 try:
35 komut_isle(satir.decode().strip())
36 except ValueError:
37 pass # bozuk komutu atla / skip malformed command
38 if len(tampon) > 32:
39 tampon = b'' # satır sonu hiç gelmedi / no newline arrived
40 if time.ticks_diff(time.ticks_ms(), son) >= 2000:
41 son = time.ticks_ms()
42 try:
43 sensor.measure()
44 bt.write('{:.1f};{}\n'.format(sensor.temperature(), sensor.humidity()))
45 except OSError:
46 pass # sensör okunamadı / sensor read failed
47 time.sleep_ms(10)App InventorとArduinoのプロジェクトアイデア
- Bluetooth操作のカー:方向ボタンにF、B、L、Rの文字を割り当て、L298Nでモーターを動かします。すべての手順はArduinoのロボットカーのガイドにあります。
- スマートホームのパネル:4チャンネルのリレーモジュールで、ランプ、コンセント、ファンを1つの画面から操作します。アイデアはArduinoのスマートホームプロジェクトを参照してください。
- 色を選べるランプ:3つのスライダーでRGB LEDの赤、緑、青の値を別々に設定します。
- ロボットアーム:関節ごとにスライダーを1つ追加し、スマホからサーボモーターを動かします。
- 温室モニター:DHT11と土壌水分センサーの値をスマホに表示し、土が乾いたらポンプを動かすボタンを追加します。
- 音声操作:App InventorのSpeechRecognizerコンポーネントを使い、「電気をつけて」と言うとL1を送るアプリを作ります。
よくある間違いと解決法
- アプリは接続されるのにLEDが点かない:HC-05のTXDとRXDの線が逆になっているか、コードのSoftwareSerialのピンが回路と合っていません。モジュールのTXDはArduinoのRX(D10)につなぐ必要があります。
- コマンドが無視される:テキストブロックに\nがありません。Arduinoは改行を待っています。届いたコマンドがシリアルモニタに表示されるか確認してください。
- 文字化けする:HC-05のボーレートがbt.begin()と違います。モジュールは出荷時9600ボーです。ATコマンドで変更した場合は、コードを合わせてください。
- サーボが震える、または接続が切れる:サーボがArduinoの5 Vラインを引き下げています。別の5 V電源から給電し、GNDを共通にしてください。わずかな震えは、SoftwareSerialが受信中に一瞬割り込みを止めることでも起きます。根本的に直すなら、予備のハードウェアシリアルポートがあるArduino MegaかESP32を使います。また、サーボがときどき一瞬違う角度に跳ねることもあります。DHTライブラリは読み取りのたびに約5 ms割り込みを止めるので、その瞬間に届いたコマンドが壊れることがあるためです。スライダーの次のコマンドで元に戻ります。
- スマホの値が遅れて表示される:Clockの間隔がArduinoの送信間隔より長く、行がたまっています。TimerIntervalは送信間隔より短くしてください。
- アプリが固まる:DelimiterByteが0のままか、Arduinoが行を\nで終えていません。ReceiveText(-1)は来ることのない区切りを待っています。
- iPhoneで動かない:HC-05はクラシックBluetoothを使っており、iOSではそれが許可されていません。iPhoneにはBLEモジュール(HM-10)かESP32が必要です。
App InventorとArduinoの組み合わせは、モバイルアプリ開発と電子工作を1つのプロジェクトで学ぶ最も楽しい方法です。接続とプロトコルさえ作れば、同じ骨組みがカーにも、スマートホームにも、温室のリモコンにもなります。変わるのは、ボタンがどの文字を送るかだけです。BlueGraysアプリでは、BluetoothでのLED制御と、L298NのBluetooth操作カーのプロジェクトを、回路図とコード付きでステップごとに紹介しています。なお、日本国内では技適マークのない無線モジュールの使用が電波法で制限されているので、HC-05を選ぶときは技適の有無を確認してください。
よくある質問
App InventorのBluetoothアプリはiPhoneで動く?
HC-05やHC-06では動きません。これらのモジュールはクラシックBluetooth(SPP)を使っていて、iOSでは普通のアプリがこの種のシリアル接続を開くことが許可されていません。iPhoneと通信するには、HM-10のようなBLEモジュールかESP32に切り替える必要があります。HC-05のプロジェクトには、Androidスマホが最もトラブルの少ない選択です。
App InventorでBluetoothのデバイス一覧が空になるのはなぜ?
AddressesAndNamesは、すでにスマホとペアリングされたデバイスしか一覧に出しません。まずBluetoothの設定でPIN 1234を使ってHC-05とペアリングしてください。Android 12以降では、付近のデバイスの権限が許可されていないと、初回もリストが空で開きます。許可してからもう一度Connectをタップしてください。Bluetoothがオンになっていることも確認しましょう。
App Inventorのエラー507を直すには?
エラー507は「Unable to connect. Is the device turned on?」(接続できません。デバイスの電源は入っていますか?)という意味で、スマホがモジュールとの接続を開けなかったことを示します。HC-05に電源が来ているか、LEDが速く点滅しているか、ほかのスマホに接続されていないかを確認してください。それでも直らなければ、ペアリングを解除してやり直すか、BluetoothClientのSecureプロパティをオフにしてみてください。
App InventorでAPKを作るには?
プロジェクトを開き、上のメニューでBuild > Android App (.apk)をクリックします。ビルドが終わったら、スマホでQRコードを読み取るか、APKをパソコンにダウンロードしてスマホにコピーします。インストールの際は、不明なアプリのインストールを許可する必要があります。
App InventorはESP32にBluetoothで接続できる?
はい。BluetoothSerialライブラリを使うと初代ESP32はSPPデバイスとして振る舞い、App InventorのBluetoothClientはHC-05と同じように接続できます。ESP32-S3とESP32-C3はBLEにしか対応していないので、これらのボードではBluetoothLE拡張機能が必要です。
App InventorのアプリをGoogle Playで公開できる?
はい。BuildメニューのAndroid App Bundle (.aab)を選ぶと、Google Playにアップロードできるファイルが作られます。Google Playのデベロッパーアカウントが必要です。アップデートは同じ鍵で署名しなければならないので、Projects > Export keystoreでキーストアをバックアップしておいてください。
この記事を読んだ後に作れるプロジェクト
全プロジェクトを見る
LEDの点滅
電子工作の第一歩は、1個のLEDを点けたり消したりすることです。Arduinoで短い間隔で点灯と消灯を繰り返すと、LEDが素早く点滅します。ボードが光を制御する仕組みを最もシンプルな形で確かめ、これから取り組むプロジェクトの基礎を身につけます。

DHT11で温度と湿度を測定
部屋の温度は何度で、空気は湿っているでしょうか、それとも乾燥しているでしょうか?DHT11センサーが温度と湿度を測定し、Arduinoが2行のLCDディスプレイに値を表示して、数秒ごとに更新します。センサーに異常があればエラーメッセージが表示されます。自分だけの小さな気象観測装置を作る第一歩です。

BluetoothでスマートフォンからLEDを操作
スマートフォンで部屋の照明をつけたり消したりするための、小さな練習です。Bluetoothモジュールがスマートフォンから送られた文字をArduinoに伝えます。ある文字で赤いLEDが、別の文字で緑のLEDが点灯し、同じ文字を小文字で送ると消灯します。無線操作の仕組みを、ごくシンプルな形で学べます。

L298Nで作るBluetooth操作の車
スマートフォンをリモコンにして、自作のロボットカーを操作できます。ArduinoはBluetoothモジュールを通じてスマートフォンのリモコンアプリから指令を受け取り、モータードライバーで車輪を回します。前進、後退、左右や斜めへの移動ができ、速度もスマートフォンから調整できます。停止ボタンを押すと、短いブレーキ動作でモーターがすぐに止まります。

サーボモーターの使い方
小型サーボモーターのアームが、一方の端、中央、もう一方の端の3つの位置へ順番に回り、それぞれの位置で2秒間止まります。Arduinoが保持する角度をモーターに指示すると、モーターはその位置まで動いて止まります。ロボットアームや自動開閉するふたが動く仕組みを学べます。

RGB LEDの使い方
1つのLEDに3つの色が入っています。RGB LEDが赤、緑、青の順に、それぞれ0.5秒点灯して0.5秒消灯し、このサイクルを繰り返します。ArduinoにRGB LEDを1つと抵抗を3つ接続するだけです。色とりどりの光を使うプロジェクトを、手軽に始められます。

ボタンを押している間だけ220V電球を点灯
ボタンを押している間は実際の220ボルトの電球が点灯し、離すとすぐに消灯します。Arduinoの小さな信号では電球を直接点灯できないため、電気で動くスイッチであるリレーを間に入れ、電球の回路を開閉します。ボードで家庭の照明を操作するための第一歩です。家庭用電源を扱う際は十分に注意し、不安がある場合は詳しい人に手伝ってもらってください。

2台のArduinoで一方向通信
2台のArduinoを1本のワイヤーで通信させます。一方のボタンを押すともう一方のLEDが点灯し、もう一度押すと消灯します。送信側のボードがボタンの押下を短いメッセージで伝え、受信側のボードはそのメッセージを受け取るとLEDの点灯と消灯を切り替えます。機器同士が情報を伝える仕組みを、最もシンプルな形で確かめられます。
ほかの記事
BlueGraysアプリ
この回路をスマホでステップごとに作る
回路図、コード、部品リストを1つの画面で確認できます。Arduino、ESP32、Picoのプロジェクトを200件以上用意しています。

