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

Dependencies:   microbit

main.cpp

Committer:
masakjm
Date:
2018-09-17
Revision:
1:3b9ae1dbcdcf
Parent:
0:28fb3e9ef81a
Child:
3:b6e9850d3e76

File content as of revision 1:3b9ae1dbcdcf:

//=================================
// microbit_switch_if_3sw
//=================================
//    BLE switch interface with 3 tact switches for micro:bit
//    It is intended for use with ios devices.
//
//    The MIT License (MIT)   Copyright (c) 2018 Masatomo Kojima
//
//  LED message
//    S  key code setting sw1,2
//    T  key code setting sw3
//    W  Waiting
//    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
//
//  Please refer to the following site. (Japanese only)
//     http://mahoro-ba.net/e2036.html
//     http://mahoro-ba.net/e2038.html
//
//  I refer to information written on the following sites.
//    (1)https://os.mbed.com/teams/microbit/code/microbit_presenter/
//           Licensed under the Apache License, Version 2.0
//
//    (2)https://lancaster-university.github.io/microbit-docs/
//          The MIT License (MIT)
//          Copyright (c) 2016 British Broadcasting Corporation.
//
//---------------------------------

#define VERSION     "3SW-180917"
//#define NO_DEBUG

#include "microbit_switch_if.h"
#include "KeyValueInt.h"

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

//-----  数値の表示
static void displayNumber(int data)
{
    if (0<=data && data<=9) {  // 1桁はスクロールしない
        display.print(data);
    } else {
        display.scroll(data);
    }
}

//---------------------------------
//  Flash Memory
//---------------------------------

//-----  不揮発メモリの読み出し
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;
}

//-----  不揮発メモリの書き込み
//     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);
KeyValueInt kviKeyCode2("keycode2",'T', 1, 1, NUM_GROUP2, true);

//----- パラメータ設定

static int 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 0;
    }

    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);
    }
    if (FlashPut(storage, para->key, para->value)) {
        dispChar = 'e';
        return -1;
    } else {
        dispChar = 0;
        return 0;
    }
}

static void paraSetting(bool change)
{
    MicroBitStorage storage;

    kviKeyCode1.set(FlashGet(storage, kviKeyCode1.key, 1));
    kviKeyCode2.set(FlashGet(storage, kviKeyCode2.key, 1));
    int err = paraSettingOne(storage, &kviKeyCode1, change);
    if (err) return;
    paraSettingOne(storage, &kviKeyCode2, change);
}
//---------------------------------
//  BLE & HID
//---------------------------------
BLE ble;
KeyboardService *kbdServicePtr;

//-----  BLE接続が切断された
static void onDisconnect(const Gap::DisconnectionCallbackParams_t *params)
{
    DEBUG("(BLE)disconnected\r\n");
    ble.gap().startAdvertising(); // restart advertising
    dispChar = 0;
}

//-----  BLE接続された
static void onConnect(const Gap::ConnectionCallbackParams_t *params)
{
    if(kbdServicePtr->isConnected()) {
        DEBUG("(BLE)connected\r\n");
        dispChar = 'C';
    }
}

//-----  BLE接続待ち状態
static void waiting()
{
    if (!kbdServicePtr->isConnected()) {
        dispChar ='W';
    }
}

//-----  パスキー表示
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");
}

//-----  セキュリティ設定完了
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);
        dispChar = 'P';

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

//-----  セキュリティ初期設定
static void securitySetupInitiatedCallback(Gap::Handle_t, bool allowBonding, bool requireMITM, SecurityManager::SecurityIOCapabilities_t iocaps)
{
    DEBUG("(BLE)Security setup initiated\r\n");
}

//-----  セキュリティ初期設定
static void initializeSecurity(BLE &ble)
{
    bool enableBonding = true;
    bool requireMITM = HID_SECURITY_REQUIRE_MITM;

    ble.securityManager().onSecuritySetupInitiated(securitySetupInitiatedCallback);
    ble.securityManager().onPasskeyDisplay(passkeyDisplayCallback);
    ble.securityManager().onSecuritySetupCompleted(securitySetupCompletedCallback);

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

//-----  HOGP (HID Over GATT Profile) 初期化
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);
}

//-----  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);
MicroBitButton button0(MICROBIT_PIN_P0, MICROBIT_ID_IO_P0, MICROBIT_BUTTON_ALL_EVENTS, PullUp);
MicroBitButton button1(MICROBIT_PIN_P1, MICROBIT_ID_IO_P1, MICROBIT_BUTTON_ALL_EVENTS, PullUp);
MicroBitButton button2(MICROBIT_PIN_P2, MICROBIT_ID_IO_P2, MICROBIT_BUTTON_ALL_EVENTS, PullUp);

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);
            }
        }
    }
}
//-----  ボタン保持時イベント
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]);
     }
}

//-----  ボタン押下げ時イベント
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 = '1';
                    code = keyCodeGroup1[kviKeyCode1.value-1][0];
                    break;
                case MICROBIT_ID_IO_P1 :
                    dispChar = '2';
                    code = keyCodeGroup1[kviKeyCode1.value-1][1];
                    break;
                case MICROBIT_ID_IO_P2 :
                    dispChar = '3';
                    code = keyCodeGroup2[kviKeyCode2.value-1];
                    break;
            }
            sendKeyCode(code);
    }
}

//-----  ボタン離し時イベント
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 が続く時に表示
            }
    }
}

//---------------------------------
//  Main
//---------------------------------
int main()
{
    state = STATE_NORMAL;

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

    if (bA && bB) {     // ボタンABを押しながら起動
        while(1) {
            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);

//----- Setting
    paraSetting(state == STATE_SETTING);
    state = STATE_OPERATING;

//----- BLE & HID
    Ticker heartbeat;
    heartbeat.attach(waiting, 1);     // 1秒毎に関数waiting を呼び出す
    initialize_BLE_HID();
    display.clear();

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

        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;
    }
}