tktk360のアイコン画像

綱渡りサンタ

tktk360 2020年12月28日に作成  (2020年12月30日に更新)
綱渡りサンタ

作成の動機

  • クリスマスの飾りつけをしていて、何か物足りないと思い、
    電子工作好きなら、家庭にもあるかもしれないもので、動くサンタの玩具を作ってみました。
    家族団らんの場で、父の威厳を取り戻すこと間違いなし!?(責任はとれませんのであしからず)

できたもの「綱渡りサンタ」

  • M5Stick Cを搭載したサンタが、綱を渡っていきます。
    操作は、Androidアプリケーションを使い、BLE通信でON/OFFを切替えさせます。
    M5Santa 動画

M5 Santa

スマホアプリ画面

  • BLE Start ボタンで、接続を開始
  • ToggleSwitch ボタンを、押す毎に、サンタの動作のON/OFFの切替え

パーツ

  • 作成に使用したパーツは下記となります。
品目 価格
M5StickC 1,650
電動ロボットキット 850
GROVE - リレー 440
単三電池2本 110
プラ板 110
カラー紙 50
Android(操作用) -
Bluetooth LE for iOS, tvOS and Android 2400
合計 5610

M5Stick C & GROVE リレー

電動ロボットキット

  • 同じものを買わなくても、DCモーターと、電池ボックスを使って、
    動かす部分を自作できれば良し。

綱渡りサンタ2020の電子工作

  • 電子工作部分は、M5StickCとGROVEのリレーが中心となります。
    他の部品は家にあるもので自由に置き換えてもらって構いません。

  • 【作成手順】
    1.電動ロボットのキットを組み立てます
     ・キットの説明書を参考に組み立ててください
    2.M5Stick CとGROVEリレーを付属のGROVEの専用線でつなげます
     ・GROVEの専用線はコネクタの形に合わせて接続するため、向きのさし間違えはありません
    3.電子部品を接着します
     ・DCモーターの全面に、両面テープで、M5Stick Cを接着します
     ・DCモーターの背面に、両面テープで、GROVEリレーを接着します
    4.DCモーターと電池ボックス、GROVEリレーをつなげます
    5.デコレーション
     ・サンタのイラストを作成し、印刷してください
     ・印刷した紙をプラ板に張り付け、カットしてください
     ・カットしたプラ板を電子工作したものに貼り付けてください

3.M5Stick CとGROVEリレー:上から

3.M5Stick CとGROVEリレー:横から

4.配線図

5.サンタのイラスト

5.イラストをプラ板に張付け
綱渡りサンタ2020の全プログラム
Github

  • BLE通信は、電子工作のサンタが「ペリフェラル」、Androidアプリケーションが「セントラル」となります。
    BLE通信は、ペリフェラルとセントラルで、UUIDを揃えてください。

  • UUIDの作成は、Online UUID Generatorを使い生成しました。
    値を変更する場合は、URL内の「Version 1 UUID Generator」の「How Many?」を「3」にし、Generateボタンで3つUUIDを作成してください。

綱渡りサンタ2020のマイコンプログラム

  • プログラムは、Arudino IDEを使用しています。
    下記コードで完結しているため、そのまま利用可能です。
    注意点は、GROVEのリレー操作は、ピン番号が「33」です。「32」ではありません。
    「BLE_NAME 」「SERVICE_UUID 」「CHARACTERISTIC_UUID_NOTIFY」「CHARACTERISTIC_UUID_RX 」の値は、「M5SantaBleScript.cs」と合わせて下さい

M5Stick-Cのプログラム(M5Santa.ino)

