ラベル ESP32 の投稿を表示しています。 すべての投稿を表示
ラベル ESP32 の投稿を表示しています。 すべての投稿を表示

2020年8月23日日曜日

【家の自動化プロジェクト】#4 Node.jsでWEBインターフェースを作る

どうもcaketetuです。
今回は、前回製作したデバイスにアクセスするためのインターフェースをNode.jsで作ります。
より具体的にはRaspberry Pi上にWEBサーバを作り、スマホやPCのブラウザからアクセスし指示コマンドを受け付けるようにします。受信した指示コマンドをBLE信号に変換し前回製作したデバイスに飛ばせるようにします。要するに以下の経路を作ります。

webブラウザ →  <<socket.io通信>  →  webサーバ  ->  <<BLE通信>>  →  BLEデバイス



ちなみに現在のRaspberry Piの様子は…



こんな感じになってます。
3B+です。OSはちょっと古いStretch。
Amazonで安く購入できたタッチパネル付き液晶を付けている以外は特にいじっていません。いずれは音楽プレイヤー機能、キオスク機能、フォトフレーム機能を搭載したいと考えております。



PythonでBLE

bluepyでPythonからBLE信号を出せるようにします。
送信と読み込みのサンプルを貼っておきます。

ble_write.py
# -*- coding: utf-8 -*-
import time
import bluepy

HANDLE_LED = 0x002d		#ハンドル
devadr = "3C:71:BF:F0:66:26"   # アドレス

def main():
    peri = bluepy.btle.Peripheral()
    peri.connect(devadr, bluepy.btle.ADDR_TYPE_PUBLIC)
    peri.writeCharacteristic(HANDLE_LED, b'Ms' )
    time.sleep(5)
    peri.disconnect()

if __name__ == "__main__":
    main()
ble_read.py
# -*- coding: utf-8 -*-
import bluepy
import time

HANDLE_SERIAL = 0x002d		#ハンドル
devadr = "24:6F:28:9D:C6:6A"   # アドレス

def main():
    peri = bluepy.btle.Peripheral()
    peri.connect(devadr, bluepy.btle.ADDR_TYPE_PUBLIC)
    time.sleep(1)
    serialnum = peri.readCharacteristic(HANDLE_SERIAL)
    print(len(serialnum))
    print( serialnum.decode('utf-8') )
    peri.disconnect()

if __name__ == "__main__":
    main()

bluepyの簡単な使い方ですが、
まず 
peri = bluepy.btle.Peripheral()で宣言 、
peri.connect(デバイスアドレス, bluepy.btle.ADDR_TYPE_PUBLIC) 
で接続します。読み込みは 
データ = peri.readCharacteristic(ハンドル)、
送信は 
peri.writeCharacteristic(ハンドル, データ )
です。最後に
peri.disconnect()
でクローズしましょう。

デバイスのハンドル、アドレスの調べ方はここでご紹介しています。

ここで、前回作ったデバイスの通信コードですが、
//*****命令コード表****
CMD_SERVO_MOVE 0x78 //照明用サーボ動作指令
命令コード + 角度(2byte) + 動作時間(2byte)
CMD_LIGHT_LIV   0x80 //リビング照明
命令コードのみ
CMD_LIGHT_ENT 0x81 //エントランス照明
命令コードのみ
CMD_IR_SEND 0x79 //エアコン、データ書き込み
命令コード  + ボタン + 温度+ 風力+ 動作モード + ON/OFF (全1byte)
CMD_IR_POWER_OFF 0x82 //エアコンOFF
命令コードのみ
CMD_IR_REPEAT 0x83 //エアコン指令をリピート
命令コードのみ
REQ_DATA 0x50 //全データリクエスト
   命令コードのみ(読み取りで実行)

となっていました。これにあわせて作るとこんな感じになります。参考にどうぞ。
コードを書く時詰まったのがバイト操作です。WEBソケット通信の命令は文字列なのに対しBLE通信はバイト列になっているので数値を送るときはバイト単位の結合、分離が必要です。これはpythonのpack,unpackモジュールで実現できます。



Raspberry Pi上にWEBブラウザを作る

WEBブラウザの作り方はこちらに別記事でまとめました。これに上記で作成したPython
プログラムを呼び出すコードを追加することでWEBブラウザから指示を出せるようになります。現在は以下のようになってます。

app.js
	
//********************************************************** */
//      モジュールオブジェクトの初期化
//********************************************************** */
var express = require('express');
var app = express();
var http = require('http').Server(app);
const io = require('socket.io')(http);          //Socket.io
const execSync = require('child_process').execSync; //外部コマンド実行(同期)

//********************************************************** */
//      グローバル変数
//********************************************************** */
//エアコン送信パラメータ
var ac_para = ['b_off', '27', 'auto', 'cooling', 'off'];

//********************************************************** */
//      EXPRESS関連
//********************************************************** */
//サーバー実装の前に、エラーハンドリングを記載します。
process.on('uncaughtException', function(err) {
    console.log(err);
  });
//テンプレートエンジンの指定s
app.set("view engine", "ejs");
//ルーティング
app.get('/', (req, res)=> { res.render('index'); });
//3000番ポートで待ち受けてサーバー開始
http.listen(3000, function () {
    console.log("Start server at port:3000...");
});

//********************************************************** */
//    SocketIOリクエスト処理
//********************************************************** */
io.sockets.on("connection", function (socket) {
    //=====照明コントロール=====
    socket.on("light", function (msg) {
        var res;
        if(msg=='living'){
            res = request_shellsync("python3 ble_light_ctrl.py servo 55 1000");
        }else if(msg=='entrance'){
            res = request_shellsync("python3 ble_light_ctrl.py servo 115 1000");
        }
        console.log(res.toString());
    });
    //=====エアコンコントロール=====
    socket.on("aircon", function (msg) {
      var res;
      var cmd = "python3 ble_aircon_ctrl.py ";
      if(msg.cmd=='send'){
          cmd += msg.btn + " ";
          cmd += msg.temp + " ";
          cmd += msg.level + " ";
          cmd += msg.select;
          res = request_shellsync(cmd);
      }else if(msg.cmd=='off'){
          cmd += "off";
          res = request_shellsync(cmd);
      }
      console.log(res.toString());
    });
    //=====画面コントロール=====
    socket.on("display", function (msg) {
      var res;
      if(msg=='on'){
          res = request_shellsync("vcgencmd display_power 1");
      }else if(msg=='off'){
          res = request_shellsync("vcgencmd display_power 0");
      }
      console.log(res.toString());
    });

    //端末データリクエスト
    socket.on("data_req", function (msg) {
        var res;
        res = request_shellsync("python3 ble_read.py");
        socket.emit("req_data", res.toString());
        console.log(res.toString());
    });
});

  //********************************************************** */
  //Shellに指示を投げる関数 同期
  //********************************************************** */
  var request_shellsync = function(command){
    var str_cmd =  command;   //コマンド列生成
    //child_processでシェルコマンドを実行
    result = execSync(str_cmd, function(error, stdout, stderr) {
      if (error !== null) {
        console.log('exec error: ' + error);    //エラーなら表示
      }
    });
    return result;  //結果を返す
  }

