Skip to content

File ns_ble.c

File List > neuralSPOT > neuralspot > ns-ble > src > ns_ble.c

Go to the documentation of this file

#include "ns_ble.h"

const ns_core_api_t ns_ble_V0_0_1 = {.apiId = NS_BLE_API_ID, .version = NS_BLE_V0_0_1};

// *** Globals
ns_ble_control_t g_ns_ble_control;

// *** Generic Default Configurations

static appAdvCfg_t ns_ble_default_AdvCfg = {
    {60000, 0, 0}, 
    {96, 96, 0}    
};

static appSlaveCfg_t ns_ble_default_SlaveCfg = {
    NS_BLE_CONN_MAX, 
};

static appSecCfg_t ns_ble_default_SecCfg = {
    DM_AUTH_BOND_FLAG | DM_AUTH_SC_FLAG, // TODO SECURE vs non-secure
    0,                                      
    DM_KEY_DIST_LTK,                        
    FALSE,                                  
    FALSE                                   
};

static appUpdateCfg_t ns_ble_default_UpdateCfg = {
    3100, 
    // 3000, /*! Connection idle period in ms before attempting */
    8,   
    18,  
    0,   
    600, 
    5    
};

static smpCfg_t ns_ble_default_SmpCfg = {
    // 3000,                /*! 'Repeated attempts' timeout in msec */
    3200,                
    SMP_IO_NO_IN_NO_OUT, 
    7,                   
    16,                  
    3,                   
    0,                   
};

static const uint8_t ns_ble_generic_data_disc[] = {
    2,                        
    DM_ADV_TYPE_FLAGS,        
    DM_FLAG_LE_GENERAL_DISC | 
        DM_FLAG_LE_BREDR_NOT_SUP,

    2,                    
    DM_ADV_TYPE_TX_POWER, 
    0,                    
    3,                   
    DM_ADV_TYPE_16_UUID, 
    UINT16_TO_BYTES(ATT_UUID_DEVICE_INFO_SERVICE),

    17,                   
    DM_ADV_TYPE_128_UUID, 
    0,
    0,
    0,
    0,
    0,
    0,
    0,
    0,
    0,
    0,
    0,
    0,
    0,
    0,
    0,
    0,
};

static const uint8_t ns_ble_generic_scan_data_disc[] = {
    32,                     
    DM_ADV_TYPE_LOCAL_NAME, 
    'n',
    's',
    '_',
    'g',
    'e',
    'n',
    'e',
    'r', // 31 bytes
    'i',
    'c',
    ' ',
    ' ',
    ' ',
    ' ',
    ' ',
    ' ',
    ' ',
    ' ',
    ' ',
    ' ',
    ' ',
    ' ',
    ' ',
    ' ',
    ' ',
    ' ',
    ' ',
    ' ',
    ' ',
    ' ',
    ' ',
};

/*************************************************************************************************/
/*************************************************************************************************/
static void ns_ble_generic_DmCback(dmEvt_t *pDmEvt) {
    dmEvt_t *pMsg;
    uint16_t len;
    // ns_lp_printf("ns_ble_generic_DmCback\n");
    len = DmSizeOfEvt(pDmEvt);

    if ((pMsg = WsfMsgAlloc(len)) != NULL) {
        memcpy(pMsg, pDmEvt, len);
        // ns_lp_printf(
        // "ns_ble_generic_DmCback: pMsg->hdr.event = %d\n", (ns_ble_msg_t *)pMsg->hdr.event);
        WsfMsgSend(g_ns_ble_control.handlerId, pMsg);
    }
}

/*************************************************************************************************/
/*************************************************************************************************/
static void ns_ble_generic_AttCback(attEvt_t *pEvt) {
    attEvt_t *pMsg;
    if ((pMsg = WsfMsgAlloc(sizeof(attEvt_t) + pEvt->valueLen)) != NULL) {
        memcpy(pMsg, pEvt, sizeof(attEvt_t));
        pMsg->pValue = (uint8_t *)(pMsg + 1);
        memcpy(pMsg->pValue, pEvt->pValue, pEvt->valueLen);
        // ns_lp_printf(
        // "ns_ble_generic_AttCback: pMsg->pValue = %d\n", (ns_ble_msg_t *)pMsg->hdr.event);
        WsfMsgSend(g_ns_ble_control.handlerId, pMsg);
    }
}

/*************************************************************************************************/
/*************************************************************************************************/
static void ns_ble_generic_CccCback(attsCccEvt_t *pEvt) {
    attsCccEvt_t *pMsg;
    appDbHdl_t dbHdl;
    // ns_lp_printf("ns_ble_generic_CccCback\n");
    /* if CCC not set from initialization and there's a device record */
    if ((pEvt->handle != ATT_HANDLE_NONE) &&
        ((dbHdl = AppDbGetHdl((dmConnId_t)pEvt->hdr.param)) != APP_DB_HDL_NONE)) {
        /* store value in device database */
        // ns_lp_printf("ns_ble_generic_CccCback: pEvt->handle = %d\n", pEvt->handle);
        AppDbSetCccTblValue(dbHdl, pEvt->idx, pEvt->value);
    }

    if ((pMsg = WsfMsgAlloc(sizeof(attsCccEvt_t))) != NULL) {
        memcpy(pMsg, pEvt, sizeof(attsCccEvt_t));
        WsfMsgSend(g_ns_ble_control.handlerId, pMsg);
    } else {
        ns_lp_printf("ns_ble_generic_CccCback allocated failed\n");
    }
}

