BLE switch interface with GROVE joystic for micro:bit http://mahoro-ba.net/e2073.html

Dependencies:   microbit

main.cpp

Committer:
masakjm
Date:
2019-04-09
Revision:
21:961da341b755
Parent:
19:4354c463c36c

File content as of revision 21:961da341b755:

//=================================
// microbit_switch_if_joy
//=================================
//    BLE switch interface with GROVE joystic for micro:bit
//    It is intended for use with ios devices.
//
//    The MIT License (MIT)   Copyright (c) 2019 Masatomo Kojima
//
//  LED message
//    S  key code setting
//    C  Conected
//    P  success Pearing
//    F  Fail pearing
//    E  Error at sending data by ble
//    e  Error at writting data to flash memory
//    I  Error by Incorrect cording
//    G  GROVE sensor connect error
//---------------------------------

#define VERSION     "JOY-190330"
//#define NO_DEBUG

#include "microbit_switch_if_joy.h"
#include "KeyValueInt.h"

//---------------------------------
//  Display
//---------------------------------
MicroBitDisplay display;
int  State;                   // 状態遷移
char DispChar  = 0;           // LEDに表示する文字コード
char DispCharLast = 0;        // 最後に表示した文字コード
bool TurnOffMode = false;     // LED非表示モードのフラグ

//---------------------------------
//  GROVE Joystick
//---------------------------------

AnalogIn AnalogP1(MICROBIT_PIN_P1);
AnalogIn AnalogP2(MICROBIT_PIN_P2);
    
/** ----------
 * @brief 整数値をLEDに表示する
 * @param data 整数値
 */
static void displayNumber(int data)
{
    if (0<=data && data<=9) {  // 1桁はスクロールしない
        display.print(data);
    } else {
        display.scroll(data);
    }
}

//---------------------------------
//  Flash Memory
//---------------------------------
/** ----------  
 * @brief   キー名を指定して不揮発メモリから値を読み出す
 * @param   key  キー名
 * @param   init  データが保存されていなかった時の初期値
 * @return  int 取得したデータ
 */
static int FlashGet(MicroBitStorage storage, const char* key, int init=0)
{
    KeyValuePair* kvp = storage.get(key);
    if(kvp != NULL) {
        int data;
        memcpy(&data, kvp->value, sizeof(int));
        DEBUG("== FlashGet exist %s = %d\r\n", key, data);
        return data;
    }
    return init;
}

/** ----------  
 * @brief   不揮発メモリに値を書き込む
 * @param   storage  不揮発メモリインスタンス
 * @param   key      キー名
 * @param   data     値
 * @return  int MICROBIT_OK = 0
 *              MICROBIT_INVALID_PARAMETER = -1001,
 *              MICROBIT_NO_RESOURCES = -1005,
 */
static int FlashPut(MicroBitStorage storage, const char* key, int data)
{
    int ret = storage.put(key, (uint8_t *)&data, sizeof(int));
    DEBUG("== FlashPut %s %d %d\r\n", key, data, ret);
    return ret;
}

//---------------------------------
//  Setting
//---------------------------------
bool setting_inc;           // 設定値を増加
bool setting_enter;         // 設定値を入力
bool setting_next;          // 次の設定値に移動

KeyValueInt kviKeyCode1("keycode1",'S', 1, 1, NUM_GROUP1, true);

/** ----------  
 * @brief   1つのパラメータの表示と変更
 * @param   storage  不揮発メモリインスタンス
 * @param   para     パラメータのキーと値の組
 * @param   change   変更する時 true
 * @return  bool true : Success    false : Failed
 */
static bool paraSettingOne(MicroBitStorage storage, KeyValueInt* para, bool change)
{
    display.print(para->disp);    // 識別文字をLED表示
    wait(SETTING_DISPLAY_WAIT);
    displayNumber(para->value);   // 値をLED表示
    if (!change) {
        wait(SETTING_DISPLAY_WAIT);
        return true;
    }

    DEBUG("== paraSetting\r\n");
    setting_inc = false;
    setting_next = setting_enter = false;
    while( ! setting_next) {      // Bボタンが離されるまで
        if (setting_inc) {        // Aボタンが押されたら
            setting_inc = false;
            para->inc();
            displayNumber(para->value);
        }
        wait(0.2);
    }
    int ret = FlashPut(storage, para->key, para->value);
    if (ret) DEBUG("(strage)Error %d\r\n",ret);
    return (ret == MICROBIT_OK);
}

/** ----------  
 * @brief  パラメータの表示と変更
 * @param  change   true:変更する時
 */
