LoginSignup
2
2

More than 5 years have passed since last update.

iPhoneを使ってM5BALAを操作する(Pythonista-BLE編)

Last updated at Posted at 2018-11-07

概要

iPhone を使って M5BALA を操作する、の4回目は Pythonista を使いBLE経由で M5BALA を操作します。iPhone を使ってBLE経由で M5BALA を動かす仕組みとしては前回の Blynk を使用する方法が手っ取り早いですが、お勉強ということで。

環境

実行例

iPhone画面表示例
IMG_1881.png

M5BALA制御用プログラム

以下のサンプルプログラムを一部修正して使用します。

https://github.com/m5stack/M5Bala/tree/master/src
Default_firmware.ino
M5Bala.cpp
M5Bala.h

Default_firmware.ino
    :    :    :
// setup() の修正および BLE Setup の呼び出しを追加
void setup() {
    :    :    :
  //M5.setPowerBoostKeepOnは未定義のエラーになるのでコメントアウト
  //M5.setPowerBoostKeepOn(false);
    :    :    :
  // BLE Setup
  BLE_setup();
}

// loop() に BLE Loop の呼び出しを追加
void loop() {
    :    :    :
  // BLE Loop
  BLE_loop();
    :    :    :
}

// BLE関連の処理を追加

///////////////////
// Bluetooth LE  //
///////////////////
#include <BLEDevice.h>
#include <BLEServer.h>
#include <BLEUtils.h>

BLEServer *pServer = NULL;
BLECharacteristic *pCharacteristic;
bool deviceConnected = false;
bool oldDeviceConnected = false;

int16_t m5bala_move = 0;
int16_t m5bala_turn = 0;
int16_t last_m5bala_move = 0;
int16_t last_m5bala_turn = 0;

#define LOCAL_NAME "M5BALA"

// See the following for generating UUIDs:
// https://www.uuidgenerator.net/
#define SERVICE_UUID          "c44205a6-c87c-11e8-a8d5-f2801f1b9fd1"
#define CHARACTERISTIC_UUID   "c442090c-c87c-11e8-a8d5-f2801f1b9fd1"

// Bluetooth LE Change Connect State
class MyServerCallbacks: public BLEServerCallbacks {
    void onConnect(BLEServer* pServer) {
        Serial.println("Device Connected");
        deviceConnected = true;
    };

    void onDisconnect(BLEServer* pServer) {
        Serial.println("Device Disconnected");
        deviceConnected = false;
    }
};

class MyCallbacks: public BLECharacteristicCallbacks {
    void onWrite(BLECharacteristic *pChar) {
        std::string rxValue = pCharacteristic->getValue();
        if (rxValue.length() == 4) {
            int16_t move = *(int16_t *)&rxValue[0];
            int16_t turn = *(int16_t *)&rxValue[2];

            m5bala_move = move;
            m5bala_turn = turn;
        }
    }
};

// Bluetooth LE initialize
void BLE_setup() {
    // Create the BLE Device
    BLEDevice::init(LOCAL_NAME);

    // Create the BLE Server
    pServer = BLEDevice::createServer();
    pServer->setCallbacks(new MyServerCallbacks());

    // Create the BLE Service
    BLEService *pService = pServer->createService(SERVICE_UUID);

    // Create a BLE Characteristic
    pCharacteristic = pService->createCharacteristic(
                          CHARACTERISTIC_UUID,
                          BLECharacteristic::PROPERTY_WRITE);
    pCharacteristic->setCallbacks(new MyCallbacks());  // コールバック関数を設定

    // Start the service
    pService->start();

    // Start advertising
    pServer->getAdvertising()->start();
    //pServer->startAdvertising();
    Serial.println("Start Advertising");

    M5.Lcd.setCursor(220, 0*18); M5.Lcd.print("Not Connected");
    M5.Lcd.setCursor(230, 1*18); M5.Lcd.print("Move :");
    M5.Lcd.setCursor(230, 2*18); M5.Lcd.print("Turn :");
  }