index.ejs
	
<!DOCTYPE html><html>
    <head>
        <title>Home Ctrl</title>
    </head>
    <body>
        <script src="/socket.io/socket.io.js"></script>
        <script type="text/javascript">
            var socket = io.connect('http://ip_adress:3000');
            //ライトコントロール
            function cmd_light(cmd) {
                socket.emit("light",cmd);
            }
            //エアコンコントロール
            function cmd_aircon(cmd) {
                var btn = document.getElementById('ac_btn');
                var temp = document.getElementById('ac_temp');
                var level = document.getElementById('ac_level');
                var select = document.getElementById('ac_select');
                var send_data = {
                    "cmd" : cmd,
                    "btn" : btn.value,
                    "temp" : temp.value,
                    "level" : level.value,
                    "select" : select.value,
                }
                socket.emit("aircon",send_data);
            }
            function cmd_display(cmd) {
                socket.emit("display",cmd);
            }
            //端末データリクエスト
            function cmd_request() {
                socket.emit("data_req",0);
            }
            //リクエストデータ受信時
            socket.on("req_data", function (data) {
                document.getElementById("request_data").innerHTML=data;
            });

        </script>
        <div class="title"><h1>My Home Automation</h1></div>
        <!-- 端末データ表示 -->
        <div class="function">
            <div class="fc_title">Sensor data</div>
            <div id="request_data">Info:</div>
            <button type="button" onClick="cmd_request()">Data Request</button></br>
        </div>
        <!-- 照明コントロールボタン -->
        <div class="function">
            <div class="fc_title">light switch</div>
            <button type="button" onClick="cmd_light('living')">Living</button>
            <button type="button" onClick="cmd_light('entrance')">Entrance</button>
        </div>
        <!-- エアコン操作ボタン -->
        <div class="function">
            <div class="fc_title">Air Con Controll</div>
            <select id="ac_btn">
                <option value="off">off</option>
                <option value="up">up</option>
                <option value="down">down</option>
                <option value="tistop">tistop</option>
                <option value="tiwkup">tiwkup</option>
                <option value="wind_lev">wind_lev</option>
                <option value="wind_dir">wind_dir</option>
            </select>
            <select id="ac_temp">
                <option value="19">19</option>
                <option value="20">20</option>
                <option value="21">21</option>
                <option value="22">22</option>
                <option value="23">23</option>
                <option value="24">24</option>
                <option value="25">25</option>
                <option value="26">26</option>
                <option value="27">27</option>
                <option value="28">28</option>
                <option value="29">29</option>
            </select>
            <select id="ac_level">
                <option value="auto">auto</option>
                <option value="strong">strong</option>
                <option value="nomal">nomal</option>
                <option value="week">week</option>
            </select>
            <select id="ac_select">
                <option value="cooling">cooling</option>
                <option value="dehumid">dehumid</option>
                <option value="heating">heating</option>
            </select>
            <button type="button" onClick="cmd_aircon('send')">send</button>
            <button type="button" onClick="cmd_aircon('off')">off</button>
        </div>
        <!-- 画面バックライト制御ボタン -->
        <div class="function">
            <div class="fc_title">Display Controll</div>
            <button type="button" onClick="cmd_display('on')">on</button>
            <button type="button" onClick="cmd_display('off')">off</button>
        </div>
    </body>
</html>

Node.jsで外部プログラムを呼び出すにはchild_processモジュールを使います。
const execSync = require('child_process').execSync; //外部コマンド実行(同期)

//********************************************************** */
//Shellに指示を投げる関数 同期
//********************************************************** */
var request_shellsync = function(command){
  var str_cmd =  command;   //コマンド列生成
  //child_processでシェルコマンドを実行
  result = execSync(str_cmd, function(error, stdout, stderr) {
    if (error !== null) {
      console.log('exec error: ' + error);    //エラーなら表示
    }
  });
  return result;  //結果を返す
}

res = request_shellsync("vcgencmd display_power 1");
端末で実行するようなコマンドを文字列で渡すだけなので楽です。pythonだけではなく
シェルコマンドや多言語のプログラムも同じように実行できます。
同じ手法でRaspberry Piの画面をON/OFFするコードも作っています。
実行コマンドは
vcgencmd display_power 1    //画面ON
vcgencmd display_power 0  //画面OFF
です。覚えておくといつか使う時があるかも。



サーバ起動!

ディレクトリ内でnode app.jsを打ち込みサーバを起動します。ブラウザから"http://アドレス:ポート"にアクセスするとコントローラ画面が表示されます。
動作の様子







今回は以上です。インターフェースの見た目がだいぶアレですがかなり実用的になったと思います。私も日常生活では非常に活用していて、電気を消すためいちいち立ち上がったりしなくてよいので便利になったなぁと思います。さらにIoTしてる感による近未来感が良い。

とはいえRaspberry Piでサーバーを動かしている割にはまだまだ機能がショボいので、もっとデバイスを作って機能を増やし、より快適な自動化生活を目指します。

2020年8月21日金曜日

【家の自動化プロジェクト】#3 一つ目のBLEデバイス

どうも、caketetuです。
相変わらず家の自動化プロジェクトを進めています。今回は一つ目のデバイスが完成したので
ご紹介します。



外観


前回でESP32を使ったベースに小型液晶とサブプロセッサとしてのArduino、環境センサ
を乗っけたデバイスを作りました。今回はさらに機能を足して最終的に以下機能を持った
デバイスになりました。(リンクは個別記事です。参考にどうぞ)

・照明操作(アナログサーボ) アナログサーボを動かす
・エアコン操作(赤外線LED) 赤外線でエアコンを操作する
・環境測定(環境センサ) 自動化プロジェクト#2
・画面表示(小型液晶) 自動化プロジェクト#2

全部乗せ感がすごいですね(笑)


回路設計



回路はこんな感じです。赤外線LEDとサーボはAruduinoを介して接続しているので
ちょっとごちゃついています。ESP32のBLEを使うと割込みが不安定になることが
あったのでこのような構成にしました。

照度センサが標準でついているのは寝るときに画面を消すためです。<<意外と重要>>





ハード設計


画像中央のコアユニット部です。タミヤのユニバーサルプレートに合わせて
設計することで他の工作にも応用が利くように工夫しました。