static void ns_ble_generic_conn_update(dmEvt_t *pMsg) {
    // hciLeConnUpdateCmplEvt_t *evt = (hciLeConnUpdateCmplEvt_t *)pMsg;

    // ns_lp_printf("connection update status = 0x%x\n", evt->status);

    // if (evt->status == 0) {
    //     ns_lp_printf("handle = 0x%x", evt->handle);
    //     ns_lp_printf("connInterval = 0x%x", evt->connInterval);
    //     ns_lp_printf("connLatency = 0x%x", evt->connLatency);
    //     ns_lp_printf("supTimeout = 0x%x", evt->supTimeout);
    // }
}

static void ns_ble_generic_conn_open(dmEvt_t *pMsg) {
    // hciLeConnCmplEvt_t *evt = (hciLeConnCmplEvt_t *)pMsg;

    // ns_lp_printf("connection opened\n");
    // ns_lp_printf("handle = 0x%x\n", evt->handle);
    // ns_lp_printf("role = 0x%x\n", evt->role);
    // APP_TRACE_INFO3(
    //     "addrMSB = %02x%02x%02x%02x%02x%02x\n", evt->peerAddr[0], evt->peerAddr[1],
    //     evt->peerAddr[2]);
    // APP_TRACE_INFO3(
    //     "addrLSB = %02x%02x%02x%02x%02x%02x\n", evt->peerAddr[3], evt->peerAddr[4],
    //     evt->peerAddr[5]);
    // ns_lp_printf("connInterval = 0x%x\n", evt->connInterval);
    // ns_lp_printf("connLatency = 0x%x\n", evt->connLatency);
    // ns_lp_printf("supTimeout = 0x%x\n", evt->supTimeout);
}

static void ns_ble_generic_advSetup(ns_ble_msg_t *pMsg) {
    ns_ble_service_control_t *svc = g_ns_ble_control.service_config;
    // ns_lp_printf("ns_ble_generic_advSetup\n");
    /* set advertising and scan response data for discoverable mode */
    AppAdvSetData(APP_ADV_DATA_DISCOVERABLE, svc->advDataLen, (uint8_t *)svc->advData);
    AppAdvSetData(APP_SCAN_DATA_DISCOVERABLE, svc->scanDataLen, (uint8_t *)svc->scanData);

    /* set advertising and scan response data for connectable mode */
    AppAdvSetData(APP_ADV_DATA_CONNECTABLE, svc->advDataLen, (uint8_t *)svc->advData);
    AppAdvSetData(APP_SCAN_DATA_CONNECTABLE, svc->scanDataLen, (uint8_t *)svc->scanData);

    /* start advertising; automatically set connectable/discoverable mode and bondable mode */
    AppAdvStart(APP_MODE_AUTO_INIT);
}