#include <M5StickC.h> // Bluetooth LE #include <BLEDevice.h> #include <BLEServer.h> #include <BLEUtils.h> #include <BLE2902.h> #define BLE_NAME "M5Santa" #define SERVICE_UUID "8f6156ca-48cc-11eb-b378-0242ac130002" #define CHARACTERISTIC_UUID_NOTIFY "8f61590e-48cc-11eb-b378-0242ac130002" #define CHARACTERISTIC_UUID_RX "8f615b70-48cc-11eb-b378-0242ac130002" //Relay #define RELAY_PIN 33 //----------------------------------------------------------------------------- //Relay //----------------------------------------------------------------------------- //button bool _isRelay = false; uint16_t getColor(uint8_t red, uint8_t green, uint8_t blue) { return ((red>>3)<<11) | ((green>>2)<<5) | (blue>>3); } void RelayON() { digitalWrite(RELAY_PIN, HIGH); M5.Lcd.fillScreen(getColor(255,0,0)); Serial.println("ON"); } void RelayOFF() { digitalWrite(RELAY_PIN, LOW); M5.Lcd.fillScreen(getColor(0,0,255)); Serial.println("OFF"); } //----------------------------------------------------------------------------- //BLE //----------------------------------------------------------------------------- BLEServer *pServer = NULL; BLECharacteristic * pNotifyCharacteristic; bool deviceConnected = false; bool oldDeviceConnected = false; // Bluetooth LE Change Connect State class SantaBleServerCallbacks: public BLEServerCallbacks { void onConnect(BLEServer* pServer) { deviceConnected = true; }; void onDisconnect(BLEServer* pServer) { deviceConnected = false; } }; // Bluetooth LE Recive class SantaBleCallbacks: public BLECharacteristicCallbacks { void onWrite(BLECharacteristic *pCharacteristic) { std::string rxValue = pCharacteristic->getValue(); if (rxValue.length() <= 0) { return; } String cmd = String(rxValue.c_str()); Serial.print("Received Value: "); Serial.println(cmd); //Function if (cmd == "1") { RelayON(); } else if (cmd == "0") { RelayOFF(); } } }; // Bluetooth LE initialize void InitBLE() { // Create the BLE Device BLEDevice::init(BLE_NAME); // Create the BLE Server pServer = BLEDevice::createServer(); pServer->setCallbacks(new SantaBleServerCallbacks()); // Create the BLE Service BLEService *pService = pServer->createService(SERVICE_UUID); // Create a BLE Characteristic pNotifyCharacteristic = pService->createCharacteristic( CHARACTERISTIC_UUID_NOTIFY, BLECharacteristic::PROPERTY_NOTIFY ); pNotifyCharacteristic->addDescriptor(new BLE2902()); BLECharacteristic * pRxCharacteristic = pService->createCharacteristic( CHARACTERISTIC_UUID_RX, BLECharacteristic::PROPERTY_WRITE ); pRxCharacteristic->setCallbacks(new SantaBleCallbacks()); // Start the service pService->start(); // Start advertising pServer->getAdvertising()->start(); } // Bluetooth LE loop void LoopBLE() { // disconnecting if (!deviceConnected && oldDeviceConnected) { // give the bluetooth stack the chance to get things ready delay(500); // restart advertising pServer->startAdvertising(); Serial.println("startAdvertising"); oldDeviceConnected = deviceConnected; } // connecting if (deviceConnected && !oldDeviceConnected) { // do stuff here on connecting oldDeviceConnected = deviceConnected; } } //----------------------------------------------------------------------------- //Main //----------------------------------------------------------------------------- void setup() { M5.begin(); //Relay pinMode(RELAY_PIN, OUTPUT); RelayOFF(); //Ble InitBLE(); } void loop() { M5.update(); //Button if (M5.BtnA.wasReleased()) { _isRelay = !_isRelay; if (_isRelay) { RelayON(); } else { RelayOFF(); } } //BLE LoopBLE(); }

綱渡りサンタ2020のAndroidプログラム

  • プログラムは、Unityを使用しています。
    事前にUnityがインストールされた環境での説明となります。
    BLE通信は、有料のAssetを使用して実現しています。
    腕に自信がある人は、自作しても構いませんが、日曜プログラマーは、お金で時間を買うのも選択肢としてありだと思います。

  • 【作成手順】
    1.Githubからソースコードをダウンロードしてください
    2.Unityを起動し、プロジェクトを読み込んでください
      フォルダ階層は、ダウンロードした「M5Santa\Unity」になります
    3.AssetStoreで購入した「Bluetooth LE for iOS, tvOS and Android」を、プロジェクトにImportしてください。
    4.Unityのプログラムシーン「Assets\Scenes」を開いてください
    5.Androidプロジェクトで出力してください
    6.Androidスマートフォンにインストールしてください

  • コードの説明
    ・購入したAssetのSampleコードを流用し、少ないプログラム数で実現させるようにしているため、コードが煩雑となっています。
    ・「DeviceName」「ServiceUUID」「SubscribeCharacteristic」「WriteCharacteristic」の値は、「M5Santa.ino」と合わせて下さい
    ・BLE接続開始:UGUIのボタンに「StartProcess」をアタッチして実現しています。
    ・操作のON/OFF:UGUIのボタンに「WriteToggleData」をアタッチして実現しています。