static bool paraSetting(bool change=false)
{
    MicroBitStorage storage;

    kviKeyCode1.set(FlashGet(storage, kviKeyCode1.key, 1));
    return paraSettingOne(storage, &kviKeyCode1, change);
}
//---------------------------------
//  BLE & HID
//---------------------------------
BLE ble;
KeyboardService *kbdServicePtr;
BLE_MESSAGE bleMessage;

/** ----------  
 * @brief  BLE接続が切断された時のコールバック関数
 */
static void onDisconnect(const Gap::DisconnectionCallbackParams_t *params)
{
    DEBUG("(BLE)disconnected\r\n");
    ble.gap().startAdvertising(); // restart advertising
    bleMessage = BLE_NO_MESSAGE;
}

/** ----------  
 * @brief  BLE接続が接続された時のコールバック関数
 */
static void onConnect(const Gap::ConnectionCallbackParams_t *params)
{
    if(kbdServicePtr->isConnected()) {
        DEBUG("(BLE)connected\r\n");
        ble.gap().stopAdvertising();
        bleMessage = BLE_CONNECTED;
    }
}

/** ----------  
 * @brief  パスキー入力を求められた時のコールバック関数
 */
static void passkeyDisplayCallback(Gap::Handle_t handle, const SecurityManager::Passkey_t passkey)
{
    printf("(BLE)Input passKey: ");
    for (unsigned i = 0; i < Gap::ADDR_LEN; i++) {
        printf("%c", passkey[i]);
    }
    printf("\r\n");
}

/** ----------  
 * @brief   セキュリティ設定完了時のコールバック関数
 */
static void securitySetupCompletedCallback(Gap::Handle_t handle, SecurityManager::SecurityCompletionStatus_t status)
{
    if (status == SecurityManager::SEC_STATUS_SUCCESS) {
//        DEBUG("(BLE)Security success %d\r\n", status);
        bleMessage = BLE_PAIRING_SUCCESS;

    } else {
//        DEBUG("(BLE)Security failed %d\r\n", status);
        bleMessage = BLE_PAIRING_FAILED;
    }
}

/** ----------  
 * @brief  セキュリティ初期化
 * @para   _ble  BLEインスタンス
 */
static void initializeSecurity(BLE &_ble)
{
    bool enableBonding = true;
    bool requireMITM = HID_SECURITY_REQUIRE_MITM;

    _ble.securityManager().onPasskeyDisplay(passkeyDisplayCallback);
    _ble.securityManager().onSecuritySetupCompleted(securitySetupCompletedCallback);

    _ble.securityManager().init(enableBonding, requireMITM, HID_SECURITY_IOCAPS);
}

/** ----------  
 * @brief   HOGP (HID Over GATT Profile) 初期化
 * @para   _ble  BLEインスタンス
 */
static void initializeHOGP(BLE &_ble)
{
    static const uint16_t uuid16_list[] =  {GattService::UUID_HUMAN_INTERFACE_DEVICE_SERVICE,
                                            GattService::UUID_DEVICE_INFORMATION_SERVICE,
                                            GattService::UUID_BATTERY_SERVICE
                                           };

    PnPID_t pnpID;
    pnpID.vendorID_source = 0x2;    // from the USB Implementer's Forum
    pnpID.vendorID = 0x0D28;        // NXP
    pnpID.productID = 0x0204;       // CMSIS-DAP (well, it's a keyboard but oh well)
    pnpID.productVersion = 0x0100;  // v1.0
    HIDDeviceInformationService deviceInfo(_ble, "ARM", "m1", "abc", "def", "ghi", "jkl", &pnpID);

    BatteryService batteryInfo(_ble, 80);

    _ble.gap().accumulateAdvertisingPayload(GapAdvertisingData::BREDR_NOT_SUPPORTED |
                                           GapAdvertisingData::LE_GENERAL_DISCOVERABLE);
    _ble.gap().accumulateAdvertisingPayload(GapAdvertisingData::COMPLETE_LIST_16BIT_SERVICE_IDS,
                                           (uint8_t *)uuid16_list, sizeof(uuid16_list));

    // see 5.1.2: HID over GATT Specification (pg. 25)
    _ble.gap().setAdvertisingType(GapAdvertisingParams::ADV_CONNECTABLE_UNDIRECTED);
    // 30ms to 50ms is recommended (5.1.2)
    _ble.gap().setAdvertisingInterval(50);
}

/** ----------  
 * @brief  BLE接続のHID(human interface device) の初期化
 */