static void ns_ble_generic_procMsg(ns_ble_msg_t *pMsg) {
    uint8_t uiEvent = APP_UI_NONE;
    ns_ble_service_control_t *svc = g_ns_ble_control.service_config;
    // ns_lp_printf("ns_ble_generic_procMsg %d\n", pMsg->hdr.event);
    // Pass it to the service message handler first, if it returns true, then it means
    // the message is handled by the service
    if (svc->procMsg_cb != NULL && svc->procMsg_cb(pMsg)) {
        return;
    }

    switch (pMsg->hdr.event) {
    case ATTS_HANDLE_VALUE_CNF:
        ns_lp_printf("ATTS_HANDLE_VALUE_CNF, status = %d", pMsg->att.hdr.status);
        break;

    case ATTS_CCC_STATE_IND:
        ns_lp_printf("ATTS_CCC_STATE_IND\n");
        break;

    case ATT_MTU_UPDATE_IND:
        ns_lp_printf("Negotiated MTU %d", ((attEvt_t *)pMsg)->mtu);
        break;

    case DM_CONN_DATA_LEN_CHANGE_IND:
        APP_TRACE_INFO2(
            "DM_CONN_DATA_LEN_CHANGE_IND, Tx=%d, Rx=%d",
            ((hciLeDataLenChangeEvt_t *)pMsg)->maxTxOctets,
            ((hciLeDataLenChangeEvt_t *)pMsg)->maxRxOctets);
        break;

    case DM_RESET_CMPL_IND:
        ns_lp_printf("DM_RESET_CMPL_IND\n");
        AttsCalculateDbHash();
        DmSecGenerateEccKeyReq();
        ns_ble_generic_advSetup(pMsg);
        uiEvent = APP_UI_RESET_CMPL;
        break;

    case DM_ADV_START_IND:
        uiEvent = APP_UI_ADV_START;
        break;

    case DM_ADV_STOP_IND:
        uiEvent = APP_UI_ADV_STOP;
        break;

    case DM_CONN_OPEN_IND:
        ns_ble_generic_conn_open((dmEvt_t *)pMsg);
        uiEvent = APP_UI_CONN_OPEN;
        break;

    case DM_CONN_CLOSE_IND:
        ns_lp_printf("DM_CONN_CLOSE_IND, reason = 0x%x", pMsg->dm.connClose.reason);
        uiEvent = APP_UI_CONN_CLOSE;
        break;

    case DM_CONN_UPDATE_IND:
        ns_ble_generic_conn_update((dmEvt_t *)pMsg);
        break;

    case DM_PHY_UPDATE_IND:
        APP_TRACE_INFO3(
            "DM_PHY_UPDATE_IND status: %d, RX: %d, TX: %d", pMsg->dm.phyUpdate.status,
            pMsg->dm.phyUpdate.rxPhy, pMsg->dm.phyUpdate.txPhy);
        break;

    case DM_SEC_PAIR_CMPL_IND:
        if (g_ns_ble_control.secureConnections) {
            DmSecGenerateEccKeyReq();
        }
        uiEvent = APP_UI_SEC_PAIR_CMPL;
        break;

    case DM_SEC_PAIR_FAIL_IND:
        if (g_ns_ble_control.secureConnections) {
            DmSecGenerateEccKeyReq();
        }
        ns_lp_printf("DM_SEC_PAIR_FAIL_IND, status = 0x%x", pMsg->dm.pairCmpl.hdr.status);
        uiEvent = APP_UI_SEC_PAIR_FAIL;
        break;

    case DM_SEC_ENCRYPT_IND:
        uiEvent = APP_UI_SEC_ENCRYPT;
        break;

    case DM_SEC_ENCRYPT_FAIL_IND:
        uiEvent = APP_UI_SEC_ENCRYPT_FAIL;
        break;

    case DM_SEC_AUTH_REQ_IND:
        AppHandlePasskey(&pMsg->dm.authReq);
        break;

    case DM_SEC_ECC_KEY_IND:
        DmSecSetEccKey(&pMsg->dm.eccMsg.data.key);
        break;

    case DM_SEC_COMPARE_IND:
        AppHandleNumericComparison(&pMsg->dm.cnfInd);
        break;

    case DM_HW_ERROR_IND:
        ns_lp_printf("DM_HW_ERROR_IND\n");
        uiEvent = APP_UI_HW_ERROR;
        break;

    case DM_VENDOR_SPEC_CMD_CMPL_IND:
        break;

    default:
        break;
    }

    if (uiEvent != APP_UI_NONE) {
        AppUiAction(uiEvent);
    }
}

void ns_ble_generic_handlerInit(wsfHandlerId_t handlerId, ns_ble_service_control_t *cfg) {
    // ns_lp_printf("ns_ble_generic_handlerInit\n");
    /* store handler ID */
    cfg->handlerId = handlerId;
    g_ns_ble_control.handlerId = handlerId;

    /* Set configuration pointers */
    pAppAdvCfg = g_ns_ble_control.advCfg;
    pAppSlaveCfg = g_ns_ble_control.slaveCfg;
    pAppSecCfg = g_ns_ble_control.secCfg;
    pAppUpdateCfg = g_ns_ble_control.updateCfg;

    /* Set stack configuration pointers */
    pSmpCfg = g_ns_ble_control.smpCfg;

    // Application specific initialization
    if (cfg->handler_init_cb != NULL)
        cfg->handler_init_cb(handlerId);
    // if (cfg->service_init_cb != NULL)
    //     cfg->service_init_cb(handlerId, &g_ns_ble_control, cfg);

    /* Initialize application framework */
    AppSlaveInit();
}

void ns_ble_generic_handler(wsfEventMask_t event, wsfMsgHdr_t *pMsg) {
    // ns_lp_printf("ns_ble_generic_handler: %d\n", event);
    if (pMsg != NULL) {
        // ns_lp_printf("Amdtp got evt %d\n", pMsg->event);

        /* process ATT messages */
        if (pMsg->event >= ATT_CBACK_START && pMsg->event <= ATT_CBACK_END) {
            /* process server-related ATT messages */
            AppServerProcAttMsg(pMsg);
        }
        /* process DM messages */
        else if (pMsg->event >= DM_CBACK_START && pMsg->event <= DM_CBACK_END) {
            /* process advertising and connection-related messages */
            // ns_lp_printf("Amdtp got evt %d\n", pMsg->event);

            AppSlaveProcDmMsg((dmEvt_t *)pMsg);

            /* process security-related messages */
            AppSlaveSecProcDmMsg((dmEvt_t *)pMsg);
        }

        /* perform profile and user interface-related operations */
        if (g_ns_ble_control.service_config->handler_cb != NULL)
            g_ns_ble_control.service_config->handler_cb(event, pMsg);

        ns_ble_generic_procMsg((ns_ble_msg_t *)pMsg);

        // amdtpProcMsg((amdtpMsg_t *) pMsg);
    }
}