画像左側のユニットにくっついているのはエアコン操作用の赤外線照射
モジュールです。LEDを三個直列につなげて照射範囲を広くしています。
結構遠くでも反応してくれます(5mぐらい?)
2自由度あるので調整も割と簡単です。


画像上部に乗っているのは照明操作用のサーボモータです。一軸で二つの照明を操作
出来ます。既製品のスイッチに左右のネジでクランプして固定します。
既存のモノの面積がとても小さいので、ちゃんと固定するのに苦労しました。



ソフト

上記の個別記事で紹介したサンプルプログラムをこちらで作ったBLEデバイスのプロトタイプにぶち込んで作りました。参考までにすべて載せておきます。

BLE_Unit_Firmware1.0.c      ESP32のメインプログラム
code_list.h                        通信コードヘッダ
Arduino_sub_prossor1.0.c    Aruduino 側プログラム
IR_Ctrl.h                          エアコン操作ヘッダ 

通信プロトコルはESP32側の136行目から始まる関数とcode_list.hに定義されており、
BLEデータの先頭バイトで識別します。
現在、以下のように定義されています。

//*****命令コード表****
CMD_SERVO_MOVE   0x78   //照明用サーボ動作指令  
        命令コード +  角度(2byte) + 動作時間(2byte)
CMD_LIGHT_LIV      0x80   //リビング照明
        命令コードのみ
CMD_LIGHT_ENT       0x81   //エントランス照明
        命令コードのみ
CMD_IR_SEND           0x79   //エアコン、データ書き込み
        命令コード  + ボタン + 温度+ 風力+ 動作モード + ON/OFF (全1byte)
CMD_IR_POWER_OFF 0x82   //エアコンOFF
        命令コードのみ
CMD_IR_REPEAT        0x83   //エアコン指令をリピート
        命令コードのみ
REQ_DATA                 0x50   //全データリクエスト
   命令コードのみ(読み取りで実行)

現在、通信エラーはめったに発生していませんが、チェックサムなどを用いた
エラーチェックをしたほうがいいかもしれませんね。

また、パラメータ設定指令などを作ってパラメータを保存できるようにするのも課題です。



今回は以上です。若干ゴツゴツした見た目が意外とお気に入りです。
次回はRaspberry piにWEBサーバを作りそこから操作できるようにしたいと思います。











2020年7月29日水曜日

【ESP32】 BLEでRaspberry Piと通信する

どうもcaketetuです。
最近暇つぶしにFalloutというゲームを買ってみたら、かなりハマってしまって
猛烈な勢いで時間を消費しています。短期間にゲームをやりこむと燃え尽きて
すぐ飽きてしまうのでほどほどにして現実でもクラフトしましょう。
今回はホームオートメーションの核となるBLEでRaspberryPiと通信してみます。



こちらにBLEの基礎について詳しい解説がありました。実際はライブラリに隠れているので
そこまで意識することはありませんがザっと読んでおくと大変勉強になります。

・BLEプログラムのプロトタイプを作る

ちょっと勉強して理解したつもりになったところでArduinoIDEのサンプルからBLEデバイスの
プロトタイプを作ってみました。

//==========================================================
//    Include Header
//==========================================================
#include <BLEDevice.h>
#include <BLEServer.h>
#include <BLE2902.h>

//*****ヘッダ都度部******

//*********************

//==========================================================
//    Macro Definition
//==========================================================
#define SERVER_NAME "ESP32_BLE_TEST"     // サーバー名 各自設定する
//====シグナル定義====
//#define SIGNAL_ERROR      'E'         // (異常発生:Error)
//#define SIGNAL_REQUEST    'r'         // デバイス状態取得要求
//====UUID設定====
#define SERVICE_UUID               "28b0883b-7ec3-4b46-8f64-8559ae036e4e"      // サービスのUUID
#define CHARACTERISTIC_UUID_Notify "2049779d-88a9-403a-9c59-c7df79e1dd7c"      // NotifyUUID
#define CHARACTERISTIC_UUID_RX     "9348db8a-7c61-4c6e-b12d-643625993b84"      // 受信用UUID
#define CHARACTERISTIC_UUID_TX     "e303cfe5-b463-4b82-bbc3-e0789945b499"      // 送信用UUID

//*****マクロ都度部******

//*********************

//==========================================================
//    Function Prototype
//==========================================================
void BLE_Setup(void);

//****関数プロトタイプ都度部*****

//****************************

//==========================================================
//    Global Variable
//==========================================================
BLECharacteristic *pCharacteristicTX;   // 送信用キャラクタリスティック
BLECharacteristic *pCharacteristicRX;   // 受信用キャラクタリスティック
BLECharacteristic *pCharacteristicDD;   // データ送信要求キャラクタリスティック
BLEServer *pServer = NULL;
bool deviceConnected = false;           // デバイスの接続状態
bool bInAlarm  = false;                 // デバイス異常判定
bool oldDeviceConnected = false;        //

//*****グローバル変数都度部******

//****************************

//----------------------------------------------------------
//    接続・切断時コールバック関数
//----------------------------------------------------------
class funcServerCallbacks: public BLEServerCallbacks {
    void onConnect(BLEServer* pServer) {
      deviceConnected = true;
    }
    void onDisconnect(BLEServer* pServer) {
      deviceConnected = false;
    }
};

//----------------------------------------------------------
//    シグナル受信時のコールバック
//----------------------------------------------------------
class funcReceiveCallback: public BLECharacteristicCallbacks {
    void onWrite(BLECharacteristic *pCharacteristicRX) {
      // シリアルモニタに表示する
      std::string rxValue = pCharacteristicRX->getValue();
      if (rxValue.length() > 0) {
        //受信したデータを全部表示
        Serial.print("RECV_DATA: ");
        for (int i = 0; i < rxValue.length(); i++) Serial.print(rxValue[i]);
        Serial.println();

        //*******************受信時処理の都度部**********************************

        
        //*******************************************************
      }
    }
    void onRead(BLECharacteristic *pCharacteristicRX) {
      
      //*******************受信時処理の都度部**********************************
      char send_str[] = "Hello world";
      pCharacteristicRX->setValue(send_str);
      Serial.println("BLE:Data Request");
      //*******************************************************
    }
};