M5Santaのメインスクリプト(M5SantaBleScript.cs)

using UnityEngine; using UnityEngine.UI; public class M5SantaBleScript : MonoBehaviour { protected string DeviceName = "M5Santa"; protected string ServiceUUID = "8f6156ca-48cc-11eb-b378-0242ac130002"; protected string SubscribeCharacteristic = "8f61590e-48cc-11eb-b378-0242ac130002"; protected string WriteCharacteristic = "8f615b70-48cc-11eb-b378-0242ac130002"; public Text StateText; public Text LogText; public Text Log2Text; protected string _deviceName = string.Empty; protected string _serviceName = string.Empty; private bool _connected = false; private float _timeout = 0f; private States _state = States.None; private string _deviceAddress; private bool _foundSubscribeID = false; private bool _foundWriteID = false; private byte[] _dataBytes = null; private bool _rssiOnly = false; private int _rssi = 0; private bool _isON = false; public AudioSource audioSound; /// <summary> /// States /// </summary> public enum States { None, Scan, ScanRSSI, Connect, Subscribe, Unsubscribe, Disconnect, Write, } public States State { get { return _state; } } /// <summary> /// Update /// </summary> void Update() { if (_timeout > 0f) { _timeout -= Time.deltaTime; if (_timeout > 0f) { return; } _timeout = 0f; switch (_state) { //None case States.None: break; //Scan case States.Scan: BluetoothLEHardwareInterface.ScanForPeripheralsWithServices(null, (address, name) => { // デバイスがrssiおよびメーカー固有のデータをアドバタイズしない場合、次のコールバックはメーカー固有のデータがある場合にのみ呼び出されるため、このコールバックを使用する必要があります。 _deviceName += string.Format(",{0} ", name); if (!_rssiOnly) { if (name.Contains(DeviceName)) { _deviceName += string.Format("[FIND]-{0}\n", _deviceAddress); BluetoothLEHardwareInterface.StopScan(); _deviceAddress = address; SetState(States.Connect, 0.5f); } else { _deviceName += "\n"; } } if (LogText != null) { LogText.text = _deviceName; } }, (address, name, rssi, bytes) => { // デバイスがメーカー固有のデータとrssiで応答する場合、これを使用します if (name.Contains(DeviceName)) { if (_rssiOnly) { _rssi = rssi; } else { BluetoothLEHardwareInterface.StopScan(); _deviceAddress = address; SetState(States.Connect, 0.5f); } } }, _rssiOnly); if (_rssiOnly) { SetState(States.ScanRSSI, 0.5f); } break; //ScanRSSI case States.ScanRSSI: break; //Connect case States.Connect: // set these flags _foundSubscribeID = false; _foundWriteID = false; // 最初のパラメータは名前ではなくアドレスであることに注意してください。後方互換性のため、これは修正していません。 // また、最初の2つのコールバックを使用していることに注意してください。特定の特性を探していない場合は、最初の2つのいずれかを使用できますが、 // デバイスはすべてを列挙するので、サブスクライブする前に列挙が完了するまでタイムアウトを大きくする必要があることに注意してください。他の操作を行います。 BluetoothLEHardwareInterface.ConnectToPeripheral(_deviceAddress, null, null, (address, serviceUUID, characteristicUUID) => { _serviceName += string.Format("{0} ", serviceUUID); if (IsEqual(serviceUUID, ServiceUUID)) { _serviceName += string.Format("[FIND]-{0}\n", _deviceAddress); _foundSubscribeID = _foundSubscribeID || IsEqual(characteristicUUID, SubscribeCharacteristic); _foundWriteID = _foundWriteID || IsEqual(characteristicUUID, WriteCharacteristic); _serviceName += string.Format("<{0}>,{1},{2}\n", characteristicUUID, _foundSubscribeID, _foundWriteID); if (_foundSubscribeID) { _connected = true; SetState(States.Subscribe, 2f); } } else { _serviceName += "\n"; } if (Log2Text != null) { Log2Text.text = _serviceName; } }); break; // Subscribe case States.Subscribe: BluetoothLEHardwareInterface.SubscribeCharacteristicWithDeviceAddress(_deviceAddress, ServiceUUID, SubscribeCharacteristic, null, (address, characteristicUUID, bytes) => { _state = States.None; // we received some data from the device _dataBytes = bytes; }); break; // Unsubscribe case States.Unsubscribe: BluetoothLEHardwareInterface.UnSubscribeCharacteristic(_deviceAddress, ServiceUUID, SubscribeCharacteristic, null); SetState(States.Disconnect, 4f); break; // Disconnect case States.Disconnect: if (_connected) { BluetoothLEHardwareInterface.DisconnectPeripheral(_deviceAddress, (address) => { BluetoothLEHardwareInterface.DeInitialize(() => { _connected = false; _state = States.None; }); }); } else { BluetoothLEHardwareInterface.DeInitialize(() => { _state = States.None; }); } break; } } } /// <summary> /// Reset /// </summary> void Reset() { _deviceName = string.Empty; _serviceName = string.Empty; _connected = false; _timeout = 0f; _state = States.None; _deviceAddress = null; _foundSubscribeID = false; _foundWriteID = false; _dataBytes = null; _rssi = 0; } /// <summary> /// SetState /// </summary> /// <param name="newState"></param> /// <param name="timeout"></param> void SetState(States newState, float timeout) { _state = newState; _timeout = timeout; //State if (StateText != null) { StateText.text = _state.ToString(); } } /// <summary> /// StartProcess /// </summary> public void StartProcess() { Reset(); BluetoothLEHardwareInterface.Initialize(true, false, () => { SetState(States.Scan, 0.1f); }, (error) => { BluetoothLEHardwareInterface.Log("Error during initialize: " + error); }); } /// <summary> /// StopProcess /// </summary> public void StopProcess() { if (_state == States.ScanRSSI) { BluetoothLEHardwareInterface.StopScan(); SetState(States.Disconnect, 0.5f); } } /// <summary> /// WriteData /// </summary> /// <param name="str"></param> public void WriteToggleData() { _isON = !_isON; var strData = _isON ? "1" : "0"; //BGM if (audioSound != null) { if (_isON) { audioSound.Play(); } else { audioSound.Stop(); } } //Send Device WriteData(strData); } /// <summary> /// WriteData /// </summary> /// <param name="str"></param> public bool WriteData(string str) { if (!_connected) { return false; } SetState(States.Write, 0.5f); var data = System.Text.Encoding.UTF8.GetBytes(str); SendBytes(data); return true; } string FullUUID(string uuid) { return "0000" + uuid + "-0000-1000-8000-00805f9b34fb"; } bool IsEqual(string uuid1, string uuid2) { if (uuid1.Length == 4) { uuid1 = FullUUID(uuid1); } if (uuid2.Length == 4) { uuid2 = FullUUID(uuid2); } return (uuid1.ToUpper().CompareTo(uuid2.ToUpper()) == 0); } void SendBytes(byte[] data) { BluetoothLEHardwareInterface.WriteCharacteristic(_deviceAddress, ServiceUUID, WriteCharacteristic, data, data.Length, true, (characteristicUUID) => { BluetoothLEHardwareInterface.Log("Write Succeeded"); }); } void SendByte(byte value) { var data = new byte[] { value }; BluetoothLEHardwareInterface.WriteCharacteristic(_deviceAddress, ServiceUUID, WriteCharacteristic, data, data.Length, true, (characteristicUUID) => { BluetoothLEHardwareInterface.Log("Write Succeeded"); }); } }

最後に

  • この内容をもとに、ちょっとした改造でオリジナリティを出してみてはいかがでしょうか。
    例えば、以下のものが考えられます。是非チャレンジしてみてください。

【応用編】
・M5StickCにHATを追加する
 ・スピーカで音を鳴らす。
・Androidアプリケーションの操作方法に工夫を加える
 ・スマホを振ることで、ON/OFFを切り替える
 ・マイク音声のレベルが基準値以上になった場合にONに切り替える

tktk360 さんが 2020/12/28 に 編集 をしました。 (メッセージ: 初版)
tktk360 さんが 2020/12/28 に 編集 をしました。
tktk360 さんが 2020/12/29 に 編集 をしました。
tktk360 さんが 2020/12/30 に 編集 をしました。
ログインしてコメントを投稿する