void ns_ble_generic_init(
    bool useDefault, ns_ble_control_t *generic_cfg, ns_ble_service_control_t *service_cfg) {
    wsfHandlerId_t handlerId;
    uint16_t wsfBufMemLen;
    // Boot the radio.
    HciDrvRadioBoot(1);
    // Initialize the control block.
    if (useDefault) {
        memset(&g_ns_ble_control, 0, sizeof(g_ns_ble_control));

        // set default config, should work most of the time
        g_ns_ble_control.api = &ns_ble_V0_0_1;
        g_ns_ble_control.advCfg = &ns_ble_default_AdvCfg;
        g_ns_ble_control.slaveCfg = &ns_ble_default_SlaveCfg;
        g_ns_ble_control.secCfg = &ns_ble_default_SecCfg;
        g_ns_ble_control.smpCfg = &ns_ble_default_SmpCfg;
        g_ns_ble_control.updateCfg = &ns_ble_default_UpdateCfg;
        // g_ns_ble_control.connCfg = &ns_ble_connCfg_default;
        // g_ns_ble_control.appDiscCfg = &ns_ble_appDiscCfg_default;
        // g_ns_ble_control.appCfg = &ns_ble_appCfg_default;
        g_ns_ble_control.secureConnections = true;
    } else {
        // API Sanity Check

        // Copy in desired config (only config sections are valid at this point)
        if (&g_ns_ble_control != generic_cfg) {
            memcpy(&g_ns_ble_control, generic_cfg, sizeof(ns_ble_control_t));
        }
    }
    g_ns_ble_control.nextHandleId = 0;

    g_ns_ble_control.service_config = service_cfg;

    // Initialize the WSF OS.
    // Set up timers for the WSF scheduler.
    WsfOsInit();
    WsfTimerInit();

    // Initialize a buffer pool for WSF dynamic memory needs.
    wsfBufMemLen = WsfBufInit(
        service_cfg->bufferPoolSize, (uint8_t *)service_cfg->bufferPool, service_cfg->wsfBufCount,
        service_cfg->bufferDescriptors);

    if (wsfBufMemLen > service_cfg->bufferPoolSize) {
        ns_lp_printf(
            "Memory pool is too small by %d\r\n", wsfBufMemLen - service_cfg->bufferPoolSize);
    }

    // Initialize the WSF security service.
    SecInit();
    SecAesInit();
    SecCmacInit();
    SecEccInit();

    // Set up callback functions for the various layers of the ExactLE stack.
    handlerId = WsfOsSetNextHandler(HciHandler);
    HciHandlerInit(handlerId);

    handlerId = WsfOsSetNextHandler(DmHandler);
    DmDevVsInit(0);
    DmAdvInit();
    DmPhyInit();
    DmConnInit();
    DmConnSlaveInit();
    DmSecInit();
    DmSecLescInit();
    DmPrivInit();
    DmHandlerInit(handlerId);

    handlerId = WsfOsSetNextHandler(L2cSlaveHandler);
    L2cSlaveHandlerInit(handlerId);
    L2cInit();
    L2cSlaveInit();

    handlerId = WsfOsSetNextHandler(AttHandler);
    AttHandlerInit(handlerId);
    AttsInit();
    AttsIndInit();
    AttcInit();

    handlerId = WsfOsSetNextHandler(SmpHandler);
    SmpHandlerInit(handlerId);
    SmprInit();
    SmprScInit();
    HciSetMaxRxAclLen(251);

    handlerId = WsfOsSetNextHandler(AppHandler);
    AppHandlerInit(handlerId);

    handlerId = WsfOsSetNextHandler(ns_ble_generic_handler);
    ns_ble_generic_handlerInit(handlerId, service_cfg);

    handlerId = WsfOsSetNextHandler(HciDrvHandler);
    HciDrvHandlerInit(handlerId);

    // Register Generic BLE Callbacks
    DmRegister(&ns_ble_generic_DmCback);
    DmConnRegister(DM_CLIENT_ID_APP, &ns_ble_generic_DmCback);
    AttRegister(&ns_ble_generic_AttCback);
    AttConnRegister(AppServerConnCback);
    AttsCccRegister(service_cfg->cccCount, service_cfg->cccSet, &ns_ble_generic_CccCback);

    SvcCoreGattCbackRegister(GattReadCback, GattWriteCback);

    // Add generic groups
    SvcCoreAddGroup();
    SvcDisAddGroup();
#if defined(AM_PART_APOLLO3P) || defined(AM_PART_APOLLO3)
    HciVscSetRfPowerLevelEx(TX_POWER_LEVEL_MINUS_10P0_dBm);
#else
    HciVscSetRfPowerLevelEx(TX_POWER_LEVEL_MINUS_20P0_dBm);
#endif

    // if (useDefault) {
    //     *control = &g_ns_ble_control;
    // }
}

#if defined(AM_PART_APOLLO3P) || defined(AM_PART_APOLLO3)
void am_ble_isr(void) { HciDrvIntService(); }
#else
void am_cooper_irq_isr(void) {
    uint32_t ui32IntStatus;
    AM_CRITICAL_BEGIN
    am_hal_gpio_interrupt_irq_status_get(AM_COOPER_IRQn, false, &ui32IntStatus);
    am_hal_gpio_interrupt_irq_clear(AM_COOPER_IRQn, ui32IntStatus);
    AM_CRITICAL_END
    am_hal_gpio_interrupt_service(AM_COOPER_IRQn, ui32IntStatus);
}
#endif
void am_uart_isr(void) {
    uint32_t ui32Status;

    // Read and save the interrupt status, but clear out the status register.
    ui32Status = UARTn(0)->MIS;
    UARTn(0)->IEC = ui32Status;
}