//----------------------------------------------------------
//    BLEセットアップ関数
//----------------------------------------------------------
void BLE_Setup(void) {
  BLEDevice::init(SERVER_NAME);   // 初期化処理を行ってBLEデバイスを初期化する
  // Serverオブジェクトを作成してコールバックを設定する
  pServer = BLEDevice::createServer();
  pServer->setCallbacks(new funcServerCallbacks());
  // Serviceオブジェクトを作成して準備処理を実行する
  BLEService *pService = pServer->createService(SERVICE_UUID);

  // Notify用のキャラクタリスティックを作成する
  pCharacteristicTX = pService->createCharacteristic(
                        CHARACTERISTIC_UUID_TX,
                        BLECharacteristic::PROPERTY_NOTIFY
                      );
  pCharacteristicTX->addDescriptor(new BLE2902());

  // 受信用キャラクタリスティックを作成してシグナル受信時のコールバックを設定する
  pCharacteristicRX = pService->createCharacteristic(
                        CHARACTERISTIC_UUID_RX,
                        BLECharacteristic::PROPERTY_WRITE
                      );
  pCharacteristicRX->setCallbacks(new funcReceiveCallback());

  // サービスを開始して、SERVICE_UUIDでアドバタイジングを開始する
  pService->start();
  BLEAdvertising *pAdvertising = pServer->getAdvertising();
  pAdvertising->addServiceUUID(SERVICE_UUID);
  pAdvertising->start();
}

//----------------------------------------------------------
//    Set Up Function
//----------------------------------------------------------
void setup() {
  Serial.begin(115200);  //シリアル設定 
  BLE_Setup();           //BLEセットアップ
}

//----------------------------------------------------------
//    Main Loop Function
//----------------------------------------------------------
void loop() {
  // notifyしたいときはここに書く
  if (deviceConnected) {}
  
  //接続段時の処理
  if (!deviceConnected && oldDeviceConnected) {
    delay(500); // give the bluetooth stack the chance to get things ready
    pServer->startAdvertising(); // restart advertising
    //Serial.println("start advertising");
    oldDeviceConnected = deviceConnected;
  }
  //接続開始時の処理
  if (deviceConnected && !oldDeviceConnected) {
    oldDeviceConnected = deviceConnected;   // do stuff here on connecting
  }
  delay(100);
}

「都度部」と書かれたところに固有の処理を書くことで簡単にBLEデバイスを作れるように
しました。

具体的にはセントラルからデータが送信されると68行目のコールバック関数
が呼ばれ、データはrxValue[]に入っているのでこの値にその下の都度部に処理を書きます。

また、セントラルから読み出し指示があった時は84行目のonRead関数が呼ばれるので
pCharacteristicRX->setValue()にデータをセットすれば送信することができます。

本来は機能ごとにUUIDを振るのが正解っぽいですが送信データの先頭で処理を
場合分けすればいいかなと思ったのでUUIDは一つだけ使っています。



このプログラムでは単体テストできるようにセントラルから来たデータを
シリアルで書き出す処理、読み取り指示があった時は指定文字列を送信するコード
になっています。次にRaspberri Piから接続してデータをやり取りしてみます。


・Raspberry piから接続する

作ったデバイスにRaspberry Piから接続してみます。今回はテストなので
gattoolというソフトを使って直接値を見てみます。
まずはアドレスを調べます。

pi@raspberrypi:~ $ sudo hcitool lescan
LE Scan ...
3C:71:BF:F1:E6:7E (unknown)
3C:71:BF:F1:E6:7E ESP32_BLE_TEST
41:DD:FB:51:61:AB (unknown)
41:DD:FB:51:61:AB (unknown)

アドレスがわかったらCtrl+Cで止めてしまいましょう。私の場合3C:71:BF:F1:E6:7E
であることがわかります。ESP32側のプログラムと一致しますね。
gatttool -b アドレス -I でデバイスを指定しconnectで接続します。

pi@raspberrypi:~ $ gatttool -b 3C:71:BF:F1:E6:7E -I
[3C:71:BF:F1:E6:7E][LE]> connect
Attempting to connect to 3C:71:BF:F1:E6:7E
Connection successful

characteristicsでキャラクタリスティックを見つけましょう。

[3C:71:BF:F1:E6:7E][LE]> characteristics
handle: 0x0002, char properties: 0x20, char value handle: 0x0003, uuid: 00002a05-0000-1000-8000-00805f9b34fb
handle: 0x0015, char properties: 0x02, char value handle: 0x0016, uuid: 00002a00-0000-1000-8000-00805f9b34fb
handle: 0x0017, char properties: 0x02, char value handle: 0x0018, uuid: 00002a01-0000-1000-8000-00805f9b34fb
handle: 0x0019, char properties: 0x02, char value handle: 0x001a, uuid: 00002aa6-0000-1000-8000-00805f9b34fb
handle: 0x0029, char properties: 0x10, char value handle: 0x002a, uuid: e303cfe5-b463-4b82-bbc3-e0789945b499
handle: 0x002c, char properties: 0x08, char value handle: 0x002d, uuid: 9348db8a-7c61-4c6e-b12d-643625993b84

今回はCHARACTERISTIC_UUID_RXにアクセスしたいのでハンドルは0x002dになります。
このハンドルを使って通信をしてみましょう。char-read-hnd ハンドル で読み取り、
char-write-cmdでデータを送信します。

[3C:71:BF:F1:E6:7E][LE]> char-read-hnd 0x002d
Characteristic value/descriptor: 48 65 6c 6c 6f 20 77 6f 72 6c 64
[3C:71:BF:F1:E6:7E][LE]> char-write-cmd 0x002d 30313233



ArduinoIEDのシリアルモニタを見るとなんかでています。Raspberry Pi側は
16進数になっているのでわかりずらいですが、アスキーコードに直すとちゃんと
データがやり取りできていることがわかります。



これで無事通信できていることがわかりました。まだBLEについて理解できていない
部分があるので使い方が間違っているかもしれませんが、データを送受信できるように
なったのでとりあえず動かせるようになります。今回作ったプロトタイププログラム
に色々追加してホームオートメーション用BLEネットワークを構築していきたいと
思います。

参考にさせて頂いたサイト

2020年7月12日日曜日

【家の自動化プロジェクト】#2 モジュールをテストしてみた

どうも。caketetuです。
前回はESP32を使ってBLE子機ユニットと拡張モジュールを作りました。
これだけではただマイコンを囲った箱ですので接続できても何もできません。
よって今回は、拡張モジュールにいくつか機能を付け足し、テストしてみよう
と思います。

前回より、以下のモジュールを追加しました。
 ・環境センサモジュール
 ・ミニI/Fパネル
 ・サブプロセッサモジュール



これが全面のミニI/Fパネルです。ここで紹介したSSD1306 OLED
ディスプレイと3つのボタン、赤青のLEDがついています。これで
単体でも最低限の操作をできるようになると思います。




モジュール右側に追加した環境センサモジュールです。使用してるセンサは
BME280とCCS811で気温、湿度、気圧、CO2濃度、総揮発性有機化合物(TVOC)
を測定できます。どちらもI2C接続なので配線は電源合わせ4線で済みます。