// Bluetooth LE loop
void BLE_loop() {
    // disconnecting
    if (!deviceConnected && oldDeviceConnected) {
        M5.Lcd.setCursor(220, 0*18); M5.Lcd.print("Not Connected");
        delay(500); // give the bluetooth stack the chance to get things ready
        pServer->startAdvertising(); // restart advertising
        Serial.println("Start Advertising");
        oldDeviceConnected = deviceConnected;
        m5bala_move = 0;
        m5bala_turn = 0;
    }
    // connecting
    if (deviceConnected && !oldDeviceConnected) {
        M5.Lcd.setCursor(220, 0*18); M5.Lcd.print("Connected    ");
        // do stuff here on connecting
        oldDeviceConnected = deviceConnected;
    }
    if (m5bala_move != last_m5bala_move) {
        M5.Lcd.setCursor(275, 1*18); M5.Lcd.printf("%4d ", m5bala_move);
        m5bala.move(m5bala_move);
        last_m5bala_move = m5bala_move;
    }
    if (m5bala_turn != last_m5bala_turn) {
        M5.Lcd.setCursor(275, 2*18); M5.Lcd.printf("%4d ", m5bala_turn);
        if (m5bala_turn == 0) {
          m5bala.stop();
        } else {
          m5bala.turn(m5bala_turn);
        }
        last_m5bala_turn = m5bala_turn;
    }
}

Pythonista3プログラム

iPhoneの前後左右の傾きに応じて、左右の車輪を動かすための指示をBLE経由で送信する仕組みです。

m5bala_controller_ble.py
import motion
import time
import math
import ui
import cb
import struct

M5BALA_SERVICE_UUID        = 'c44205a6-c87c-11e8-a8d5-f2801f1b9fd1'.upper()
M5BALA_CHARACTERISTIC_UUID = 'c442090c-c87c-11e8-a8d5-f2801f1b9fd1'.upper()
M5BALA_NAME = 'M5BALA'

class MyCentralManagerDelegate (object):
    def __init__(self):
        self.peripheral = None
        self.data_characteristics = None

    def did_discover_peripheral(self, p):
        if p.name and M5BALA_NAME in p.name and not self.peripheral:
            # Keep a reference to the peripheral, so it doesn't get garbage-collected:
            print('+++ Discovered peripheral: %s (%s)' % (p.name, p.uuid))
            self.peripheral = p
            cb.connect_peripheral(self.peripheral)
            view['status'].text = 'Detected'

    def did_connect_peripheral(self, p):
        print('*** Connected: %s' % p.name)
        print('Discovering services...')
        view['status'].text = 'Connected'
        p.discover_services()

    def did_fail_to_connect_peripheral(self, p, error):
        print('Failed to connect')
        view['status'].text = 'Failed'

    def did_disconnect_peripheral(self, p, error):
        print('Disconnected, error: %s' % (error,))
        self.peripheral = None
        self.data_characteristics = None
        view['status'].text = 'Scanning'
        cb.scan_for_peripherals()

    def did_discover_services(self, p, error):
        for s in p.services:
            print(s.uuid)
            if M5BALA_SERVICE_UUID in s.uuid:
                print('M5BALA found')
                p.discover_characteristics(s)

    def did_discover_characteristics(self, s, error):
        if M5BALA_SERVICE_UUID in s.uuid:
            for c in s.characteristics:
                if M5BALA_CHARACTERISTIC_UUID in c.uuid:
                    self.data_characteristics = c

    def send_action(self, move, turn):
        if self.peripheral and self.data_characteristics:
            data = struct.pack('hh', move, turn)
            #print('{:04x} {:04x} {:4d} {:4d}'.format(int(move) & 0xffff, int(turn) & 0xffff, int(move), int(turn)))
            #print('{:4d} {:4d}'.format(move, turn))
            self.peripheral.write_characteristic_value(self.data_characteristics, data, True)