static void initialize_BLE_HID()
{
    static const char SHORT_DEVICE_NAME[] = "micro:bit";
    static char DeviceName[20];

    int id = microbit_serial_number()& 0xfff;    // シリアル番号の下12桁を4桁の10進で表示
    sprintf(DeviceName, "micro:bit %04d", id);
    DEBUG("(BLE)id %s\r\n", DeviceName);

//    DEBUG("(BLE)initialising ble\r\n");
    ble.init();

    ble.gap().onDisconnection(onDisconnect);
    ble.gap().onConnection(onConnect);

    initializeSecurity(ble);

    DEBUG("(BLE)adding hid service\r\n");
    KeyboardService kbdService(ble);
    kbdServicePtr = &kbdService;

    DEBUG("(BLE)adding device info and battery service\r\n");
    initializeHOGP(ble);

    DEBUG("(BLE)setting up gap\r\n");
    ble.gap().accumulateAdvertisingPayload(GapAdvertisingData::KEYBOARD);
    ble.gap().accumulateAdvertisingPayload(GapAdvertisingData::COMPLETE_LOCAL_NAME,
                                           (uint8_t *)DeviceName, sizeof(DeviceName));
    ble.gap().accumulateAdvertisingPayload(GapAdvertisingData::SHORTENED_LOCAL_NAME,
                                           (uint8_t *)SHORT_DEVICE_NAME, sizeof(SHORT_DEVICE_NAME));
    ble.gap().setDeviceName((const uint8_t *)DeviceName);

    DEBUG("(BLE)advertising\r\n");
    ble.gap().startAdvertising();
}

//---------------------------------
//  Button
//---------------------------------
MicroBitMessageBus bus;
MicroBitButton buttonA(MICROBIT_PIN_BUTTON_A, MICROBIT_ID_BUTTON_A);
MicroBitButton buttonB(MICROBIT_PIN_BUTTON_B, MICROBIT_ID_BUTTON_B);
//MicroBitPin P0(MICROBIT_ID_IO_P0, MICROBIT_PIN_P0, PIN_CAPABILITY_ALL);
MicroBitButton P0(MICROBIT_PIN_P0, MICROBIT_ID_IO_P0, MICROBIT_BUTTON_ALL_EVENTS, PullUp);

/** ----------  
 * @brief  キーコードを送信する
 * @para   code 上位8bit:修飾キー  下位8bit:キーコード    
 */
static void sendKeyCode(int code) {
    if (code) {
        uint8_t key = code & 0xff;
        uint8_t modif = code >> 8;
                
        if (key > KEYMAP_SIZE ) {
            DispChar  ='I';      // キーコード設定間違い
        } else {
            ble_error_t ret = kbdServicePtr->keyDownCode(key, modif);
//                    DEBUG("  code=%d  modif=%d\r\n",key , modif);
            if (ret) {
                DispChar  ='E';
                DispCharLast =0;   // E が続く時に表示
                DEBUG("(BLE)Error %d\r\n",ret);
            }
        }
    }
}
/** ----------  
 * @brief  ボタン保持時イベント
 * @para   e  イベント
 */
static void onButtonHold(MicroBitEvent e)
{
    if ((e.source == MICROBIT_ID_BUTTON_A && buttonB.isPressed()) ||
        (e.source == MICROBIT_ID_BUTTON_B && buttonA.isPressed()) ) {
           DispChar  = 'H';
           sendKeyCode(keyCodeGroup0[2]);
     }
}

/** ----------  
 * @brief  ボタン押下げ時イベント
 * @para   e  イベント
 */
static void onButtonDown(MicroBitEvent e)
{
//   DEBUG("  Button Down %d  State=%d\r\n", e.source, State);

    switch (State) {
        case STATE_SETTING :
            switch(e.source) {
                case MICROBIT_ID_BUTTON_A :
                    setting_inc = true;
                    break;
                case MICROBIT_ID_BUTTON_B :
                    setting_enter = true;
                    break;
            }
            break;
        case STATE_OPERATING :
            int code = 0;
            switch(e.source) {
                case MICROBIT_ID_BUTTON_A :
                    DispChar  = 'A';
                    code = keyCodeGroup0[0];
                    break;
                case MICROBIT_ID_BUTTON_B :
                    DispChar  = 'B';
                    code = keyCodeGroup0[1];
                    break;
                case MICROBIT_ID_IO_P0 :
                    DispChar  = '0';
                    code = keyCodeGroup1[kviKeyCode1.value-1][JOY_CENTER_PRESS];
                    break;
            }
            if (code != 0 )sendKeyCode(code);
    }
}

/** ----------  
 * @brief  ボタン離し時イベント
 * @para   e  イベント
 */
static void onButtonUp(MicroBitEvent e)
{
//    DEBUG("  Button up %d\r\n", e.source);
    switch (State) {
        case STATE_SETTING :
            if(setting_enter) setting_next = true;    // 決定ボタンを離したら次へ
            break;
        case STATE_OPERATING :
            DispChar  = 0;
            ble_error_t ret = kbdServicePtr->keyUpCode();
            if (ret) {
                DispChar  ='E';
                DispCharLast =0;   // E が続く時に表示
            }
    }
}