//*****************************************************************************
// ns-ble Simplified BLE API
//*****************************************************************************

void ns_ble_pre_init(void) {
// Set NVICs for BLE
#if defined(AM_PART_APOLLO3P) || defined(AM_PART_APOLLO3)
    NVIC_SetPriority(BLE_IRQn, NVIC_configMAX_SYSCALL_INTERRUPT_PRIORITY);
#else
    NVIC_SetPriority(COOPER_IOM_IRQn, 4);
    NVIC_SetPriority(AM_COOPER_IRQn, 4);
#endif
}

void ns_ble_new_handler(wsfEventMask_t event, wsfMsgHdr_t *pMsg) {
    // ns_lp_printf("ns_ble_new_handler\n");
}

void ns_ble_new_handler_init(wsfHandlerId_t handlerId) {
    // ns_lp_printf("ns_ble_new_handler_init\n");
}

static void ns_ble_generic_new_handle_cnf(attEvt_t *pMsg){};

void ns_ble_send_value(ns_ble_characteristic_t *c, attEvt_t *pMsg) {
    dmConnId_t connId = 1;
    // ns_lp_printf("ns_ble_send_value");
    if (AttsCccEnabled(connId, c->indicationTimer.msg.status)) {
        int ret = AttsSetAttr(c->valueHandle, c->valueLen, c->applicationValue);
        if (ret != ATT_SUCCESS) {
            ns_lp_printf("... failed to send\n");
        }
        ns_interrupt_master_disable(); // critical region
        AttsHandleValueNtf(connId, c->valueHandle, c->valueLen, c->applicationValue);
        ns_interrupt_master_enable();
    } else {
        // ns_lp_printf("... not sent\n");
    }
}

static bool ns_ble_handle_indication_timer_expired(ns_ble_msg_t *pMsg) {
    uint8_t event = pMsg->hdr.event;
    ns_ble_service_t *service;
    // ns_lp_printf("ns_ble_handle_indication_timer_expired\n");
    for (int i = 0; i < g_ns_ble_control.numServices; i++) {
        service = g_ns_ble_control.services[i];
        for (int j = 0; j < service->numCharacteristics; j++) {
            if (service->characteristics[j]->cccIndicationHandle == event) {
                ns_ble_characteristic_t *c = service->characteristics[j];
                // Found a match, handle timer expiry
                // Call the callback to update the value of attribute
                c->notifyHandlerCb(service, c);

                // Send the value if not asynchronous
                if (c->indicationIsAsynchronous == false) {
                    ns_ble_send_value(c, (attEvt_t *)pMsg);
                }

                // Restart timer
                WsfTimerStartMs(&c->indicationTimer, c->indicationPeriod);
                return true;
            }
        }
    }
    return false;
}

static void ns_ble_process_ccc_state(attsCccEvt_t *pMsg) {
    uint8_t idx = pMsg->idx;
    ns_ble_service_t *service;
    // ns_lp_printf("ns_ble_process_ccc_state\n");
    for (int i = 0; i < g_ns_ble_control.numServices; i++) {
        service = g_ns_ble_control.services[i];
        for (int j = 0; j < service->numCharacteristics; j++) {
            ns_ble_characteristic_t *c = service->characteristics[j];
            // ns_lp_printf("webbleProcessCccState: %d %d %d\n", j, idx, c->cccIndex);
            if (service->characteristics[j]->cccIndex == idx) {
                if (pMsg->value == ATT_CLIENT_CFG_NOTIFY) {
                    // Start the timer
                    ns_lp_printf("webbleStartTimer\n");
                    WsfTimerStartMs(&c->indicationTimer, c->indicationPeriod);
                } else {
                    // Stop the timer
                    ns_lp_printf("webbleStopTimer\n");
                    WsfTimerStop(&c->indicationTimer);
                }
            }
        }
    }
}

bool ns_ble_new_proc_msg(ns_ble_msg_t *pMsg) {
    bool messageHandled = true;
    // ns_lp_printf("ns_ble_new_proc_msg\n");
    // ns_lp_printf("ns_ble_new_proc_msg: %d\n", pMsg->hdr.event);
    switch (pMsg->hdr.event) {
    case DM_CONN_OPEN_IND:
        ns_ble_generic_conn_open((dmEvt_t *)pMsg);
        DmConnSetDataLen(1, 251, 0x848);
        break;

    case DM_CONN_CLOSE_IND:
        // amdtps_conn_close((dmEvt_t *) pMsg);
        break;

    case ATTS_CCC_STATE_IND:
        ns_ble_process_ccc_state((attsCccEvt_t *)pMsg);
        break;

    case ATTS_HANDLE_VALUE_CNF:
        ns_ble_generic_new_handle_cnf((attEvt_t *)pMsg);
        break;
    default:
        // Check to see if even matches an indication handle, if so, call the
        // handler.
        messageHandled = ns_ble_handle_indication_timer_expired(pMsg);
        break;
    }

    return messageHandled;
}

