BLE switch interface using micro:bit with 3 tact switches or 3 Makey Makey sensors

Dependencies:   microbit

main.cpp

Committer:
masakjm
Date:
2019-06-11
Revision:
3:d8fd4efb63cc
Parent:
2:8e2e6c6658be

File content as of revision 3:d8fd4efb63cc:

//=================================
// microbit_switch_if_3sw
//=================================
//  BLE switch interface using micro:bit with 3 tact switches
//    It is intended for use with ios devices.
//
//    The MIT License (MIT)   Copyright (c) 2019 Masatomo Kojima
//---------------------------------

#define VERSION     "3SW-190612"
#define NO_DEBUG

#include "microbit_switch_if_3sw.h"
#include "KeyValueInt.h"
#include <queue>

//---------------------------------
//  KeyBuff
//---------------------------------
  std::queue<int> KeyBuff;

//---------------------------------
//  Display
//---------------------------------
MicroBitDisplay display;
int  State;                       // 状態遷移
char DispChar = DISP_NO_MESSAGE;  // LEDに表示する文字コード
char DispCharLast = 0;            // 最後に表示した文字コード
int  Cnt = 0;                     // カウンター

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

/** ----------
 * @brief 一定期間後に表示をOFFにする
 */
static void turnOff() {
    display.disable();
}
 
//---------------------------------
//  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",DISP_SETTING_FREQ, 1, 1, NUM_GROUP_3SW, true);

/** ----------  
 * @brief   1つのパラメータの表示と変更
 * @param   storage  不揮発メモリインスタンス
 * @param   para     パラメータのキーと値の組
 * @param   change   変更する時        true
 * @param   disp     今の値を表示する時 true
 * @return  bool true : Success    false : Failed
 */
static bool paraSettingOne(MicroBitStorage storage, KeyValueInt* para, bool change, bool disp)
{
    if(disp){
 	   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:変更する時
 * @return  char 0 : Success    DISP_BLE_ERROR_WRITEDDATA : Failed
 */
static char paraSettingFreq(bool change=false)
{
    MicroBitStorage storage;

    kviKeyCode1.set(FlashGet(storage, kviKeyCode1.key, 1));
    if (paraSettingOne(storage, &kviKeyCode1, change, true)) return 0;
    else return DISP_BLE_ERROR_WRITEDDATA;
}

/** ----------  
 * @brief  デバイス固有のパラメータの表示と変更
 * @param  change   true:変更する時
 * @return  char 0 : Success    DISP_BLE_ERROR_WRITEDDATA : Failed
 */
static char paraSettingSpec(bool change=false)
{
    MicroBitStorage storage;

//    kviStickDirec.set(FlashGet(storage, kviStickDirec.key, 1));

    if(State == STATE_SETTING_DEVICE) {
//        if (paraSettingOne(storage, &kviStickDirec, true , true)) return 0;
//        else return DISP_BLE_ERROR_WRITEDDATA;
    } else  {
//        if (paraSettingOne(storage, &kviStickDirec, false , false)) return 0;
//        else return DISP_BLE_ERROR_WRITEDDATA;
    }
}
//---------------------------------
//  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);
MicroBitButton P0(MICROBIT_PIN_P0, MICROBIT_ID_IO_P0, MICROBIT_BUTTON_ALL_EVENTS, PullUp);
MicroBitButton P1(MICROBIT_PIN_P1, MICROBIT_ID_IO_P1, MICROBIT_BUTTON_ALL_EVENTS, PullUp);
MicroBitButton P2(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 = DISP_ERROR_INCORRECT_CODE;      // キーコード設定間違い
        } else {
            printf("  Cnt=%d  code=%d  modif=%d\r\n",Cnt++, key , modif);
            wait(0.05);
            ble_error_t ret = KbdServicePtr->keyDownCode(key, modif);
            if (ret) {
                DispChar = DISP_BLE_ERROR_SENDDATA;
                DispCharLast = DISP_NO_MESSAGE;   
                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()) ) {
        KeyBuff.push(BUTTON_STATUS_HOLD + BUTTON_NAME_A + BUTTON_NAME_B);
     }
}

static void ButtonHoldProcess(int button) 
{
    int code = 0;
    switch(button) {
        case BUTTON_NAME_A + BUTTON_NAME_B :
            DispChar = DISP_HOLD_BUTTON_AB;
            code = keyCodeGroup_AB[2];
            break;
    }
    if (code != 0 ) sendKeyCode(code);
}

/** ----------  
 * @brief  ボタン押下げ時イベント
 * @para   e  イベント
 */
static void onButtonDown(MicroBitEvent e)
{
    switch (State) {
        case STATE_SETTING_FREQ :
        case STATE_SETTING_DEVICE :
            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 :
            switch(e.source) {
                case MICROBIT_ID_BUTTON_A :
					KeyBuff.push(BUTTON_STATUS_DOWN + BUTTON_NAME_A);
                    break;
                case MICROBIT_ID_BUTTON_B :
					KeyBuff.push(BUTTON_STATUS_DOWN + BUTTON_NAME_B);
                    break;
                case MICROBIT_ID_IO_P0 :
					KeyBuff.push(BUTTON_STATUS_DOWN + BUTTON_NAME_P0);
                    break;
                case MICROBIT_ID_IO_P1 :
					KeyBuff.push(BUTTON_STATUS_DOWN + BUTTON_NAME_P1);
                    break;
                case MICROBIT_ID_IO_P2 :
					KeyBuff.push(BUTTON_STATUS_DOWN + BUTTON_NAME_P2);
                    break;
            }
    }
}

static void ButtonDownProcess(int button) 
{
    int code = 0;
    switch(button) {
        case BUTTON_NAME_A :
            DispChar = DISP_PRESS_BUTTON_A;
            code = keyCodeGroup_AB[0];
            break;
        case BUTTON_NAME_B :
            DispChar = DISP_PRESS_BUTTON_B;
            code = keyCodeGroup_AB[1];
            break;
        case BUTTON_NAME_P0 :
            DispChar = DISP_ACTIVE_IO_P0;
            code = keyCodeGroup_3SW[kviKeyCode1.value-1][0];
            break;
        case BUTTON_NAME_P1 :
            DispChar = DISP_ACTIVE_IO_P1;
            code = keyCodeGroup_3SW[kviKeyCode1.value-1][1];
            break;
        case BUTTON_NAME_P2 :
            DispChar = DISP_ACTIVE_IO_P2;
            code = keyCodeGroup_3SW[kviKeyCode1.value-1][2];
            break;
    }
    if (code != 0 ) sendKeyCode(code);
}

/** ----------  
 * @brief  ボタン離し時イベント
 * @para   e  イベント
 */
static void onButtonUp(MicroBitEvent e)
{
    switch (State) {
        case STATE_SETTING_FREQ :
        case STATE_SETTING_DEVICE :
            if(Setting_enter) Setting_next = true;    // 決定ボタンを離したら次へ
            break;
        case STATE_OPERATING :
			KeyBuff.push(BUTTON_STATUS_UP);
    }
}

static void ButtonUpProcess() 
{
    DispChar = DISP_NO_MESSAGE;
    wait(0.05);
    ble_error_t ret = KbdServicePtr->keyUpCode();
    if (ret) {
        DispChar = DISP_BLE_ERROR_SENDDATA;
        DispCharLast = DISP_NO_MESSAGE;   
    }
}

//---------------------------------
//  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_FREQ;
    } else if(bB) {         // ボタンBを押しながら起動
        State = STATE_SETTING_DEVICE;
    }
    