/** ----------  
 * @brief   ジョイスティックの接続をチェックする
 * @return  false:接続エラー
 */
static bool checkJoy() 
{
    float joyX = AnalogP1.read();
    float joyY = AnalogP2.read();

    if (joyX < JOY_ERR_THRE) return false;       
    if (joyY < JOY_ERR_THRE) return false;   

    return true;
}

/** ----------  
 * @brief   ジョイスティックの状態を返す
 * @return  JOY_STATUS
 */
static JOY_STATUS readJoyStatus(JOY_STATUS last) 
{
    JOY_STATUS js = JOY_NEUTRAL;

    float joyX = AnalogP1.read();
    float joyY = AnalogP2.read();

    if (joyX > JOY_CENTER_THRE) js = JOY_CENTER_PRESS;       
    else {
        if (joyX < JOY_LOW_THRE)  js = JOY_XLOW_PRESS;        
        if (joyX > JOY_HIGH_THRE) js = JOY_XHIGH_PRESS;        
        if (joyY < JOY_LOW_THRE)  js = JOY_YLOW_PRESS;        
        if (joyY > JOY_HIGH_THRE) js = JOY_YHIGH_PRESS;        
    }
             
//    DEBUG("%d, %f, %f\r\n", js, joyX, joyY);
    return js;
}

static void joyAction(JOY_STATUS last, JOY_STATUS now) 
{
    int code = 0;
//   DEBUG("%d, %d\r\n", now, last);
            
    if( now != last){              
        if(now == JOY_NEUTRAL) {
            DispChar  = 0;
            ble_error_t ret = kbdServicePtr->keyUpCode();
            if (ret) {
                DispChar  ='E';
                DispCharLast =0;   // E が続く時に表示
            }
        } else {
            DispChar  = '0' + now;
            code = keyCodeGroup1[kviKeyCode1.value-1][now];
        }
        if (code != 0 )sendKeyCode(code);
    }
}
//---------------------------------
//  Main
//---------------------------------
int main()
{
    State = STATE_DESABLE_INPUT;

    wait(0.1);           // ボタン状態の更新
    bool bA = buttonA.isPressed();
    bool bB = buttonB.isPressed();

    if (bA && bB) {     // ボタンABを押しながら起動
        for(int i=0;i<2;i++) {
            display.scroll(VERSION);
            wait(0.5);
        }
    } else if(bA)       // ボタンAを押しながら起動
        State = STATE_SETTING;

//----- Display
    display.setDisplayMode(DISPLAY_MODE_BLACK_AND_WHITE);
    display.clear();
    Timer dispTime;         // 連続表示タイマー
    dispTime.start();

//----- Button
    bus.listen(MICROBIT_ID_ANY, MICROBIT_BUTTON_EVT_DOWN, onButtonDown);
    bus.listen(MICROBIT_ID_ANY, MICROBIT_BUTTON_EVT_UP,   onButtonUp);
    bus.listen(MICROBIT_ID_ANY, MICROBIT_BUTTON_EVT_HOLD, onButtonHold);

// Put the P0 pins into touch sense mode.
//    P0.isTouched();

//----- Joystick
    JOY_STATUS joyStatus;
    JOY_STATUS joyStatusLast = JOY_NEUTRAL;
    while (!checkJoy())  {
        DispChar  ='G';
        display.printChar(DispChar );
        wait(1);
    }   
    
//----- Setting
    if (paraSetting(State == STATE_SETTING)) DispChar  =0;
    else                                     DispChar  ='e';   

//----- BLE & HID
    State = STATE_DESABLE_INPUT;
    initialize_BLE_HID();
    display.clear();

//----- Loop
    State = STATE_OPERATING;
    while (true) {
        wait(0.15);
        ble.waitForEvent();        // BLEイベントを待つ

        joyStatus = readJoyStatus(joyStatusLast);  // Joystick の状態を読む
        joyAction(joyStatusLast, joyStatus);
        joyStatusLast = joyStatus;      

        if(bleMessage != BLE_NO_MESSAGE) {    // BLEの状態を表示する
            DispChar  = bleDispChar[bleMessage];
            bleMessage = BLE_NO_MESSAGE;    
        }

        if (DispChar  != DispCharLast) {  // 表示文字が変更
            TurnOffMode = false;
            dispTime.reset();
            display.enable(); 
            if (DispChar ) display.printChar(DispChar );
            else          display.clear();
        }
        if (!TurnOffMode) {
            if (dispTime.read() > TIME_TURN_OFF) {  // 長時間表示
                TurnOffMode = true;
                display.disable();
            }
        }
        
        DispCharLast = DispChar ;
    }
}