int ns_ble_char2uuid(char const uuidString[16], ns_ble_uuid128_t *uuid128) {
    // Convert the string into uint array needed by WSF
    // Written by CoPilot!

    // ns_lp_printf("ns_ble_char2uuid: %s\n", uuidString);
    for (int i = 0; i < 16; i++) {
        char c1 = uuidString[i * 2];
        char c2 = uuidString[i * 2 + 1];
        if (c1 >= '0' && c1 <= '9') {
            uuid128->array[15 - i] = (c1 - '0') << 4;
        } else if (c1 >= 'a' && c1 <= 'f') {
            uuid128->array[15 - i] = (c1 - 'a' + 10) << 4;
        } else if (c1 >= 'A' && c1 <= 'F') {
            uuid128->array[15 - i] = (c1 - 'A' + 10) << 4;
        } else {
            return -1;
        }

        if (c2 >= '0' && c2 <= '9') {
            uuid128->array[15 - i] |= (c2 - '0');
        } else if (c2 >= 'a' && c2 <= 'f') {
            uuid128->array[15 - i] |= (c2 - 'a' + 10);
        } else if (c2 >= 'A' && c2 <= 'F') {
            uuid128->array[15 - i] |= (c2 - 'A' + 10);
        } else {
            return -1;
        }
    }

    return NS_STATUS_SUCCESS;
}

uint16_t ns_ble_get_next_handle_id(ns_ble_service_t *service) {
    return g_ns_ble_control.nextHandleId++;
}

uint8_t ns_ble_generic_write_cback(
    dmConnId_t connId, uint16_t handle, uint8_t operation, uint16_t offset, uint16_t len,
    uint8_t *pValue, attsAttr_t *pAttr) {
    // ns_lp_printf("ns_ble_generic_write_cback, handle %d\n", handle);
    for (int i = 0; i < g_ns_ble_control.numServices; i++) {
        ns_ble_service_t *service = g_ns_ble_control.services[i];
        for (int j = 0; j < service->numCharacteristics; j++) {
            if (service->characteristics[j]->valueHandle == handle) {
                if (service->characteristics[j]->writeHandlerCb) {
                    return service->characteristics[j]->writeHandlerCb(
                        service, service->characteristics[j], pValue);
                }
            }
        }
    }

    return ATT_ERR_HANDLE;
}

uint8_t ns_ble_generic_read_cback(
    dmConnId_t connId, uint16_t handle, uint8_t operation, uint16_t offset, attsAttr_t *pAttr) {
    // ns_lp_printf("ns_ble_generic_read_cback, handle %d\n", handle);
    for (int i = 0; i < g_ns_ble_control.numServices; i++) {
        ns_ble_service_t *service = g_ns_ble_control.services[i];
        for (int j = 0; j < service->numCharacteristics; j++) {
            if (service->characteristics[j]->valueHandle == handle) {
                if (service->characteristics[j]->readHandlerCb) {
                    return service->characteristics[j]->readHandlerCb(
                        service, service->characteristics[j], pAttr->pValue);
                }
            }
        }
    }
    ns_lp_printf("ns_ble_generic_read_cback, handle %d, not found\n", handle);
    return ATT_ERR_HANDLE;
}