def end_controller(sender):
    global action_loop
    sender.superview.close()
    action_loop = False

view = ui.load_view()
view.present('sheet')

delegate = MyCentralManagerDelegate()
print('Scanning for peripherals...')
view['status'].text = 'Scanning'
cb.set_central_delegate(delegate)
cb.scan_for_peripherals()

motion.start_updates()

action_loop = True
# Keep the connection alive until the 'Stop' button is pressed:
try:
    while action_loop:
        roll, pitch, yaw = motion.get_attitude()
        yaw   = math.degrees(yaw)
        pitch = math.degrees(pitch)
        roll  = math.degrees(roll)

        view['yaw'].text   = '{:.2f}'.format(yaw)
        view['pitch'].text = '{:.2f}'.format(pitch)
        view['roll'].text  = '{:.2f}'.format(roll)

        move = int(-max(-60, min(60, pitch)) / 60 * 100)
        turn = int( max(-60, min(60, roll))  / 60 * 100)
        view['move'].text = '{}'.format(move)
        view['turn'].text = '{}'.format(turn)
        delegate.send_action(move, turn)
        time.sleep(0.5)
except KeyboardInterrupt:
    pass

# Disconnect everything:
cb.reset()

Pythonista3 UI

m5bala_controller_ble.pyui
[
  {
    "nodes" : [
      {
        "nodes" : [

        ],
        "frame" : "{{103, 27}, {60, 32}}",
        "class" : "Label",
        "attributes" : {
          "name" : "label1",
          "frame" : "{{82, 125}, {150, 32}}",
          "uuid" : "5144134F-E68A-4023-A81E-A67824AF01CA",
          "class" : "Label",
          "alignment" : "right",
          "text" : "方角",
          "font_size" : 24,
          "font_name" : "<System>"
        },
        "selected" : false
      },
      {
        "nodes" : [

        ],
        "frame" : "{{171, 27}, {103, 32}}",
        "class" : "Label",
        "attributes" : {
          "name" : "yaw",
          "frame" : "{{82, 125}, {150, 32}}",
          "uuid" : "F6954500-8FAB-4E42-B4A6-29A7865C50F9",
          "class" : "Label",
          "alignment" : "right",
          "text" : "",
          "font_size" : 24,
          "font_name" : "<System>"
        },
        "selected" : false
      },
      {
        "nodes" : [

        ],
        "frame" : "{{103, 67}, {60, 32}}",
        "class" : "Label",
        "attributes" : {
          "name" : "label2",
          "frame" : "{{82, 125}, {150, 32}}",
          "uuid" : "01A7842C-1680-4655-9F09-F3B52A0721B6",
          "class" : "Label",
          "alignment" : "right",
          "text" : "前後",
          "font_size" : 24,
          "font_name" : "<System>"
        },
        "selected" : false
      },
      {
        "nodes" : [

        ],
        "frame" : "{{171, 67}, {103, 32}}",
        "class" : "Label",
        "attributes" : {
          "name" : "pitch",
          "frame" : "{{82, 125}, {150, 32}}",
          "uuid" : "A73FDD85-DBEE-4857-9B9E-39B13FEEA553",
          "class" : "Label",
          "alignment" : "right",
          "text" : "",
          "font_size" : 24,
          "font_name" : "<System>"
        },
        "selected" : false
      },
      {
        "nodes" : [

        ],
        "frame" : "{{103, 107}, {60, 32}}",
        "class" : "Label",
        "attributes" : {
          "name" : "label3",
          "frame" : "{{82, 125}, {150, 32}}",
          "uuid" : "A5EF17D2-3351-4AA3-B918-A82382B2DF4D",
          "class" : "Label",
          "alignment" : "right",
          "text" : "左右",
          "font_size" : 24,
          "font_name" : "<System>"
        },
        "selected" : false
      },
      {
        "nodes" : [

        ],
        "frame" : "{{171, 107}, {103, 32}}",
        "class" : "Label",
        "attributes" : {
          "name" : "roll",
          "frame" : "{{82, 125}, {150, 32}}",
          "uuid" : "7744E0CF-F2CD-4985-AAD3-A68158E7F67D",
          "class" : "Label",
          "alignment" : "right",
          "text" : "",
          "font_size" : 24,
          "font_name" : "<System>"
        },
        "selected" : false
      },
      {
        "nodes" : [

        ],
        "frame" : "{{96, 160}, {67, 45}}",
        "class" : "Label",
        "attributes" : {
          "font_size" : 32,
          "frame" : "{{111, 149}, {150, 32}}",
          "uuid" : "9B0F3B32-82ED-4C90-A99C-9E827DCB1DE6",
          "class" : "Label",
          "alignment" : "right",
          "text" : "移動",
          "name" : "label4",
          "font_name" : "<System>"
        },
        "selected" : false
      },
      {
        "nodes" : [

        ],
        "frame" : "{{171, 160}, {103, 45}}",
        "class" : "Label",
        "attributes" : {
          "font_size" : 32,
          "frame" : "{{111, 149}, {150, 32}}",
          "uuid" : "4D458FEF-D620-49B5-A99E-DCA9B9988380",
          "class" : "Label",
          "alignment" : "right",
          "text" : "",
          "name" : "move",
          "font_name" : "<System>"
        },
        "selected" : false
      },
      {
        "nodes" : [

        ],
        "frame" : "{{96, 225}, {67, 45}}",
        "class" : "Label",
        "attributes" : {
          "font_size" : 32,
          "frame" : "{{111, 149}, {150, 32}}",
          "uuid" : "E4C08BE2-1406-4855-8DCD-539F4E46676B",
          "class" : "Label",
          "alignment" : "right",
          "text" : "回転",
          "name" : "label5",
          "font_name" : "<System>"
        },
        "selected" : false
      },
      {
        "nodes" : [

        ],
        "frame" : "{{171, 225}, {103, 45}}",
        "class" : "Label",
        "attributes" : {
          "font_size" : 32,
          "frame" : "{{111, 149}, {150, 32}}",
          "uuid" : "DC741DAD-FA01-43E7-B4C6-3FBD6699E467",
          "class" : "Label",
          "alignment" : "right",
          "text" : "",
          "name" : "turn",
          "font_name" : "<System>"
        },
        "selected" : false
      },
      {
        "nodes" : [

        ],
        "frame" : "{{71, 301}, {218, 32}}",
        "class" : "Label",
        "attributes" : {
          "name" : "status",
          "frame" : "{{111, 149}, {150, 32}}",
          "uuid" : "42BB3ACF-D696-434B-A296-9EEEEC13DC27",
          "class" : "Label",
          "alignment" : "center",
          "text" : "Status",
          "font_size" : 24,
          "font_name" : "<System>"
        },
        "selected" : false
      },
      {
        "nodes" : [

        ],
        "frame" : "{{140, 365}, {87, 37}}",
        "class" : "Button",
        "attributes" : {
          "action" : "end_controller",
          "border_width" : 1,
          "frame" : "{{146, 149}, {80, 32}}",
          "title" : "終了",
          "uuid" : "0EC4234B-2C78-4812-89A4-236FB5384680",
          "class" : "Button",
          "corner_radius" : 8,
          "font_bold" : true,
          "name" : "button1",
          "font_size" : 24
        },
        "selected" : false
      }
    ],
    "frame" : "{{0, 0}, {350, 460}}",
    "class" : "View",
    "attributes" : {
      "border_color" : "RGBA(0.000000,0.000000,0.000000,1.000000)",
      "enabled" : true,
      "background_color" : "RGBA(1.000000,1.000000,1.000000,1.000000)",
      "name" : "M5BALA Controller",
      "tint_color" : "RGBA(0.000000,0.478000,1.000000,1.000000)",
      "flex" : ""
    },
    "selected" : false
  }
]
2
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
2