モジュール上側に追加したサブプロセッサモジュールです。Arduinoを
マイコン単体で動作させ、シリアル通信で接続します。ESP32はBLEを使ったとき
動作が不安定になる感じがあったので、ハードウェア制御は別のマイコンを
使うことにしました。マイコン単体をArduino化させるのはここで紹介しています。



本ユニット全体の回路としてはこんな感じになってます。本体には
デバック用として圧電ブザーを付けました。子機デバイス一つ目としては
ほとんど完成だと思います。



ここからソフトを書き込んでモジュールごとにチェックしていきます。
まずミニI/Fパネルから動かしていきます。ボタン操作でページを変える
サンプルを作ってみました。
I/Fパネルサンプルプログラム
//*********************************************************
//    Easy_IF_pannel_test
//    ESP32_Core_Unit向け簡易IFパネルテストプログラム
//    1.0   2020 7/3  作成
//*********************************************************

//==========================================================
//    Include Header
//==========================================================
#include <Wire.h>             //I2Cヘッダ(OLED)
#include <Adafruit_GFX.h>     //Adafruit グラフィックヘッダ(OLED)
#include <Adafruit_SSD1306.h> //Adafruit LCDヘッダ(OLED)

//==========================================================
//    Macro Definition
//==========================================================
//ディスプレイ関連
#define SCREEN_WIDTH 128    //OLEDピクセルX
#define SCREEN_HEIGHT 64    //OLEDピクセルY
#define OLED_RESET     -1   //OLEDリセット(本モジュールは無し)
//ピン定義
#define BUZZER_PIN 23
#define LED1 12
#define LED2 14
#define SW1 13
#define SW2 27
#define SW3 26

#define PAGE_MAX 2    //表示ページ最大
#define KEY_OFF 150   //ボタンウェイト時間

//==========================================================
//    Global Variable
//==========================================================
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET);   //ディスプレイクラス
int page = 0;     //ページ


//==========================================================
//    Function Prototype
//==========================================================
void show_disp0(void);
void show_disp1(void);
void show_disp2(void);


//----------------------------------------------------------
//    Set Up Function
//----------------------------------------------------------
void setup() {
  Serial.begin(115200);   //シリアル初期化
  ledcSetup(1,12000, 8);  //ブザーPWM初期化

  //ピン設定
  pinMode(LED1,OUTPUT);
  pinMode(LED2,OUTPUT);
  digitalWrite(LED1,HIGH);
  digitalWrite(LED2,HIGH);
  pinMode(SW1,INPUT);
  pinMode(SW2,INPUT);
  pinMode(SW3,INPUT);

  //OLED設定
  if(!display.begin(SSD1306_SWITCHCAPVCC, 0x3C)) { // Address 0x3D for 128x64
    Serial.println(F("SSD1306 allocation failed"));
    for(;;); // 失敗の時は無限ループ
  }
  display.display();      //ディスプレイ表示
  delay(1000);            //ちょっと待つ
  display.clearDisplay(); //ディスプレイクリア
  
}

//----------------------------------------------------------
//    Main Loop Function
//----------------------------------------------------------
void loop() {
  if(digitalRead(SW1)==LOW){        //ボタン1動作
    ledcAttachPin(BUZZER_PIN,1);    //ブザーピンセット
    digitalWrite(LED1,LOW);         //LED1 ON
    digitalWrite(LED2,LOW);         //LED2 ON
    ledcWriteNote(1, NOTE_C, 4);    //ブザーを鳴らす
    delay(KEY_OFF);                 //指定時間待つ(チャタリング防止)
    ledcDetachPin(BUZZER_PIN);      //ブザーピンを解除
    page++;   //次のページへ
  }else if(digitalRead(SW2)==LOW){  //ボタン2動作
    ledcAttachPin(BUZZER_PIN,1);    //ブザーピンセット
    digitalWrite(LED1,LOW);         //LED1 ON
    ledcWriteNote(1, NOTE_G, 4);    //ブザーを鳴らす
    delay(KEY_OFF);                 //指定時間待つ(チャタリング防止)
    ledcDetachPin(BUZZER_PIN);      //ブザーピンを解除
    page=0;   //最初のページに戻る
  }else if(digitalRead(SW3)==LOW){  //ボタン3動作
    ledcAttachPin(BUZZER_PIN,1);    //ブザーピンセット
    digitalWrite(LED2,LOW);         //LED2 ON
    ledcWriteNote(1, NOTE_A, 4);    //ブザーを鳴らす
    delay(KEY_OFF);                 //指定時間待つ(チャタリング防止)
    ledcDetachPin(BUZZER_PIN);      //ブザーピンを解除
    page--;   //前のページへ
  }
  
  digitalWrite(LED1,HIGH);    //LED1 OFF
  digitalWrite(LED2,HIGH);    //LED2 OFF

  if(page>PAGE_MAX) page=0;         //ページ上限
  else if(page<0) page = PAGE_MAX;  //ページ下限

  //pageによって表示を切り替え
  switch(page){
    case 0: show_disp0(); break;
    case 1: show_disp1(); break;
    case 2: show_disp2(); break;
    default: break;
  }
}

//----------------------------------------------------------
//    Show Display0 Function
//----------------------------------------------------------
void show_disp0(void){
  display.clearDisplay();   //ディスプレイクリア
  display.setTextSize(2);   //テキストサイズ設定
  display.setTextColor(SSD1306_WHITE);    //テキストカラー設定
  display.setCursor(10, 0);     //表示場所設定
  display.println(F("Page 0")); //テキスト出力
  display.display();            //画面表示
}

//----------------------------------------------------------
//    Show Display1 Function
//----------------------------------------------------------
void show_disp1(void){
  display.clearDisplay();   //ディスプレイクリア
  display.setTextSize(2);   //テキストサイズ設定
  display.setTextColor(SSD1306_WHITE);    //テキストカラー設定
  display.setCursor(10, 0);     //表示場所設定
  display.println(F("Page 1")); //テキスト出力
  display.display();            //画面表示
}

//----------------------------------------------------------
//    Show Display2Function
//----------------------------------------------------------
void show_disp2(void){
  display.clearDisplay();   //ディスプレイクリア
  display.setTextSize(2);   //テキストサイズ設定
  display.setTextColor(SSD1306_WHITE);    //テキストカラー設定
  display.setCursor(10, 0);     //表示場所設定
  display.println(F("Page 2")); //テキスト出力
  display.display();            //画面表示
}



こんな感じで動きます。画面も小さいので本当に最低限といった感じです。