// Create a Service
int ns_ble_create_service(ns_ble_service_t *service) {
    service->nextHandleId = service->baseHandle;
    service->handleId = ns_ble_get_next_handle_id(service);
    service->attributes = NULL;
    service->readCback = ns_ble_generic_read_cback;
    service->writeCback = ns_ble_generic_write_cback;

    // *** Fill in service attributes
    // Primary Service Declaration
    service->primaryAttributeLen = sizeof(service->uuid128.array); // need a pointer for some reason
    service->primaryAttribute.pUuid = attPrimSvcUuid;
    service->primaryAttribute.pValue = service->uuid128.array;
    service->primaryAttribute.pLen = &(service->primaryAttributeLen);
    service->primaryAttribute.maxLen = sizeof(service->uuid128.array);
    service->primaryAttribute.settings = 0;
    service->primaryAttribute.permissions = ATTS_PERMIT_READ;

    uint16_t incomingNumAttributes = service->numAttributes;
    service->numAttributes++;
    // Create Attribute List and add primary service declaration
    service->attributes = ns_malloc(sizeof(attsAttr_t) * service->numAttributes);
    if (service->attributes == NULL) {
        return NS_STATUS_FAILURE;
    }

    // *** Service Attributes
    // Add primary service declaration
    memcpy(&(service->attributes[0]), &(service->primaryAttribute), sizeof(attsAttr_t));
    service->nextAttributeIndex = 1;
    service->nextHandleId++;

    // Allocate memory for characteristic array
    service->characteristics =
        ns_malloc(sizeof(ns_ble_characteristic_t *) * service->numCharacteristics);
    service->nextCharacteristicIndex = 0;

    // *** Discovery and Advertisement structs
    // Copy over the generic values, then overwrite with service-specific values

    // Advertisement Data
    service->advData = ns_malloc(sizeof(ns_ble_generic_data_disc));
    if (service->advData == NULL) {
        return NS_STATUS_FAILURE;
    }
    service->advDataLen = sizeof(ns_ble_generic_data_disc);
    memcpy(service->advData, &ns_ble_generic_data_disc, sizeof(ns_ble_generic_data_disc));
    memcpy(
        service->advData + 12, &service->uuid128,
        sizeof(service->uuid128)); // uuid128 is at offset 12

    // Scan Data
    service->scanData = ns_malloc(sizeof(ns_ble_generic_scan_data_disc));
    if (service->scanData == NULL) {
        return NS_STATUS_FAILURE;
    }

    // service->scanDataLen = sizeof(ns_ble_generic_scan_data_disc);
    service->scanDataLen = service->nameLen + 2;
    memcpy(service->scanData, &ns_ble_generic_scan_data_disc, service->scanDataLen);
    memcpy(service->scanData + 2, service->name, service->nameLen); // name is at offset 2
    service->scanData[0] = service->nameLen + 1;

    // *** CCC Configuration
    // Allocate memory for CCCSet array
    // Fill in as Notify attributes are added

    // The number of CCC attributes can be calculated from numCharacteristics and numAttributes
    // There are always at least 2 attributes (declaration and value). Characteristics
    // with CCC will have a 3rd attribute (CCC).

    uint16_t numCccAttributes = incomingNumAttributes - service->numCharacteristics * 2;

    service->cccSet =
        ns_malloc(sizeof(attsCccSet_t) * (numCccAttributes + 1)); // add one for GAT_SC
    if (service->cccSet == NULL) {
        return NS_STATUS_FAILURE;
    }
    service->cccSet[0].handle = GATT_SC_CH_CCC_HDL;
    service->cccSet[0].valueRange = ATT_CLIENT_CFG_INDICATE;
    service->cccSet[0].secLevel = DM_SEC_LEVEL_NONE;
    service->nextCccIndex = 1;
    service->nextCccIndicationHandle = 0xC0; // TODO pick a better way to start this

    // *** TODO name and version attributes

    // *** Create and populate service control block
    service->control = ns_malloc(sizeof(ns_ble_service_control_t));
    if (service->control == NULL) {
        return NS_STATUS_FAILURE;
    }

    // *** Fill in service control block
    // TODO: refactor service and control block structs
    service->control->bufferPool = service->poolConfig->pool;
    service->control->bufferPoolSize = service->poolConfig->poolSize;
    service->control->bufferDescriptors = service->poolConfig->desc;
    service->control->wsfBufCount = service->poolConfig->descNum;
    service->control->advData = service->advData;
    service->control->advDataLen = service->advDataLen;
    service->control->scanData = service->scanData;
    service->control->scanDataLen = service->scanDataLen;
    service->control->cccSet = service->cccSet;
    service->control->cccCount = numCccAttributes + 1;
    service->control->handler_init_cb = &ns_ble_new_handler_init;
    service->control->handler_cb = &ns_ble_new_handler;
    service->control->procMsg_cb = &ns_ble_new_proc_msg;
    // Generic_init will fill in g_ns_ble_control with handlerIds
    // (after initializing the cordio stack.)
    ns_ble_generic_init(TRUE, &g_ns_ble_control, service->control);
    g_ns_ble_control.services[0] = service;
    g_ns_ble_control.numServices = 1;

    return NS_STATUS_SUCCESS;
}