//----- Display
    display.setDisplayMode(DISPLAY_MODE_BLACK_AND_WHITE);
    display.clear();
	Timeout dispTime;

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

//----- Device specific settings  
    DispChar = paraSettingSpec();
//----- Setting
    DispChar = paraSettingFreq(State == STATE_SETTING_FREQ);
//----- BLE & HID
    State = STATE_DESABLE_INPUT;
    initialize_BLE_HID();
    display.clear();

//----- Wait to connect
    display.printChar(DISP_BLE_WAIT);
    while (BleMessage != BLE_CONNECTED) {
        wait(0.15);
        ble.waitForEvent();        // BLEイベントを待つ
        if(BleMessage != BLE_NO_MESSAGE) {    // BLEの状態を表示する
            display.printChar(bleDispChar[BleMessage] );
        }
    }
    wait(3.0);
    BleMessage = BLE_NO_MESSAGE;
    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(! KeyBuff.empty()) {          // キーバッファ処理
//            DEBUG("  size=%d  front=%x\r\n", KeyBuff.size(), KeyBuff.front());

		    switch (KeyBuff.front() & 0xFF00) {
        		case BUTTON_STATUS_UP :
					ButtonUpProcess();
			    	break;
        		case BUTTON_STATUS_DOWN :
					ButtonDownProcess(KeyBuff.front() & 0xFF);
			        break;
        		case BUTTON_STATUS_HOLD :
					ButtonHoldProcess(KeyBuff.front() & 0xFF);
			        break;
			}
			KeyBuff.pop();
		}

        if (DispChar != DispCharLast) {  // 表示文字が変更
            display.enable(); 
            if (DispChar) display.printChar(DispChar);
            else          display.clear();
            dispTime.detach();
			dispTime.attach(&turnOff, TIME_TURN_OFF);    // 一定時間後に表示をOFF
        }
        DispCharLast = DispChar;
    }
}