次に環境センサモジュールを動かしていきます。
使用したセンサは結構有名でライブラリが豊富にそろっていますので
そちらを使っていくのが楽でしょう。私はSparkFunBME280とSparkFunCCS811
を使うことにしました。とりあえずセンサ値を垂れ流すサンプルを載せます。
//*********************************************************
//    Environmental_Sensor_Test
//    ESP32_Core_Unit向け環境センサユニットテストプログラム
//    1.0   2020 7/3  作成 BME280に対応
//    1.1   2020 7/3  作成 CCS811に対応
//*********************************************************
//==========================================================
//    Include Header
//==========================================================
#include <Wire.h>
#include "SparkFunBME280.h"
#include "SparkFunCCS811.h"

//==========================================================
//    Macro Definition
//==========================================================
#define SDA 18    //I2C SDA PIN
#define SCL 19    //I2C SCL PIN

#define BME280_ADDR 0x76    //センサアドレス BME280
#define CCS811_ADDR 0x5A    //センサアドレス CCS811

//==========================================================
//    Global Variable
//==========================================================
BME280 sen_bme280;    //センサクラス BME280
CCS811 sen_ccs811(CCS811_ADDR);    //センサクラス CCS811
//センサデータ
double temp_act = 0.0, press_act = 0.0,hum_act=0.0;
unsigned short co2=0, tvoc=0;

//==========================================================
//    Function Prototype
//==========================================================

//----------------------------------------------------------
//    Set Up Function
//----------------------------------------------------------
void setup() {
  Serial.begin(115200);   //シリアル初期化

  //I2C設定&センサ初期化
  Wire.begin( SDA, SCL, 40000);           //Pin SDA, Pin SCL, Frequency
  sen_bme280.setI2CAddress(BME280_ADDR);  //BME280 アドレスセット
  //BME280 セットアップ
  if(sen_bme280.beginI2C() == false){ 
    Serial.println("Sensor BME280 connect failed");   //エラーなら停止
    while(1);
  }
  //CCS811 セットアップ
  if (sen_ccs811.begin() == false){
      Serial.print("Sensor CCS811 connect failed");   //エラーなら停止
      while (1);
  }
  Serial.println("start ");
}


//----------------------------------------------------------
//    Main Loop Function
//----------------------------------------------------------
void loop() {
  //センサ値取得 BME280
  temp_act = sen_bme280.readTempC();
  press_act = sen_bme280.readFloatPressure();
  hum_act = sen_bme280.readFloatHumidity();

  //センサ値取得 CCS811
  if (sen_ccs811.dataAvailable()){
    sen_ccs811.readAlgorithmResults();
    co2 = sen_ccs811.getCO2();
    tvoc = sen_ccs811.getTVOC();
  }

  //シリアル出力
  Serial.print(" Humidity: ");
  Serial.print(hum_act, 0);
  Serial.print(" Pressure: ");
  Serial.print(press_act, 0);
  Serial.print(" Temp: ");
  Serial.print(temp_act, 2);

  Serial.print(" CO2: ");
  Serial.print(co2);
  Serial.print(" tVOC: ");
  Serial.print(tvoc);
  
  Serial.println();

  delay(500);   //ちょっと待つ
}

動きはこんな感じです。
値は取れているので通信はOKのようです。しかし、値は何となく違う感じ
がしています。実用にはセンサ値を分析してフィルタや補正を考えてやる
必要がありそうです。


今回、拡張モジュールを追加してIoT機器っぽくなりました。しかしこれだけではちょっと
物足りません。部屋のものをいろいろ操作できるぐらいでないとホームオートメーション
とは言えません。次回はESP32から部屋の機器を何かを操作できるようにしてみたい
と思います。





2020年7月11日土曜日

【家の自動化プロジェクト】#1 BLE子機の製作1

どうもcaketetuです。

去年末からHA(ホームオートメーション)に興味があり、色々作って遊んでおりました。
Google Assistantの力を借りて音声認識で色々動かせるところまでいったのですが
ちょっとお粗末な部分もあったのでリニューアルも兼ねて作り直してみようと思います。


構成としてはRaspBerryPiがBLE親機になり複数のBLE子機を一括管理します。
そこにNode.jsでwebサーバーを作り、スマホやパソコンでアクセスし命令を飛ばす
仕組みです。今回は実際の指令を実行するBLE子機ユニットをESP32で作っていこう
と思います。



設計はこんな感じ。ESP32モジュールを入れるためのただの箱です。
穴がたくさん開いている板はタミヤのユニバーサルプレートで、
これを共通規格として用いることで上下左右に拡張できる所が
本計画のミソになっています。




内部の回路です。これもIOを使いやすいように引っ張ってきてるだけの基板
です。GND、3.3V、Vinはよく使うのでピンを多くしています。



各パーツになります。ユニバーサルプレートを使用しているので
パーツをそろえる時間がぐっと節約できます。こういった板状のものを
印刷すると時間かかりますからね。




組み立てた様子がこちら。しっかりと組みあがりました。
タミヤのユニバーサルプレートは設計しやすい寸法になっており、
穴でカットすれば簡単に精度よく加工できるので重宝しています。



次に拡張パーツを作っていきます。本計画は拡張性の高い基本設計に
拡張パーツをどんどん付け足せるようにすることで機能を簡単に
増やすことができます。PLCにモジュールを足す感じにヒントを得ました。



こんな感じで拡張していきます。最初の基本設計から全面のIFパネルと
上、横に回路を取り付けられる拡張モジュールを増やしました。




わーっと出力し…



はい、全部乗せ完成です。
拡張モジュールの使い方としては今のところ以下を考えています。

 ・温湿度センサ、CO2センサなどをまとめた、”環境センサモジュール”
 ・マイコンを乗っけてピンとリソースを拡張する "サブプロセッサモジュール"
 ・モータ制御に対応した、”モータコントローラモジュール”

次回は拡張モジュールに回路を追加していくつかのサンプルを動かしてみようと思います。







2020年6月28日日曜日

ESP32でLCDディスプレイを動かす

どうも。caketetuです。
ESP32で使うディスプレイを買ってきたのでテストしてみます。
今回はライブラリのサンプルを動かしてみるだけの動画になります。
環境:Arduino IDE 1.8.9


今回テストしてみたのはこちら(Amazonのリンクに飛びます)

1.8インチディスプレイは前から持っていたものです。WiFi時計を作った時に
使用しました。こちらがちょっとイマイチだったので買い足した次第です。


まず一番大きい2.8インチのものから使っていきます。



サンプルはArduinoIDEのライブラリマネージャから
Adafruit GFX LibraryとAdafruit ILI9341をインストールします。

ここで問題があり、私の環境ではAdafruit ILI9341のバージョンを落とさなければ
コンパイルできませんでした。今は1.3.6を使用しています。

ファイル→スケッチブックの例->Adafruit ILI9341からgraphicstestを開き
ピン定義部分とクラス宣言の部分を少し変更します。