// Create a Characteristic and related attributes
int ns_ble_create_characteristic(
    ns_ble_characteristic_t *c, const char *uuidString, void *applicationValue,
    uint16_t valueLength, uint16_t properties, ns_ble_characteristic_read_handler_t readHandlerCb,
    ns_ble_characteristic_write_handler_t writeHandlerCb,
    ns_ble_characteristic_notify_handler_t notifyHandlerCb, uint16_t periodMs, uint8_t async,
    uint16_t *attributeCount) {
    uint8_t prop = 0;
    uint16_t permissions = 0;

    c->readHandlerCb = readHandlerCb;
    c->writeHandlerCb = writeHandlerCb;
    c->notifyHandlerCb = notifyHandlerCb;

    // *** Remember mem location of attribute's value
    // (different from WSF 'value', which is a placeholder)
    c->applicationValue = applicationValue;
    c->valueLen = valueLength;

    NS_TRY(ns_ble_char2uuid(uuidString, &(c->uuid128)), "Failed to create UUID\n");

    // Parse properties
    if (properties & NS_BLE_READ) {
        permissions |= ATTS_PERMIT_READ;
        prop |= ATT_PROP_READ;
    }

    if (properties & NS_BLE_WRITE) {
        permissions |= ATTS_PERMIT_WRITE;
        prop |= ATT_PROP_WRITE;
    }

    if (properties & NS_BLE_NOTIFY) {
        prop |= ATT_PROP_NOTIFY;
    }
    c->pValue = ns_malloc(valueLength);

    // *** Create Attribute List entries (Declaration, Value, CCC)

    // Characteristic Declaration Attribute
    // Declaration Properties
    c->declarationProperties[0] = prop;
    c->declarationProperties[1] = 0; // placeholder for declaration handle
    c->declarationProperties[2] = 0; // placeholder for declaration handle
    memcpy(&(c->declarationProperties[3]), c->uuid128.array, 16);
    c->declarationLen = sizeof(c->declarationProperties);

    // Put declaration attribute together
    c->declaration.pUuid = attChUuid;
    c->declaration.pValue = c->declarationProperties;
    c->declaration.pLen = &(c->declarationLen);
    c->declaration.maxLen = sizeof(c->declarationProperties);
    c->declaration.settings = 0;
    c->declaration.permissions = ATTS_PERMIT_READ;
    *attributeCount += 1;

    // Characteristic Value Attribute
    c->value.pUuid = c->uuid128.array;
    c->value.pValue = c->pValue;
    c->value.pLen = &(c->valueLen);
    c->value.maxLen = valueLength;
    c->value.settings = ATTS_SET_UUID_128 | ATTS_SET_VARIABLE_LEN;
    if (readHandlerCb != NULL) {
        c->value.settings |= ATTS_SET_READ_CBACK;
    }
    if (writeHandlerCb != NULL) {
        c->value.settings |= ATTS_SET_WRITE_CBACK;
    }
    c->value.permissions = permissions;
    *attributeCount += 1;

    // If Notify, add CCC
    if (properties & NS_BLE_NOTIFY) {
        // Create the CCC attribute
        c->cccLen = sizeof(c->cccArray);
        c->ccc.pUuid = attCliChCfgUuid;
        c->ccc.pValue = c->cccArray;
        c->ccc.pLen = &(c->cccLen);
        c->ccc.maxLen = sizeof(c->cccArray);
        c->ccc.settings = ATTS_SET_CCC;
        c->ccc.permissions = ATTS_PERMIT_READ | ATTS_PERMIT_WRITE;
        c->indicationPeriod = periodMs;
        c->indicationIsAsynchronous = async;
        *attributeCount += 1;
    } else {
        c->ccc.pUuid = NULL;
    }

    return NS_STATUS_SUCCESS;
}

int ns_ble_add_characteristic(ns_ble_service_t *s, ns_ble_characteristic_t *c) {
    // *** Add Characteristic to Service Attributes List
    // Declaration Attribute
    memcpy(&(s->attributes[s->nextAttributeIndex++]), &(c->declaration), sizeof(attsAttr_t));

    // Value Attribute
    memcpy(&(s->attributes[s->nextAttributeIndex++]), &(c->value), sizeof(attsAttr_t));

    // CCC Attribute
    if (c->ccc.pUuid != NULL) {
        memcpy(&(s->attributes[s->nextAttributeIndex++]), &(c->ccc), sizeof(attsAttr_t));
    }

    // *** Set Characteristic Handles
    c->declarationHandle = s->nextHandleId++;
    c->valueHandle = s->nextHandleId++;
    c->declarationProperties[1] = c->valueHandle & 0xff;
    c->declarationProperties[2] = (c->valueHandle >> 8) & 0xff;

    // *** CCC table and time setup (if notify is enabled for the characteristic)
    if (c->ccc.pUuid != NULL) {
        c->cccHandle = s->nextHandleId++;
        c->cccIndicationHandle = s->nextCccIndicationHandle++;
        c->cccIndex = s->nextCccIndex;

        // Add an entry to the CCC table
        s->cccSet[s->nextCccIndex].handle = c->cccHandle;
        s->cccSet[s->nextCccIndex].valueRange = ATT_CLIENT_CFG_NOTIFY;
        s->cccSet[s->nextCccIndex].secLevel = DM_SEC_LEVEL_NONE;

        // Set up CCC timer
        c->indicationTimer.handlerId = g_ns_ble_control.handlerId;
        c->indicationTimer.msg.event = c->cccIndicationHandle;
        c->indicationTimer.msg.status = s->nextCccIndex;
        s->nextCccIndex += 1;
    }

    // Add Characteristic to Service Characteristic List
    s->characteristics[s->nextCharacteristicIndex++] = c;

    return NS_STATUS_SUCCESS;
}

int ns_ble_start_service(ns_ble_service_t *s) {
    // *** Finish creating Service structures, then kick it off
    if (s->nextCharacteristicIndex != s->numCharacteristics) {
        ns_lp_printf(
            "ns_ble_start_service: numCharacteristics mismatch, specified %d, added %d\n",
            s->numCharacteristics, s->nextCharacteristicIndex);
        return NS_STATUS_FAILURE;
    }
    // Populate Group
    s->group.pNext = NULL;
    s->group.pAttr = s->attributes;
    s->group.readCback = s->readCback;
    s->group.writeCback = s->writeCback;
    s->group.startHandle = s->baseHandle;
    s->group.endHandle = s->nextHandleId - 1;

    AttsAddGroup(&(s->group));
    GattSetSvcChangedIdx(0); // TODO something better here

    // *** Kick off the service
    DmDevReset();

    return NS_STATUS_SUCCESS;
}


int ns_ble_set_tx_power(txPowerLevel_t power) {
    // valid power level is checked in the function for both ap3 and ap4, so no need to check here
    if(HciVscSetRfPowerLevelEx(power)) {
        return NS_STATUS_SUCCESS;
    }
    else return NS_STATUS_FAILURE;
}