Cocos2d-xでドローン制御してみる

#概要
Cocos2d-xでParrot社[MAMBO](https://www.parrot.com/us/minidrones/parrot-Mambo#parrot-mambo-)の制御を行ってみました。
このMAMBOはiOS、Android用の[SDK](http://developer.parrot.com/docs/SDK3/)が提供されているので、
比較的簡単に飛行制御することが出来ます。
今回は、Cocos2d-x + iOS環境で、このドローンを制御してみたいと思います。
MAMBOの接続はBlueToothを使って行います。

#環境
開発環境は以下の通りです。

- Cocos2d-x 3.13
- XCode 8.2.1
- iPod Touch 第６世代

#インストール
ダウンロードしてきたSDKをiOSフォルダに保存してください。
proj.ios_mac
└iOS
　└Framework
　　└ARSDK3_iOS_3_11_0
　　　├include
　　　└lib

Other Linker Flagsは
`-ObjC`を忘れず、その他に必要なライブラリを記述していきます。
`-larcommands -larcontroller -lardiscovery -larnetwork -larnetworkal -larsal -larstream -larstream2 -larmavlink -ljson -larmedia -larutils -lcurl -lardatatransfer -lmux -lpomp -lcrypto -lssl -lz`

`ExternalAccessory.framework`も忘れずに追加してください。
#プログラミング
ドローン検索用のViewContorollerを作成します。

```objc:DeviceListVC.h
#import <UIKit/UIKit.h>
@interface DeviceListVC : UIViewController

+(DeviceListVC*) sharedInstance;

@property BOOL isConnect;
@end
```

```objc:DeviceListVC.m
#import "DeviceListVC.h"
#import "DroneDiscoverer.h"
#import <libARDiscovery/ARDISCOVERY_BonjourDiscovery.h>
#import "MiniDrone.h"

@interface DeviceListVC () <DroneDiscovererDelegate>

@property (nonatomic, strong) NSArray *dataSource;
@property (nonatomic, strong) DroneDiscoverer *droneDiscoverer;
@property (nonatomic, strong) ARService *selectedService;

@end

static DeviceListVC *deveiceListHelper;

@implementation DeviceListVC

+(DeviceListVC*) sharedInstance
{
    @synchronized(self)
    {
        if (deveiceListHelper == nil)
        {
            deveiceListHelper = [[DeviceListVC alloc] init];
        }
        
        return deveiceListHelper;
    }
    
    // to avoid compiler warning
    return nil;
}

- (void)viewDidLoad {
    [super viewDidLoad];
    
    _dataSource = [NSArray array];
    _droneDiscoverer = [[DroneDiscoverer alloc] init];
    [_droneDiscoverer setDelegate:self];
}

- (void)didReceiveMemoryWarning {
    [super didReceiveMemoryWarning];
    // Dispose of any resources that can be recreated.
}

- (void) viewDidAppear:(BOOL)animated {
    [super viewDidAppear:animated];
    
    [self registerNotifications];
    [_droneDiscoverer startDiscovering];
}

- (void) viewDidDisappear:(BOOL)animated {
    [super viewDidDisappear:animated];
    
    [self unregisterNotifications];
    [_droneDiscoverer stopDiscovering];
}

#pragma mark DroneDiscovererDelegate
- (void)droneDiscoverer:(DroneDiscoverer *)droneDiscoverer didUpdateDronesList:(NSArray *)dronesList {
    if (_isConnect) return;
    
    _dataSource = dronesList;
    
    [dronesList enumerateObjectsUsingBlock:^(ARService *service, NSUInteger idx, BOOL * _Nonnull stop)
    {
        if (ARDISCOVERY_PRODUCT_MINIDRONE_DELOS3 == service.product) {
            if (!_isConnect) {
                // MAMBOドローン初期化
                [[MiniDrone sharedInstance] initWithService:service];
                [[MiniDrone sharedInstance] connect];
                _isConnect = YES;
            }
        }
        
    }];
    
}

#pragma mark notification registration
- (void)registerNotifications {
    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(enteredBackground:) name: UIApplicationDidEnterBackgroundNotification object: nil];
    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(enterForeground:) name: UIApplicationWillEnterForegroundNotification object: nil];
}

- (void)unregisterNotifications {
    [[NSNotificationCenter defaultCenter] removeObserver:self name: UIApplicationDidEnterBackgroundNotification object: nil];
    [[NSNotificationCenter defaultCenter] removeObserver:self name: UIApplicationWillEnterForegroundNotification object: nil];
}

#pragma mark - application notifications
- (void)enterForeground:(NSNotification*)notification {
    [_droneDiscoverer startDiscovering];
}

- (void)enteredBackground:(NSNotification*)notification {
    [_droneDiscoverer stopDiscovering];
    _isConnect = NO;
}
@end 
```
次にドローンを見つけるための処理

```objc:DroneDiscoverer.h
#import <Foundation/Foundation.h>

@class DroneDiscoverer;

@protocol DroneDiscovererDelegate<NSObject>

/**
 * Called when the device found list is updated
 * Called on the main thread
 * @param droneDiscoverer the drone discoverer concerned
 * @param dronesList the list of found ARService
 */
- (void)droneDiscoverer:(DroneDiscoverer*)droneDiscoverer didUpdateDronesList:(NSArray*)dronesList;

@end

@interface DroneDiscoverer : NSObject

@property (nonatomic, weak) id<DroneDiscovererDelegate> delegate;

- (void)startDiscovering;
- (void)stopDiscovering;

@end
```

```objc:DroneDiscoverer.m
#import "DroneDiscoverer.h"
#import <libARDiscovery/ARDISCOVERY_BonjourDiscovery.h>

@implementation DroneDiscoverer

- (void)setDelegate:(id<DroneDiscovererDelegate>)delegate {
    _delegate = delegate;
    
    if (_delegate && [_delegate respondsToSelector:@selector(droneDiscoverer:didUpdateDronesList:)]) {
        [_delegate droneDiscoverer:self didUpdateDronesList:[[ARDiscovery sharedInstance] getCurrentListOfDevicesServices]];
    }
}

- (void)startDiscovering {
    [self registerNotifications];
    [[ARDiscovery sharedInstance] start];
}

- (void)stopDiscovering {
    [[ARDiscovery sharedInstance] stop];
    [self unregisterNotifications];
}

#pragma mark notification registration
- (void)registerNotifications {
    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(discoveryDidUpdateServices:) name:kARDiscoveryNotificationServicesDevicesListUpdated object:nil];
}

- (void)unregisterNotifications {
    [[NSNotificationCenter defaultCenter] removeObserver:self name:kARDiscoveryNotificationServicesDevicesListUpdated object:nil];
}

#pragma mark ARDiscovery notification
- (void)discoveryDidUpdateServices:(NSNotification *)notification {
    // reload the data in the main thread
    dispatch_async(dispatch_get_main_queue(), ^{
        if (_delegate && [_delegate respondsToSelector:@selector(droneDiscoverer:didUpdateDronesList:)]) {
            [_delegate droneDiscoverer:self didUpdateDronesList:[[notification userInfo] objectForKey:kARDiscoveryServicesList]];
        }
    });
}

@end
```
AppController.mmに先程作成したViewControllerを呼び出します。

```objc++:AppController.mm
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
    // Override point for customization after application launch.
    NSSetUncaughtExceptionHandler(&uncaughtExceptionHandler);
    
    // Add the view controller's view to the window and display.
    window = [[UIWindow alloc] initWithFrame: [[UIScreen mainScreen] bounds]];
    
    // Use RootViewController to manage CCEAGLView
    _viewController = [[RootViewController alloc]init];
    _viewController.wantsFullScreenLayout = YES;
    [_viewController.view addSubview: [DeviceListVC sharedInstance].view];

}
```

ドローン制御用プログラムです

```objc:MiniDrone.h
#import <Foundation/Foundation.h>
#import <libARController/ARController.h>
#import <libARCommands/ARCommands.h>
#import <libARDiscovery/ARDISCOVERY_BonjourDiscovery.h>

@class MiniDrone;

@protocol MiniDroneDelegate <NSObject>
@required
/**
 * Called when the connection to the drone did change
 * Called on the main thread
 * @param miniDrone the drone concerned
 * @param state the state of the connection
 */
- (void)miniDrone:(MiniDrone*)miniDrone connectionDidChange:(eARCONTROLLER_DEVICE_STATE)state;

/**
 * Called when the battery charge did change
 * Called on the main thread
 * @param MiniDrone the drone concerned
 * @param batteryPercent the battery remaining (in percent)
 */
- (void)miniDrone:(MiniDrone*)miniDrone batteryDidChange:(int)batteryPercentage;

/**
 * Called when the piloting state did change
 * Called on the main thread
 * @param miniDrone the drone concerned
 * @param batteryPercent the piloting state of the drone
 */
- (void)miniDrone:(MiniDrone*)miniDrone flyingStateDidChange:(eARCOMMANDS_MINIDRONE_PILOTINGSTATE_FLYINGSTATECHANGED_STATE)state;

/**
 * Called before medias will be downloaded
 * Called on the main thread
 * @param miniDrone the drone concerned
 * @param nbMedias the number of medias that will be downloaded
 */
- (void)miniDrone:(MiniDrone*)miniDrone didFoundMatchingMedias:(NSUInteger)nbMedias;

/**
 * Called each time the progress of a download changes
 * Called on the main thread
 * @param miniDrone the drone concerned
 * @param mediaName the name of the media
 * @param progress the progress of its download (from 0 to 100)
 */
- (void)miniDrone:(MiniDrone*)miniDrone media:(NSString*)mediaName downloadDidProgress:(int)progress;

/**
 * Called when a media download has ended
 * Called on the main thread
 * @param miniDrone the drone concerned
 * @param mediaName the name of the media
 */
- (void)miniDrone:(MiniDrone*)miniDrone mediaDownloadDidFinish:(NSString*)mediaName;

@end

@interface MiniDrone : NSObject

@property (nonatomic, weak) id<MiniDroneDelegate>delegate;

+ (MiniDrone*) sharedInstance;

- (id)initWithService:(ARService*)service;
- (eARCONTROLLER_DEVICE_STATE)connectionState;
- (eARCOMMANDS_MINIDRONE_PILOTINGSTATE_FLYINGSTATECHANGED_STATE)flyingState;
- (uint8_t)batteryLevel;

- (BOOL)takeOff;
- (BOOL)land;
- (void)setPitch:(uint8_t)pitch;
- (void)setRoll:(uint8_t)roll;
- (void)setYaw:(uint8_t)yaw;
- (void)setGaz:(uint8_t)gaz;
- (void)setFlag:(uint8_t)flag;
- (ARService*)getService;

@end
```

```objc:MiniDrone.m
@interface MiniDrone ()<MiniDroneDelegate>

@property (nonatomic, assign) ARCONTROLLER_Device_t *deviceController;
@property (nonatomic, assign) ARCOMMANDS_Decoder_t *commandsDecoder;
@property (nonatomic, strong) ARService *service;
@property (nonatomic, assign) eARCONTROLLER_DEVICE_STATE connectionState;
@property (nonatomic, assign) eARCOMMANDS_MINIDRONE_PILOTINGSTATE_FLYINGSTATECHANGED_STATE flyingState;
@property (nonatomic, assign) uint8_t batteryLevel;

@end

@implementation MiniDrone

+ (MiniDrone *)sharedInstance
{
    static MiniDrone *drone  = nil;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^ {
        drone = [[MiniDrone alloc] init];
    });
    return drone;
}

-(id)initWithService:(ARService *)service
{
    self = [super init];
    if (self) {
        _service = service;
        _flyingState = ARCOMMANDS_MINIDRONE_PILOTINGSTATE_FLYINGSTATECHANGED_STATE_LANDED;

    }
    return self;
}

- (void)dealloc
{
    if (_deviceController) {
        ARCONTROLLER_Device_Delete(&_deviceController);
    }
}

- (void)connect {
    
    if (!_deviceController) {
        // call createDeviceControllerWithService in background
        dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
            // if the product type of the service matches with the supported types
            eARDISCOVERY_PRODUCT product = _service.product;
            eARDISCOVERY_PRODUCT_FAMILY family = ARDISCOVERY_getProductFamily(product);
            if (family == ARDISCOVERY_PRODUCT_FAMILY_MINIDRONE) {
                // create the device controller
                [self createDeviceControllerWithService:_service];
            }
        });
    } else {
        ARCONTROLLER_Device_Start (_deviceController);
    }
}

- (void)disconnect {
    ARCONTROLLER_Device_Stop (_deviceController);
}

- (eARCONTROLLER_DEVICE_STATE)connectionState {
    return _connectionState;
}

- (eARCOMMANDS_MINIDRONE_PILOTINGSTATE_FLYINGSTATECHANGED_STATE)flyingState {
    return _flyingState;
}

- (uint8_t)batteryLevel
{
    return _batteryLevel;
}

- (void)createDeviceControllerWithService:(ARService*)service {
    // first get a discovery device
    ARDISCOVERY_Device_t *discoveryDevice = [self createDiscoveryDeviceWithService:service];
    
    if (discoveryDevice != NULL) {
        eARCONTROLLER_ERROR error = ARCONTROLLER_OK;
        eARCOMMANDS_DECODER_ERROR errorDecoder = ARCOMMANDS_DECODER_OK;

        // create the device controller
        _deviceController = ARCONTROLLER_Device_New (discoveryDevice, &error);
        _commandsDecoder = ARCOMMANDS_Decoder_NewDecoder(&errorDecoder);

        // add the state change callback to be informed when the device controller starts, stops...
        if (error == ARCONTROLLER_OK) {
            error = ARCONTROLLER_Device_AddStateChangedCallback(_deviceController, stateChanged, (__bridge void *)(self));
        }
        
        // add the command received callback to be informed when a command has been received from the device
        if (error == ARCONTROLLER_OK) {
            error = ARCONTROLLER_Device_AddCommandReceivedCallback(_deviceController, onCommandReceived, (__bridge void *)(self));
        }

        // start the device controller (the callback stateChanged should be called soon)
        if (error == ARCONTROLLER_OK) {
            error = ARCONTROLLER_Device_Start (_deviceController);
        }
        
        // we don't need the discovery device anymore
        ARDISCOVERY_Device_Delete (&discoveryDevice);
        
        // if an error occured, inform the delegate that the state is stopped
        if (error != ARCONTROLLER_OK) {
            dispatch_async(dispatch_get_main_queue(), ^{
                [_delegate miniDrone:self connectionDidChange:ARCONTROLLER_DEVICE_STATE_STOPPED];
            });
        }
    } else {
        // if an error occured, inform the delegate that the state is stopped
        dispatch_async(dispatch_get_main_queue(), ^{
            [_delegate miniDrone:self connectionDidChange:ARCONTROLLER_DEVICE_STATE_STOPPED];
        });
    }
}

- (ARDISCOVERY_Device_t *)createDiscoveryDeviceWithService:(ARService*)service {
    ARDISCOVERY_Device_t *device = NULL;
    eARDISCOVERY_ERROR errorDiscovery = ARDISCOVERY_OK;
    
    device = ARDISCOVERY_Device_New (&errorDiscovery);
    
    if (errorDiscovery == ARDISCOVERY_OK) {
        // get the ble service from the ARService
        ARBLEService* bleService = service.service;
        
        // create a BLE discovery device
        errorDiscovery = ARDISCOVERY_Device_InitBLE (device, service.product, (__bridge ARNETWORKAL_BLEDeviceManager_t)(bleService.centralManager), (__bridge ARNETWORKAL_BLEDevice_t)(bleService.peripheral));
    }
    
    if (errorDiscovery != ARDISCOVERY_OK) {
        NSLog(@"Discovery error :%s", ARDISCOVERY_Error_ToString(errorDiscovery));
        ARDISCOVERY_Device_Delete(&device);
    }
    
    return device;
}

- (ARCOMMANDS_Decoder_t*) createCommandsDecoder {
    return nil;
}

- (void)createSDCardModule {
}

#pragma mark commands
// 離陸
- (BOOL)takeOff {
    if (_deviceController && (_connectionState == ARCONTROLLER_DEVICE_STATE_RUNNING)) {
        _deviceController->miniDrone->sendPilotingTakeOff(_deviceController->miniDrone);
        return YES;
    }
    return NO;
}

// 着陸
- (BOOL)land {
    if (_deviceController && (_connectionState == ARCONTROLLER_DEVICE_STATE_RUNNING)) {
        _deviceController->miniDrone->sendPilotingLanding(_deviceController->miniDrone);
        return YES;
    }
    return NO;
}

- (void)setPitch:(uint8_t)pitch {
    if (_deviceController && (_connectionState == ARCONTROLLER_DEVICE_STATE_RUNNING)) {
        _deviceController->miniDrone->setPilotingPCMDPitch(_deviceController->miniDrone, pitch);
    }
}

- (void)setRoll:(uint8_t)roll {
    if (_deviceController && (_connectionState == ARCONTROLLER_DEVICE_STATE_RUNNING)) {
        _deviceController->miniDrone->setPilotingPCMDRoll(_deviceController->miniDrone, roll);
    }
}

- (void)setYaw:(uint8_t)yaw {
    if (_deviceController && (_connectionState == ARCONTROLLER_DEVICE_STATE_RUNNING)) {
        _deviceController->miniDrone->setPilotingPCMDYaw(_deviceController->miniDrone, yaw);
    }
}

- (void)setGaz:(uint8_t)gaz {
    if (_deviceController && (_connectionState == ARCONTROLLER_DEVICE_STATE_RUNNING)) {
        _deviceController->miniDrone->setPilotingPCMDGaz(_deviceController->miniDrone, gaz);
    }
}

- (void)setFlag:(uint8_t)flag {
    if (_deviceController && (_connectionState == ARCONTROLLER_DEVICE_STATE_RUNNING)) {
        _deviceController->miniDrone->setPilotingPCMDFlag(_deviceController->miniDrone, flag);
    }
}

#pragma mark Device controller callbacks
// called when the state of the device controller has changed
static void stateChanged (eARCONTROLLER_DEVICE_STATE newState, eARCONTROLLER_ERROR error, void *customData) {
    MiniDrone *miniDrone = (__bridge MiniDrone*)customData;
    if (miniDrone != nil) {
        switch (newState) {
            case ARCONTROLLER_DEVICE_STATE_RUNNING:
                break;
            case ARCONTROLLER_DEVICE_STATE_STOPPED:
                break;
            default:
                break;
        }
        dispatch_async(dispatch_get_main_queue(), ^{
            miniDrone.connectionState = newState;
            [miniDrone.delegate miniDrone:miniDrone connectionDidChange:newState];
        });
    }
}

// ドローンから送られてくる情報を取得します
static void onCommandReceived (eARCONTROLLER_DICTIONARY_KEY commandKey, ARCONTROLLER_DICTIONARY_ELEMENT_t *elementDictionary, void *customData) {
    MiniDrone *miniDrone = (__bridge MiniDrone*)customData;
    
    NSLog(@"onCommandReceived=%u %u", commandKey, ARCONTROLLER_DICTIONARY_KEY_MINIDRONE_USBACCESSORYSTATE_GUNSTATE);
    
    // バッテリー残量
    if ((commandKey == ARCONTROLLER_DICTIONARY_KEY_COMMON_COMMONSTATE_BATTERYSTATECHANGED) &&
        (elementDictionary != NULL)) {
        ARCONTROLLER_DICTIONARY_ARG_t *arg = NULL;
        ARCONTROLLER_DICTIONARY_ELEMENT_t *element = NULL;
        
        HASH_FIND_STR (elementDictionary, ARCONTROLLER_DICTIONARY_SINGLE_KEY, element);
        if (element != NULL) {
            HASH_FIND_STR (element->arguments, ARCONTROLLER_DICTIONARY_KEY_COMMON_COMMONSTATE_BATTERYSTATECHANGED_PERCENT, arg);
            if (arg != NULL) {
                uint8_t battery = arg->value.U8;
                dispatch_async(dispatch_get_main_queue(), ^{
                    [miniDrone.delegate miniDrone:miniDrone batteryDidChange:battery];
                    miniDrone.batteryLevel = battery;
                });
            }
        }
    }
    // 飛行状態
    else if ((commandKey == ARCONTROLLER_DICTIONARY_KEY_MINIDRONE_PILOTINGSTATE_FLYINGSTATECHANGED) &&
        (elementDictionary != NULL)) {
        ARCONTROLLER_DICTIONARY_ARG_t *arg = NULL;
        ARCONTROLLER_DICTIONARY_ELEMENT_t *element = NULL;
        
        HASH_FIND_STR (elementDictionary, ARCONTROLLER_DICTIONARY_SINGLE_KEY, element);
        if (element != NULL) {
            HASH_FIND_STR (element->arguments, ARCONTROLLER_DICTIONARY_KEY_MINIDRONE_PILOTINGSTATE_FLYINGSTATECHANGED_STATE, arg);
            if (arg != NULL) {
                miniDrone.flyingState = arg->value.I32;
                dispatch_async(dispatch_get_main_queue(), ^{
                    [miniDrone.delegate miniDrone:miniDrone flyingStateDidChange:miniDrone.flyingState];
                });
            }
        }
    }
    
}

- (ARService*)getService
{
    return _service;
}

static void onChargeRate(eARCOMMANDS_COMMON_CHARGERSTATE_CURRENTCHARGESTATECHANGED_STATUS status, eARCOMMANDS_COMMON_CHARGERSTATE_CURRENTCHARGESTATECHANGED_PHASE phase, void *custom) {
    NSLog(@"onChargeRate=%d", status);
}

@end

```

#呼び出し
Cocos2d-xから呼び出す為のブリッジ処理です。

```c:MiniDroneBridge.h
#include "cocos2d.h"

using namespace cocos2d;

class MiniDroneBridge;

using MiniDroneBridgeCompletionHandler = std::function<void (bool isSuccess)>;
using MiniDroneBridgeBatteryLevelHandler = std::function<void (int level)>;
using MiniDroneBridgeStatHandler = std::function<void (int stat)>;
using MiniDroneBridgeValueHandler = std::function<void (int val)>;

class MiniDroneBridge
{
public :
    static void takeOff(const MiniDroneBridgeCompletionHandler handler);
    static void land(const MiniDroneBridgeCompletionHandler handler);
    static void setGazUD(int val);
    static void setYawLR(int val);
    static void setPitchFB(int val);
    static void setRollLR(int val);
    static void connect();
    static void disconnect();
    
    static void dispBatteryLevel(const MiniDroneBridgeBatteryLevelHandler handler);
    static void dispConnectStat(const MiniDroneBridgeStatHandler handler);
    static void dispFlyingStat(const MiniDroneBridgeStatHandler handler);
};

```

```c:MiniDroneBridge.mm
#import "MiniDrone.h"
#import "MiniDroneBridge.h"

// 離陸
void MiniDroneBridge::takeOff(const MiniDroneBridgeCompletionHandler handler)
{
    if ([[MiniDrone sharedInstance] takeOff])
    {
        handler(true);
    } else {
        handler(false);
    }
    
}

// 着陸
void MiniDroneBridge::land(const MiniDroneBridgeCompletionHandler handler)
{
    if ([[MiniDrone sharedInstance] land])
    {
        handler(true);
    } else {
        handler(false);
    }
    
}

// 上昇/下降
void MiniDroneBridge::setGazUD(int val)
{
    [[MiniDrone sharedInstance] setGaz:val];
}

// ドローンの向きを左右に変える
void MiniDroneBridge::setYawLR(int val)
{
    [[MiniDrone sharedInstance] setYaw:val];
}

void MiniDroneBridge::setPitchFB(int val)
{
    // 前進/後退
    if (val == 0) {
        [[MiniDrone sharedInstance] setFlag:0];
    } else {
        [[MiniDrone sharedInstance] setFlag:1];
    }
    [[MiniDrone sharedInstance] setPitch:val];
    
}

void MiniDroneBridge::setRollLR(int val)
{
    // 左移動、右移動
    if (val == 0) {
        [[MiniDrone sharedInstance] setFlag:0];
    } else {
        [[MiniDrone sharedInstance] setFlag:1];
    }
    [[MiniDrone sharedInstance] setRoll:val];
}

// バッテリー残数取得
void MiniDroneBridge::dispBatteryLevel(const MiniDroneBridgeBatteryLevelHandler handler)
{
    handler([[MiniDrone sharedInstance] batteryLevel]);
}

// 接続状態取得
void MiniDroneBridge::dispConnectStat(const MiniDroneBridgeStatHandler handler)
{
    handler([[MiniDrone sharedInstance] connectionState]);
}

// 飛行状態取得
void MiniDroneBridge::dispFlyingStat(const MiniDroneBridgeStatHandler handler)
{
    handler([[MiniDrone sharedInstance] flyingState]);
}

void MiniDroneBridge::connect()
{
    [[MiniDrone sharedInstance] connect];
}

void MiniDroneBridge::disconnect()
{
    [[MiniDrone sharedInstance] disconnect];
}

```

#離陸/着陸処理
離陸と着陸を行うために、画面にボタンを設置します。


```c:HelloWorldScene.h
#include "cocos2d.h"

#define WINSIZE Director::getInstance()->getWinSize()
#define WINCENTER Point(WINSIZE.width*0.5, WINSIZE.height*0.5)

class HelloWorld : public cocos2d::Layer
{
public:
    static cocos2d::Scene* createScene();
    static HelloWorld* sharedScene(void);
    
    virtual bool init();
    CREATE_FUNC(HelloWorld);

private:
    CC_SYNTHESIZE(MenuItemSprite*, _btnTakeOFF, BtnTakeOFF);
    CC_SYNTHESIZE(MenuItemSprite*, _btnLand, BtnLand);
    
    CC_SYNTHESIZE(Label*, _lblBattery, LblBattery);
    CC_SYNTHESIZE(bool, _connectStat, ConnectStat);
        
    void onEnterTransitionDidFinish();
    void update(float dt);
    
    void takeOffTapped(MenuItemSprite* sender);
    void landTapped(MenuItemSprite* sender);
};

```

```HelloWorldScene.cpp
#include "HelloWorldScene.h"
#include "MiniDroneBridge.h"

USING_NS_CC;

static HelloWorld *g_Scene;

HelloWorld* HelloWorld::sharedScene() {
    return g_Scene;
}

Scene* HelloWorld::createScene()
{
    // 'scene' is an autorelease object
    auto scene = Scene::create();
    
    // 'layer' is an autorelease object
    g_Scene = HelloWorld::create();
    
    // add layer as a child to scene
    scene->addChild(g_Scene);
    
    // return the scene
    return scene;
}

// on "init" you need to initialize your instance
bool HelloWorld::init()
{
    //////////////////////////////
    // 1. super init first
    if ( !Layer::init() )
    {
        return false;
    }
    
    _connectStat = false;

    // TakeOFF
    Sprite* btnTakeOff_ON = Sprite::create();
    btnTakeOff_ON->setTextureRect(Rect(0, 0, 200, 100));
    btnTakeOff_ON->setColor(Color3B::GRAY);
    
    Sprite* btnTakeOff_OFF = Sprite::create();
    btnTakeOff_OFF->setTextureRect(Rect(0, 0, 200, 100));
    btnTakeOff_OFF->setColor(Color3B(0,0,200));
    
    _btnTakeOFF = MenuItemSprite::create(btnTakeOff_OFF,
                                         btnTakeOff_ON,
                                         [this](Ref* sender) {this->takeOffTapped((MenuItemSprite*)sender);});
    Label* lblTakeOff = Label::createWithSystemFont("離陸", "arial", 26);
    lblTakeOff->enableOutline(Color4B::BLACK, 1);
    lblTakeOff->setPosition(Vec2(_btnTakeOFF->getContentSize().width*0.5, _btnTakeOFF->getContentSize().height*0.5));
    _btnTakeOFF->addChild(lblTakeOff);
    _btnTakeOFF->setPosition(Vec2(WINCENTER.x, _btnTakeOFF->getContentSize().height*0.5));
    _btnTakeOFF->setVisible(false);
    
    // Land
    Sprite* btnLand_ON = Sprite::create();
    btnLand_ON->setTextureRect(Rect(0, 0, 200, 100));
    btnLand_ON->setColor(Color3B::GRAY);
    
    Sprite* btnLand_OFF = Sprite::create();
    btnLand_OFF->setTextureRect(Rect(0, 0, 200, 100));
    btnLand_OFF->setColor(Color3B(0,0,200));
    
    _btnLand = MenuItemSprite::create(btnLand_OFF,
                                      btnLand_ON,
                                      [this](Ref* sender) {this->landTapped((MenuItemSprite*)sender);});
    Label* lblLand = Label::createWithSystemFont("着陸", "arial", 26);
    lblLand->enableOutline(Color4B::BLACK, 1);
    lblLand->setPosition(Vec2(_btnLand->getContentSize().width*0.5, _btnLand->getContentSize().height*0.5));
    _btnLand->addChild(lblLand);
    _btnLand->setPosition(Vec2(WINCENTER.x, _btnLand->getContentSize().height*0.5));
    _btnLand->setVisible(false);
    
    Menu* menu = Menu::create();
    menu->setPosition(this->getPosition());
    menu->setContentSize(this->getContentSize());
    this->addChild(menu);
    menu->addChild(_btnTakeOFF);
    menu->addChild(_btnLand);
        
    _lblBattery = Label::createWithSystemFont("Battery 0%", "Arial", 26);
    _lblBattery->setAnchorPoint(Vec2(0, 1));
    _lblBattery->setAlignment(TextHAlignment::LEFT);
    _lblBattery->setPosition(Vec2(10, WINSIZE.height));
    this->addChild(_lblBattery);
    
    return true;
}

void HelloWorld::onEnterTransitionDidFinish()
{
    scheduleUpdate();
    

    // 0.1秒毎に呼び出す
    this->runAction(Repeat::create(Sequence::create(DelayTime::create(0.1), CallFunc::create([=]{
        MiniDroneBridge::dispBatteryLevel([=](int level)
        {                  
            // バッテリー数値表示
            _lblBattery->setString(StringUtils::format("Battery %d%%", level));
        });

        MiniDroneBridge::dispConnectStat([=](int stat)
        {
             // 接続状態かどうか
             if (stat == 2) {
                 _connectStat = true;
                 _btnTakeOFF->setVisible(true);                     
             } else {
                 _connectStat = false;
                 _btnTakeOFF->setVisible(false);                     
             }                 
        });

        // 飛行状態
        MiniDroneBridge::dispFlyingStat([=](int stat)
        {
            if (_connectStat) {
                if (stat == 0) {
                    // 着陸
                    _btnTakeOFF->setVisible(true);
                    _btnLand->setVisible(false);                        
                } else if (stat == 4) {
                    // 着陸中
                    _btnTakeOFF->setVisible(false);
                    _btnLand->setVisible(false);                        
                } else if (stat >= 2 && stat <= 3) {
                    // 離陸
                    _btnTakeOFF->setVisible(false);
                    _btnLand->setVisible(true);
                }
           }                                        
        });

    }) , NULL), -1));
}
// 離陸処理
void HelloWorld::takeOffTapped(cocos2d::MenuItemSprite *sender)
{
    MiniDroneBridge::takeOff([=](bool isSuccess)
    {
        log("takeoff=%d", isSuccess);
    });
        
}

// 着陸処理
void HelloWorld::landTapped(cocos2d::MenuItemSprite *sender)
{
    MiniDroneBridge::land([](bool isSuccess)
    {
          log("land=%d", isSuccess);
    });
}
```

#まとめ
基本的なドローンの制御はMiniDrone.mに記述しています。
それをCocos2d-xから呼び出してあげるだけで、簡単に制御することが出来ました。
ドローン自体に超音波センサーが内蔵されているので、自立飛行も自動的にしてくれます。

`MiniDroneBridge::dispConnectStat`や`MiniDroneBridge::dispFlyingStat`で、
ドローンの接続状態と飛行状態を取得しています。
リアルタイムで状態を判断して、適切なボタンを表示してあげると良いです。

このMAMBOドローンは、BB弾が発射出来たり、4gまでの物をつかむためのグラバーが付属品としてあります。
もちろん、プログラムで制御も可能なので、そちらも是非試してみてください！