Initial release

Dependencies:   microbit

main.cpp

Committer:
masakjm
Date:
2018-12-14
Revision:
5:80cb8f9db01e
Parent:
4:4fe5674bf409
Child:
6:e2074d02065b

File content as of revision 5:80cb8f9db01e:

//=================================
// microbit_switch_if_3sw
//=================================
//    BLE switch interface with 3 tact switches or 3 touch sensors using 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
//    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
//---------------------------------

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

#include "microbit_switch_if_3sw.h"
#include "KeyValueInt.h"

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

/** ----------
 * @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);
KeyValueInt kviKeyCode2("keycode2",'T', 1, 1, NUM_GROUP2, 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));
    kviKeyCode2.set(FlashGet(storage, kviKeyCode2.key, 1));
    if(!paraSettingOne(storage, &kviKeyCode1, change)) return false;
    return paraSettingOne(storage, &kviKeyCode2, 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);
MicroBitPin P1(MICROBIT_ID_IO_P1, MICROBIT_PIN_P1, PIN_CAPABILITY_ALL);
MicroBitPin P2(MICROBIT_ID_IO_P2, MICROBIT_PIN_P2, PIN_CAPABILITY_ALL);

//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);

/** ----------  
 * @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 = '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);
    }
}

/** ----------  
 * @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 が続く時に表示
            }
    }
}

//---------------------------------
//  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<3;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, P1 and P2 pins into touch sense mode.
    P0.isTouched();
    P1.isTouched();
    P2.isTouched();

//----- 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) {
        ble.waitForEvent();        // BLEイベントを待つ

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