// For the Adafruit shield, these are the default.
#define TFT_DC 17
#define TFT_RST 16
#define TFT_CS 5

// Use hardware SPI (on Uno, #13, #12, #11) and the above for CS/DC
Adafruit_ILI9341 tft = Adafruit_ILI9341(TFT_CS, TFT_DC, TFT_RST);

ESP32との結線は以下のようにします。

ESP32              2.8インチLCD
3.3V------------------------VCC、LED   
GND------------------------GND
GPIO5----------------------CS
RX2(GPIO 16)--------------RESET
TX2(GPIO 17)--------------DC
GPIO23---------------------MOSI
GPIO18---------------------SCK
GPIO19---------------------MISO

ソフトを確認するとわかりますが、CS、RESET、DCに関してはおそらく
どこでもよいと思われます。その他のピンも変更できそうですが、ソフトウェア
SPIになってしまうので動作速度が遅くなるのかなと。

動作の様子は一番下に。




次に一番小さなSSD1306モジュールを使ってみます。
今回はI2C接続で3つ入りのものを買いました。一個500円ぐらいなので
かなり安いと思います。


これもライブラリマネージャからライブラリをインストールします。
Adafruit SSD1306とAdafruit GFX Libraryを入れておきます。
こちらは最新版でOKでした。

ファイル→スケッチブックの例->Adafruit SSD1306からSSD1306_128_64_I2C
を開きちょっと変更します。

変更前
#define OLED_RESET     4 // Reset pin # (or -1 if sharing Arduino reset pin)

変更後
#define OLED_RESET     -1 // Reset pin # (or -1 if sharing Arduino reset pin)


変更前
if(!display.begin(SSD1306_SWITCHCAPVCC, 0x3D)) { // Address 0x3D for 128x64

変更後
if(!display.begin(SSD1306_SWITCHCAPVCC, 0x3C)) { // Address 0x3D for 128x64

アドレスに関してはいくつか種類があるようなので注意です。

結線は以下のようになります。私の買ったものはプルアップが必要でした。

ESP32              0.96インチLCD
3.3V------------------------VCC
GND------------------------GND
GPIO21---------------------SDA(R10Kプルアップ)
GPIO22---------------------SCL(R10Kプルアップ)

これも最後に動作の動画を載せます。

最後はST7735Rディスプレイモジュールです。


これはずいぶん前に使っていたので導入を失念してしまいました。
あれほど証拠残しつつ作れと... とりあえず動いているプログラム晒しますので
何かの参考になれば。ライブラリはAdafruit ST7735で検索すれば出てくる
かなと思います。


表示テストの動画です。


ST7735がやけに表示が遅いのはソフトウェアSPIになってしまっているためと思われます。



今回購入した2.8インチLCDと0.96インチLCDには満足しています。
この二つを使い分けることでやりたいことをかなり広い範囲カバーできるでは
と考えています。ロボットのデバック用などとりあえず表示が欲しい場合は
小さくて配線の少ない0.96インチのものを。ディスプレイをメインに据えたものを
作りたいときは2.8インチのものを使用するといいかなと思います。

参考にさせて頂いたサイト




2020年6月21日日曜日

三軸カメラユニットの製作(3) 回路設計

三軸カメラユニットの三回目です。
前回でハードはほとんど完成したので、今回は制御回路と
回路を固定するコントロールボックスを製作します。



オン飲みロボットのときKONDOサーボの制御にICS変換基盤を使用しました。
これは使いやすいのですが、一個\2000近くするため自作したいと思います。
たくさん使う予定があるため設計にはEAGLEを使い、ELECROWに
外注します。

ここに過去に発注した時の記事があります。

これがICS変換基板を参考に作成した回路図です。サイトには抵抗だけで作る
回路が記載してありましたが、受信がうまくいきませんでした。
そのため実績あるICS変換基板の回路をほぼそののまま流用して作成しました。




配線図です。自動配線でサクッと作りました。小さすぎると発注できません
ので、無駄に多面付けにして大きくします。ElecrowのVカットの仕様が
イマイチ理解できませんでしたが、外に捨て基板を設け上面シルクに切りたい
線を入れれば良かったようです。あとは発注画面でコメントを入れておけば
親切かも。




完成した基板です。基板サイズ100×100mm×5個・OCS/ANAの設定
で約2000円、一週間ぐらいでできます。不安だったVカットの部分も特に
不満なく仕上がりました。



ここからコントローラ基板を作ります。CPUはESP32をモジュールで使用。
簡易インターフェースとしてボタン×3、LED×3を搭載します。
また、ここからはユニバーサル基板で作ります。





ユニバーサル基板で作るとは言え一応回路図と配線図を作りました。
インターフェース部とCPU部は分けて配線することにしました。



また、コントローラ基板を乗っけるためのコントローラBOXを設計します。




できました。パン軸サーボモータの横に設置しました。ちょっと大きく
なっちゃったかな?




実際に作ったもの。USBやサーボ配線を通す穴の位置が若干合わず
少しやすり掛けが必要でしたが、大枠はうまくいきました。




実際に乗っけてみた様子です。ついでに配線もきれいに直しました。
狙っていたとおりLED光がPLA材で拡散してきれいに見えます。
しかし、機能の割にはちょっと大きいです。まあ実用には影響ないのでヨシ。



次回はいよいよソフトを作っていきます。










2020年6月15日月曜日

三軸カメラユニットの製作(2) ハード設計2

どうも、caketetuです。
前回に引き続きハード設計と3Dプリンタで出力をしていきたいと思います。

前回設計したものを出力していきます。


我が家で使っている3DプリンタはANYCUBIC I3 MEGAです。
Amazonで買ったのですがリンクが切れていました。もう売ってないのでしょうか?
一年ぐらい使用していますが、特に問題なく動いてくれています。



今回使用するパーツ達です。白いものが3Dプリンタで作ったものです。
こうしてみると自分で作ったパーツは少ないですね。出力にかかった時間も
4時間ぐらいでしょうか。



サクサク組み立てていきます。
ここまでで完成したものは以下になります。
全軸が干渉なく±90度は動いてくれます。3Dプリンタを買って早一年、
ハード設計も慣れてきたなあと思います。




ここから土台の部分とロール軸を換装して市販のスペアシューを取り付ける
ユニットを設計していきます。




じゃん!スペアシューはこれを使いました。下の台形部分の寸法はおそらく
共通規格かと思います。ロール軸は犠牲になりますが、市販のカメラや
GoPro、スマートフォンを取り付けられるようになります。


シューの固定部です。右のロックパーツを横からねじ止めします。

土台部です。中央のナットでスペアシューに固定します。
これで市販の三脚にも対応できるはず。KRS2572HVは軸の反対側に
コネクタがついているのでこの向きで取り付けるためにはどうしても
少し浮かせなければなりません。そのため箱状になっています。


これらも3Dプリンタで作っていきます。できたものの動画がこちら




その他ハード設計が必要なところに回路のマウントがありますが、回路が
決定してから製作することにします。次回は回路とソフトを仮組して
動かしていきたいと思います。









三軸カメラユニットの製作(1) ハード設計

どうもcaketetuです。

前回、KONDOサーボとESP32マイコンを使用してスマホをそのまま搭載できる
ロボットを製作しました。今回はそれを応用してブログ投稿やYouTubeの投稿
で使えそうな三軸カメラユニットを作ってみたいと思います。



じゃん!サクッと設計しました。
サーボはオン飲みロボットと同じくKONDO KRS2572HVを使用します。
カメラはAMAZONで買ったUSBカメラを使用します。超小型カメラ

軸はパン、チルト、ロールの三自由度あります。これによりかなり自由度の高い
動画が取れるようになるはず。カメラの3Dデータは自分で作りましたが、
サーボはKONDO公式サイトからSTEPファイルをダウンロードできます。


正面からの図です。一見いびつな形に見えますが、カメラの軸とパン軸が
一直線上になるようにしました。


サーボのギアを固定するロック機構です。CAD図にはありませんが、横の穴から
ネジで締め付けることで固定します。ギアが金属で3DプリンタのPLA材のほうが
柔らかいので、内径をちょっと小さめに作って押し込むことでギアの跡がつき、
固定できると考えました。これがうまくいけばサーボホーンを自由自在に作ること
ができるので設計の幅が広がります。


CAD上で動かしている動画です。
いつも使っているCADはDesignSpark Mechanical4.0です。
直感的に使えて良いですが、機能が物足りないので乗り換えを検討している
ところです。






今回はここまでです。ハード的にはまだいくつかやることが残っていますが、
ここまでの設計でとりあえず動かせるところまで行けると思うので
3Dプリンタを動かしながら残りを設計していこうと思います。




2020年6月7日日曜日

スマホ搭載可ラジコンロボットを作る 回路・ソフト編

前回の記事の続きです。今回は回路・ソフト編です。

アクチュエータは駆動部にサーボ2個、スマホホルダ部にサーボ二個(同軸)
なのでそれらをPS3コントローラから操作できるようにします。
KONDOの同じサーボのみ使用しているのでかなり単純にできます。

・回路
KONDOのICS通信は一線式、マイコンはTxとRxの二線式なので
回路上で対応が必要です。今回はKONDO純正のICS変換基盤を使用
しました。純正なので簡単&安心ですが、値段がちょっと高いので
次回は自作したいところです。

回路も基盤の取説に乗っていたものをそのまま使います。
下記の通りに接続します。
ESP32              ICS変換基盤
Vin------------------------- Vout
3.3V------------------------IOREF   
GND------------------------GND
RX2(GPIO 16)--------------TX
TX2(GPIO 17)--------------RX
GPIO 2----------------------EN_IN

シリアルポート2を使用しましたが、たぶん他のポートでも大丈夫です。
一点、ICS変換基盤のピンヘッダは逆接続ができるようになっているのでサーボ
を接続するときは注意が必要です。電源側が信号線になります。




・ソフト
開発環境はArduinoIDEのESP32ライブラリを使用します。
追加が必要なライブラリはPs3ControllerとICS_Classの二つです。
Ps3Controllerはライブラリマネージャから、ICS_ClassはKONDOの
サイトからダウンロードできます。

ICSクラスは公式に詳しく説明があるのでそちらを参照のこと。
Ps3Controllerの使い方はExsampleを見れば大体わかりますが

Ps3.begin(PS3のMACアドレス);

をsetup関数で唱えてると接続待ち状態になります。

Ps3.isConnected()

で接続状態を監視し、接続されていたらPs3.dataクラスにデータが
入っていますので

int rx = Ps3.data.analog.stick.rx;

のようにしてボタンの状態を取得します。あとはボタン状態によって
処理を書くだけです。ESP32+PS3コントローラで簡単にラジコンロボット
を作れますね。

ちょっとつまずいたのは駆動の部分です。アナログスティックのx軸値、y軸値
から車輪二つの回転数に変換するのですが、より直感的に操作できるように
するにはどうするか悩みました。

最終的にtan^-1で極座標変換して角度を出し、角度によって
左右の回転数を変える処理にしました。これではその場で回転できま
せんのでL2とR2を超信知旋回に割り当てています。
私はこれが操作しやすいと思いますが、ボタンはたくさんありますので
自分に合わせた操作方法を作っていくのも楽しいと思います。
今回使用したプログラム



今回はスマホをそのまま搭載できるラジコンを作ったらいろいろ使えるんじゃないか?
というアイデアでサクサク製作しました。簡単ハードで特に目新しい部分は
なかったですが、ESP32+サーボ+PS3コントローラの組み合わせはボタン
の割り当てと制御部分の作り方次第で二足歩行ロボットやアームロボットにも
使えるのでかなり応用性が高いと思います。

動作の様子








スマホ搭載可ラジコンロボットを作る ハード編

お久しぶりです。
コロナショックで自宅作業(趣味)が非常に多いcaketetuです。
昨年は新しい環境になったこともありいろいろ忙しかったので
更新できませんでしたが、電子工作は細々してました。

今回は最近作ったスマホ搭載ラジコンの解説ハード編です。






 


解説と言っても特に目新しいところは無いです。
駆動部は差動二輪型、モータはKONDO KRS2572HVを回転モードで使用。
スマホホルダー部もモータは KRS2572HV×2をダブルサーボとして
使用しています。

ちなみに操作はPS3コントローラを使用。



横からの図。スマホホルダーはタイミングプーリを介してサーボに接続されて
おり、角度を変えることができます。正直ダイレクトドライブで良かった
ですが、タイミングプーリを使った機構を使いたかったのでこのように
しました。

↓頑張ったプーリー部です↓





3Dプリンタも工夫すれば精度のいいパーツも作れるんですね~。






上から。CPUはESP32モジュールを使用。BLEとWiFiを簡単に使用できるうえ
ライブラリも充実・IOもそこそこ多いということでかなり多用しております。
詳しくはソフト編で紹介しますが、シリアルサーボを使用しているので
回路もソフトも非常に簡単に済みます。(値段は高くなりますが)



このロボットの使い方としてはスマホのカメラをネットワークカメラにできる
ソフトがあるので、それをら使用して監視カメラやオンライン飲み会のおもちゃ
として活躍できるはず。私もオン飲みで使用しましたが自宅を探索させたりして
結構盛り上がりました。

動きはこんな感じ。




次回、回路&ソフト編です。