initial commit

This commit is contained in:
2022-10-23 23:45:43 -07:00
commit e190fa5193
6450 changed files with 8626944 additions and 0 deletions
@@ -0,0 +1,962 @@
/*************************************************************************************************/
/*!
* \file
*
* \brief Application framework service discovery and configuration.
*
* Copyright (c) 2011-2019 Arm Ltd.
*
* Copyright (c) 2019 Packetcraft, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/*************************************************************************************************/
#include <string.h>
#include "wsf_types.h"
#include "wsf_msg.h"
#include "wsf_buf.h"
#include "wsf_trace.h"
#include "util/bstream.h"
#include "dm_api.h"
#include "att_api.h"
#include "svc_ch.h"
#include "app_api.h"
#include "app_main.h"
/**************************************************************************************************
Macros
**************************************************************************************************/
/*! "In progress" values */
#define APP_DISC_IDLE 0
#define APP_DISC_SVC_DISC_IN_PROGRESS 1
#define APP_DISC_CFG_IN_PROGRESS 2
#define APP_DISC_READ_DBH_IN_PROGRESS 3
/**************************************************************************************************
Data Types
**************************************************************************************************/
/*! \brief Application Discovery controler block */
typedef struct
{
attcDiscCb_t *pDiscCb; /*! ATT discovery control block */
uint16_t *pHdlList; /*! Handle list */
uint8_t connCfgStatus; /*! Connection setup configuration status */
uint8_t cmplStatus; /*! Discovery or configuration complete status */
uint8_t hdlListLen; /*! Handle list length */
uint8_t inProgress; /*! Discovery or configuration in progress */
bool_t alreadySecure; /*! TRUE if connection was already secure */
bool_t secRequired; /*! TRUE if security is required for configuration */
bool_t scPending; /*! TRUE if service changed from peer is pending */
} appDiscCb_t;
/**************************************************************************************************
Local Variables
**************************************************************************************************/
/*! Discovery connection control blocks */
static appDiscCb_t appDiscCb[DM_CONN_MAX];
/*! Discovery callback */
static appDiscCback_t appDiscCback;
/*************************************************************************************************/
/*!
* \brief Start discovery or configuration
*
* \param connId Connection ID.
* \param status Status of discovery process.
*
* \return None.
*/
/*************************************************************************************************/
static void appDiscCfgStart(dmConnId_t connId, uint8_t status)
{
appDiscCb_t *pAppDiscCb = &appDiscCb[connId - 1];
/* if configuration not complete */
if (status < APP_DISC_CFG_CMPL)
{
/* notify application to start configuration */
(*appDiscCback)(connId, APP_DISC_CFG_START);
}
/* else if configuration complete start connection setup configuration */
else if (status == APP_DISC_CFG_CMPL && pAppDiscCb->connCfgStatus == APP_DISC_INIT)
{
(*appDiscCback)(connId, APP_DISC_CFG_CONN_START);
}
}
/*************************************************************************************************/
/*!
* \brief Start discovery or configuration
*
* \param dmConnId_t Connection ID.
*
* \return None.
*/
/*************************************************************************************************/
void appDiscStart(dmConnId_t connId)
{
appDbHdl_t hdl;
uint8_t status;
appDiscCb_t *pAppDiscCb = &appDiscCb[connId - 1];
if (pAppDiscCb->inProgress == APP_DISC_IDLE)
{
/* get discovery status */
if ((hdl = AppDbGetHdl(connId)) != APP_DB_HDL_NONE)
{
status = AppDbGetDiscStatus(hdl);
}
else
{
status = pAppDiscCb->cmplStatus;
}
/* if discovery not complete */
if (status < APP_DISC_CMPL)
{
/* Read database hash first if not bonded or if secure but without bond. */
if ((!pAppDiscCb->alreadySecure) || (pAppDiscCb->alreadySecure && !AppCheckBonded(connId)))
{
/* notify application to start discovery */
(*appDiscCback)(connId, APP_DISC_READ_DATABASE_HASH);
}
else
{
/* notify application to start discovery */
(*appDiscCback)(connId, APP_DISC_START);
}
}
/* else if discovery was completed successfully */
else if (status != APP_DISC_FAILED)
{
/* get stored handle list if present */
if (hdl != APP_DB_HDL_NONE && pAppDiscCb->pHdlList != NULL)
{
/* Read hash before using handles */
if (AppDbIsCacheCheckedByHash(hdl))
{
pAppDiscCb->inProgress = APP_DISC_READ_DBH_IN_PROGRESS;
/* Read the database hash. */
AttcReadByTypeReq(connId, ATT_HANDLE_START, ATT_HANDLE_MAX, ATT_16_UUID_LEN,
(uint8_t *)attGattDbhChUuid, FALSE);
return;
}
else
{
memcpy(pAppDiscCb->pHdlList, AppDbGetHdlList(hdl), (pAppDiscCb->hdlListLen * sizeof(uint16_t)));
}
}
appDiscCfgStart(connId, status);
}
}
}
/*************************************************************************************************/
/*!
* \brief Reset service discovery.
*
* \param connId DM Connection ID.
*
* \return None.
*/
/*************************************************************************************************/
void appDiscRestartDiscovery(dmConnId_t connId)
{
appDiscCb_t *pAppDiscCb = &appDiscCb[connId - 1];
appDbHdl_t dbHdl;
/* otherwise initialize discovery and configuration status */
pAppDiscCb->connCfgStatus = APP_DISC_INIT;
pAppDiscCb->cmplStatus = APP_DISC_INIT;
pAppDiscCb->secRequired = FALSE;
pAppDiscCb->scPending = FALSE;
/* initialize handle list */
if (pAppDiscCb->pHdlList != NULL)
{
memset(pAppDiscCb->pHdlList, 0, (pAppDiscCb->hdlListLen * sizeof(uint16_t)));
/* clear stored discovery status and handle list */
if ((dbHdl = AppDbGetHdl(connId)) != APP_DB_HDL_NONE)
{
AppDbSetDiscStatus(dbHdl, APP_DISC_INIT);
AppDbSetHdlList(dbHdl, pAppDiscCb->pHdlList);
}
}
/* if configuration in progress */
if (pAppDiscCb->inProgress == APP_DISC_CFG_IN_PROGRESS)
{
/* set pending status to set up abort of configuration */
pAppDiscCb->scPending = TRUE;
}
/* else no procedure in progress */
else
{
/* if not waiting for security or connection is already secure, then
* initiate discovery now; otherwise discovery will be initiated after
* security is done
*/
if (!pAppDiscCfg->waitForSec || pAppDiscCb->alreadySecure)
{
appDiscStart(connId);
}
}
}
/*************************************************************************************************/
/*!
* \brief Handle a DM_CONN_OPEN_IND event.
*
* \param pMsg Pointer to DM callback event message.
*
* \return None.
*/
/*************************************************************************************************/
static void appDiscConnOpen(dmEvt_t *pMsg)
{
appDiscCb_t *pAppDiscCb = &appDiscCb[(dmConnId_t) pMsg->hdr.param - 1];
pAppDiscCb->alreadySecure = FALSE;
pAppDiscCb->connCfgStatus = APP_DISC_INIT;
pAppDiscCb->cmplStatus = APP_DISC_INIT;
pAppDiscCb->secRequired = FALSE;
pAppDiscCb->scPending = FALSE;
/* tell app to set up handle list */
(*appDiscCback)((dmConnId_t) pMsg->hdr.param, APP_DISC_INIT);
/* initialize handle list */
if (pAppDiscCb->pHdlList != NULL)
{
memset(pAppDiscCb->pHdlList, 0, (pAppDiscCb->hdlListLen * sizeof(uint16_t)));
}
/* if not waiting for security start discovery/configuration */
if (!pAppDiscCfg->waitForSec)
{
appDiscStart((dmConnId_t) pMsg->hdr.param);
}
}
/*************************************************************************************************/
/*!
* \brief Handle a DM_CONN_CLOSE_IND event.
*
* \param pMsg Pointer to DM callback event message.
*
* \return None.
*/
/*************************************************************************************************/
static void appDiscConnClose(dmEvt_t *pMsg)
{
appDiscCb_t *pAppDiscCb = &appDiscCb[(dmConnId_t) pMsg->hdr.param - 1];
pAppDiscCb->inProgress = APP_DISC_IDLE;
appDbHdl_t hdl;
if ((hdl = AppDbGetHdl((dmConnId_t) pMsg->hdr.param)) != APP_DB_HDL_NONE)
{
// reset discovery status
AppDbSetDiscStatus(hdl, APP_DISC_INIT);
}
if (pAppDiscCb->pDiscCb != NULL)
{
WsfBufFree(pAppDiscCb->pDiscCb);
pAppDiscCb->pDiscCb = NULL;
}
}
/*************************************************************************************************/
/*!
* \brief Handle pairing complete.
*
* \param pMsg Pointer to DM callback event message.
*
* \return None.
*/
/*************************************************************************************************/
static void appDiscPairCmpl(dmEvt_t *pMsg)
{
appDiscCb_t *pAppDiscCb = &appDiscCb[(dmConnId_t) pMsg->hdr.param - 1];
appDbHdl_t hdl;
/* procedures triggered by security are only executed once */
if (pAppDiscCb->alreadySecure)
{
return;
}
/* if bonded, disable hash check on cache if not already disabled */
if (((hdl = AppDbGetHdl((dmConnId_t)pMsg->hdr.param)) != APP_DB_HDL_NONE) &&
AppCheckBonded((dmConnId_t) pMsg->hdr.param) &&
AppDbIsCacheCheckedByHash(hdl))
{
AppDbSetCacheByHash(appConnCb[pMsg->hdr.param - 1].dbHdl, FALSE);
}
/* if we are now bonded and discovery/configuration was performed before bonding */
if (AppCheckBonded((dmConnId_t) pMsg->hdr.param) && (pAppDiscCb->cmplStatus != APP_DISC_INIT))
{
if (hdl != APP_DB_HDL_NONE)
{
/* store discovery status */
AppDbSetDiscStatus(hdl, pAppDiscCb->cmplStatus);
/* store handle list */
if (pAppDiscCb->cmplStatus == APP_DISC_CMPL || pAppDiscCb->cmplStatus == APP_DISC_CFG_CMPL)
{
if (pAppDiscCb->pHdlList != NULL)
{
AppDbSetHdlList(hdl, pAppDiscCb->pHdlList);
}
}
}
/* if configuration was waiting for security */
if (pAppDiscCb->secRequired)
{
pAppDiscCb->secRequired = FALSE;
/* resume configuration */
if (pAppDiscCb->pDiscCb != NULL)
{
AttcDiscConfigResume((dmConnId_t) pMsg->hdr.param, pAppDiscCb->pDiscCb);
}
}
}
else {
/* if waiting for security start discovery now that connection is secure */
if (pAppDiscCfg->waitForSec)
{
appDiscStart((dmConnId_t) pMsg->hdr.param);
}
}
pAppDiscCb->alreadySecure = TRUE;
}
/*************************************************************************************************/
/*!
* \brief Handle encryption indication
*
* \param pMsg Pointer to DM callback event message.
*
* \return None.
*/
/*************************************************************************************************/
static void appDiscEncryptInd(dmEvt_t *pMsg)
{
appDiscCb_t *pAppDiscCb = &appDiscCb[(dmConnId_t) pMsg->hdr.param - 1];
/* if encrypted with ltk */
if (pMsg->encryptInd.usingLtk)
{
/* procedures triggered by security are only executed once */
if (pAppDiscCb->alreadySecure)
{
return;
}
/* if we waiting for security start discovery now that connection is secure */
if (pAppDiscCfg->waitForSec)
{
appDiscStart((dmConnId_t) pMsg->hdr.param);
}
/* else if configuration was waiting for security */
else if (pAppDiscCb->secRequired)
{
pAppDiscCb->secRequired = FALSE;
/* resume configuration */
if (pAppDiscCb->pDiscCb != NULL)
{
AttcDiscConfigResume((dmConnId_t) pMsg->hdr.param, pAppDiscCb->pDiscCb);
}
}
pAppDiscCb->alreadySecure = TRUE;
}
}
/*************************************************************************************************/
/*!
* \brief Handle pairing failure
*
* \param pMsg Pointer to DM callback event message.
*
* \return None.
*/
/*************************************************************************************************/
static void appDiscPairFail(dmEvt_t *pMsg)
{
appDiscCb_t *pAppDiscCb = &appDiscCb[(dmConnId_t)pMsg->hdr.param - 1];
/* Procedures triggered by security are only executed once. */
if (pAppDiscCb->alreadySecure)
{
return;
}
/* Fall back to relying on database hash to verify handles if configured to do so. */
if (pAppDiscCfg->readDbHash)
{
pAppDiscCb->inProgress = APP_DISC_READ_DBH_IN_PROGRESS;
/* Read the database hash instead of re-performing service discovery. */
AttcReadByTypeReq((dmConnId_t) pMsg->hdr.param, ATT_HANDLE_START, ATT_HANDLE_MAX, ATT_16_UUID_LEN,
(uint8_t *) attGattDbhChUuid, FALSE);
}
}
/*************************************************************************************************/
/*!
* \brief Process discovery-related DM messages. This function should be called
* from the application's event handler.
*
* \param pMsg Pointer to DM callback event message.
*
* \return None.
*/
/*************************************************************************************************/
void AppDiscProcDmMsg(dmEvt_t *pMsg)
{
switch(pMsg->hdr.event)
{
case DM_CONN_OPEN_IND:
appDiscConnOpen(pMsg);
break;
case DM_CONN_CLOSE_IND:
appDiscConnClose(pMsg);
break;
case DM_SEC_PAIR_CMPL_IND:
appDiscPairCmpl(pMsg);
break;
case DM_SEC_PAIR_FAIL_IND:
appDiscPairFail(pMsg);
break;
case DM_SEC_ENCRYPT_IND:
appDiscEncryptInd(pMsg);
break;
default:
break;
}
}
/*************************************************************************************************/
/*!
* \brief Process discovery-related ATT messages. This function should be called
* from the application's event handler.
*
* \param pMsg Pointer to ATT callback event message.
*
* \return None.
*/
/*************************************************************************************************/
void AppDiscProcAttMsg(attEvt_t *pMsg)
{
appDiscCb_t *pAppDiscCb = &appDiscCb[(dmConnId_t)pMsg->hdr.param - 1];
uint8_t status;
/* Check status */
if (pMsg->hdr.status == ATT_ERR_DATABASE_OUT_OF_SYNC)
{
/* Restart discovery as cached handle list is out of sync with server's database. */
appDiscRestartDiscovery((dmConnId_t)pMsg->hdr.param);
}
if (pAppDiscCb->inProgress == APP_DISC_READ_DBH_IN_PROGRESS)
{
if (pMsg->hdr.event == ATTC_READ_BY_TYPE_RSP)
{
dmConnId_t connId = (dmConnId_t)pMsg->hdr.param;
if (pMsg->hdr.status != ATT_SUCCESS)
{
/* No Database hash found on peer, notify application to start discovery */
(*appDiscCback)(connId, APP_DISC_START);
}
else
{
appDbHdl_t hdl;
pAppDiscCb->inProgress = APP_DISC_IDLE;
/* If there is no existing record, create one now. The cached handles will be
* validated across connections by the peer's database hash.
*/
if ((hdl = AppDbGetHdl(connId)) == APP_DB_HDL_NONE)
{
hdl = appConnCb[connId - 1].dbHdl = AppDbNewRecord(DmConnPeerAddrType(connId),
DmConnPeerAddr(connId),
(DmConnRole(connId)==DM_ROLE_MASTER)?TRUE:FALSE);
}
/* Compare with existing database hash for this server.
* If they do not match, perform service discovery.
*/
if (memcmp(AppDbGetPeerDbHash(hdl), pMsg->pValue + 3, ATT_DATABASE_HASH_LEN))
{
/* The new hash is different. Store it. */
AppDbSetPeerDbHash(hdl, pMsg->pValue + 3);
/* Note: it is possible this record was created without a pairing or after
* a pairing failed, validate record now so that it can be stored persistently.
*/
/* AppDbValidateRecord(hdl, 0); */
/* The validity of the cached handles is checked by the hash and not necessarily by
* service changed indications.
*/
AppDbSetCacheByHash(hdl, TRUE);
/* notify application to start discovery */
(*appDiscCback)(connId, APP_DISC_START);
}
else
{
/* Hash is the same, cached handles may be reused. */
memcpy(pAppDiscCb->pHdlList, AppDbGetHdlList(hdl),
(pAppDiscCb->hdlListLen * sizeof(uint16_t)));
/* get discovery status */
status = AppDbGetDiscStatus(hdl);
appDiscCfgStart(connId, status);
}
}
}
}
else if (pAppDiscCb->inProgress == APP_DISC_SVC_DISC_IN_PROGRESS)
{
/* service discovery */
if (pMsg->hdr.event == ATTC_FIND_BY_TYPE_VALUE_RSP)
{
/* continue with service discovery */
status = AttcDiscServiceCmpl(pAppDiscCb->pDiscCb, pMsg);
APP_TRACE_INFO1("AttcDiscServiceCmpl status 0x%02x", status);
/* if discovery complete and successful */
if (status == ATT_SUCCESS)
{
/* proceed with characteristic discovery */
AttcDiscCharStart((dmConnId_t) pMsg->hdr.param, pAppDiscCb->pDiscCb);
}
/* else if failed */
else if (status != ATT_CONTINUING)
{
/* notify application of discovery failure */
(*appDiscCback)((dmConnId_t) pMsg->hdr.param, APP_DISC_FAILED);
}
}
/* characteristic discovery */
else if (pMsg->hdr.event == ATTC_READ_BY_TYPE_RSP ||
pMsg->hdr.event == ATTC_FIND_INFO_RSP)
{
/* continue with characteristic discovery */
status = AttcDiscCharCmpl(pAppDiscCb->pDiscCb, pMsg);
APP_TRACE_INFO1("AttcDiscCharCmpl status 0x%02x", status);
/* if discovery complete and successful */
if (status == ATT_SUCCESS)
{
/* notify application of discovery success */
(*appDiscCback)((dmConnId_t) pMsg->hdr.param, APP_DISC_CMPL);
}
/* else if failed */
else if (status != ATT_CONTINUING)
{
/* notify application of discovery failure */
(*appDiscCback)((dmConnId_t) pMsg->hdr.param, APP_DISC_FAILED);
}
}
}
/* characteristic configuration */
else if ((pAppDiscCb->inProgress == APP_DISC_CFG_IN_PROGRESS) &&
(pMsg->hdr.event == ATTC_READ_RSP || pMsg->hdr.event == ATTC_WRITE_RSP))
{
/* if service changed is pending */
if (pAppDiscCb->scPending)
{
/* clear pending flag */
pAppDiscCb->scPending = FALSE;
/* start discovery */
pAppDiscCb->inProgress = APP_DISC_IDLE;
appDiscStart((dmConnId_t) pMsg->hdr.param);
}
/* else if security failure */
else if ((pMsg->hdr.status == ATT_ERR_AUTH || pMsg->hdr.status == ATT_ERR_ENC) &&
(DmConnSecLevel((dmConnId_t) pMsg->hdr.param) == DM_SEC_LEVEL_NONE))
{
/* tell application to request security */
pAppDiscCb->secRequired = TRUE;
(*appDiscCback)((dmConnId_t) pMsg->hdr.param, APP_DISC_SEC_REQUIRED);
}
else
{
status = AttcDiscConfigCmpl((dmConnId_t) pMsg->hdr.param, pAppDiscCb->pDiscCb);
APP_TRACE_INFO1("AttcDiscConfigCmpl status 0x%02x", status);
/* if configuration complete */
if (status != ATT_CONTINUING)
{
/* notify application of config success */
(*appDiscCback)((dmConnId_t) pMsg->hdr.param, APP_DISC_CFG_CMPL);
}
}
}
}
/*************************************************************************************************/
/*!
* \brief Initialize app framework discovery.
*
* \return None.
*/
/*************************************************************************************************/
void AppDiscInit(void)
{
uint8_t i;
for (i = 0; i < DM_CONN_MAX; i++)
{
appDiscCb[i].inProgress = APP_DISC_IDLE;
appDiscCb[i].pDiscCb = NULL;
}
}
/*************************************************************************************************/
/*!
* \brief Register a callback function to service discovery status.
*
* \param cback Application service discovery callback function.
*
* \return None.
*/
/*************************************************************************************************/
void AppDiscRegister(appDiscCback_t cback)
{
appDiscCback = cback;
}
/*************************************************************************************************/
/*!
* \brief Set the discovery cached handle list for a given connection.
*
* \param connId Connection identifier.
* \param listLen Length of characteristic and handle lists.
* \param pHdlList Characteristic handle list.
*
* \return None.
*/
/*************************************************************************************************/
void AppDiscSetHdlList(dmConnId_t connId, uint8_t hdlListLen, uint16_t *pHdlList)
{
appDiscCb_t *pAppDiscCb = &appDiscCb[connId - 1];
pAppDiscCb->hdlListLen = hdlListLen;
pAppDiscCb->pHdlList = pHdlList;
}
/*************************************************************************************************/
/*!
* \brief Service discovery or configuration procedure complete.
*
* \param connId Connection identifier.
* \param status Service or configuration status.
*
* \return None.
*/
/*************************************************************************************************/
void AppDiscComplete(dmConnId_t connId, uint8_t status)
{
appDiscCb_t *pAppDiscCb = &appDiscCb[connId - 1];
appDbHdl_t hdl;
/* set connection as idle */
DmConnSetIdle(connId, DM_IDLE_APP_DISC, DM_CONN_IDLE);
/* store status if not doing connection setup configuration */
if (!(status == APP_DISC_CFG_CMPL && pAppDiscCb->connCfgStatus == APP_DISC_CFG_CONN_START))
{
pAppDiscCb->cmplStatus = status;
}
/* initialize control block */
pAppDiscCb->inProgress = APP_DISC_IDLE;
if (pAppDiscCb->pDiscCb != NULL)
{
WsfBufFree(pAppDiscCb->pDiscCb);
pAppDiscCb->pDiscCb = NULL;
}
if ((hdl = AppDbGetHdl(connId)) != APP_DB_HDL_NONE)
{
/* Don't store configuration complete if not bonded - it must be re-done on reconnection. */
uint8_t discComplete = AppCheckBonded(connId) ? APP_DISC_CFG_CMPL : APP_DISC_CMPL;
/* store discovery status if not doing connection setup configuration */
if (!(status == APP_DISC_CFG_CMPL && pAppDiscCb->connCfgStatus == APP_DISC_CFG_CONN_START) && (status <= discComplete))
{
AppDbSetDiscStatus(hdl, status);
}
if (pAppDiscCb->pHdlList != NULL)
{
/* if discovery complete store handles */
if (status == APP_DISC_CMPL)
{
AppDbSetHdlList(hdl, pAppDiscCb->pHdlList);
}
}
}
/* set connection setup configuration status as complete if either discovery-initiated
* configuration is complete or connection setup configuration is complete
*/
if (status == APP_DISC_CFG_CMPL)
{
pAppDiscCb->connCfgStatus = APP_DISC_CFG_CMPL;
}
APP_TRACE_INFO2("AppDiscComplete connId:%d status:0x%02x", connId, status);
}
/*************************************************************************************************/
/*!
* \brief Perform service and characteristic discovery for a given service.
*
* \param connId Connection identifier.
* \param uuidLen Length of UUID (2 or 16).
* \param pUuid Pointer to UUID data.
* \param listLen Length of characteristic and handle lists.
* \param pCharList Characterisic list for discovery.
* \param pHdlList Characteristic handle list.
*
* \return None.
*/
/*************************************************************************************************/
void AppDiscFindService(dmConnId_t connId, uint8_t uuidLen, uint8_t *pUuid, uint8_t listLen,
attcDiscChar_t **pCharList, uint16_t *pHdlList)
{
appDiscCb_t *pAppDiscCb = &appDiscCb[connId - 1];
if (pAppDiscCb->pDiscCb == NULL)
{
pAppDiscCb->pDiscCb = WsfBufAlloc(sizeof(attcDiscCb_t));
}
if (pAppDiscCb->pDiscCb != NULL)
{
/* set connection as busy */
DmConnSetIdle(connId, DM_IDLE_APP_DISC, DM_CONN_BUSY);
pAppDiscCb->inProgress = APP_DISC_SVC_DISC_IN_PROGRESS;
pAppDiscCb->pDiscCb->pCharList = pCharList;
pAppDiscCb->pDiscCb->pHdlList = pHdlList;
pAppDiscCb->pDiscCb->charListLen = listLen;
AttcDiscService(connId, pAppDiscCb->pDiscCb, uuidLen, pUuid);
}
}
/*************************************************************************************************/
/*!
* \brief Configure characteristics for discovered services.
*
* \param connId Connection identifier.
* \param status APP_DISC_CFG_START or APP_DISC_CFG_CONN_START.
* \param cfgListLen Length of characteristic configuration list.
* \param pCfgList Characteristic configuration list.
* \param hdlListLen Length of characteristic handle list.
* \param pHdlList Characteristic handle list.
*
* \return None.
*/
/*************************************************************************************************/
void AppDiscConfigure(dmConnId_t connId, uint8_t status, uint8_t cfgListLen,
attcDiscCfg_t *pCfgList, uint8_t hdlListLen, uint16_t *pHdlList)
{
appDiscCb_t *pAppDiscCb = &appDiscCb[connId - 1];
uint8_t ret;
if (pAppDiscCb->pDiscCb == NULL)
{
pAppDiscCb->pDiscCb = WsfBufAlloc(sizeof(attcDiscCb_t));
}
if (pAppDiscCb->pDiscCb != NULL)
{
/* set connection as busy */
DmConnSetIdle(connId, DM_IDLE_APP_DISC, DM_CONN_BUSY);
pAppDiscCb->inProgress = APP_DISC_CFG_IN_PROGRESS;
if (status == APP_DISC_CFG_CONN_START)
{
pAppDiscCb->connCfgStatus = APP_DISC_CFG_CONN_START;
}
/* start configuration */
pAppDiscCb->pDiscCb->pCfgList = pCfgList;
pAppDiscCb->pDiscCb->cfgListLen = cfgListLen;
pAppDiscCb->pDiscCb->pHdlList = pHdlList;
pAppDiscCb->pDiscCb->charListLen = hdlListLen;
ret = AttcDiscConfigStart(connId, pAppDiscCb->pDiscCb);
/* nothing to configure; configuration complete */
if (ret == ATT_SUCCESS)
{
(*appDiscCback)(connId, APP_DISC_CFG_CMPL);
}
}
}
/*************************************************************************************************/
/*!
* \brief Perform the GATT service changed procedure. This function is called when an
* indication is received containing the GATT service changed characteristic. This
* function may initialize the discovery state and initiate service discovery
* and configuration.
*
* \param pMsg Pointer to ATT callback event message containing received indication.
*
* \return None.
*/
/*************************************************************************************************/
void AppDiscServiceChanged(attEvt_t *pMsg)
{
appDiscCb_t *pAppDiscCb = &appDiscCb[pMsg->hdr.param - 1];
uint16_t startHdl;
uint16_t endHdl;
uint8_t *p;
uint16_t *pHdl;
uint8_t i;
bool_t foundHdl;
/* verify characteristic length */
if (pMsg->valueLen != CH_SC_LEN)
{
return;
}
/* parse and verify handles */
p = pMsg->pValue;
BSTREAM_TO_UINT16(startHdl, p);
BSTREAM_TO_UINT16(endHdl, p);
if (startHdl == 0 || endHdl < startHdl)
{
return;
}
/* if we don't have any stored handles within service changed handle range, ignore */
foundHdl = FALSE;
if (pAppDiscCb->pHdlList != NULL)
{
pHdl = pAppDiscCb->pHdlList;
for (i = pAppDiscCb->hdlListLen; i > 0; i--, pHdl++)
{
if (*pHdl >= startHdl && *pHdl <= endHdl)
{
foundHdl = TRUE;
break;
}
}
}
if (foundHdl == FALSE)
{
return;
}
/* if discovery procedure already in progress */
if (pAppDiscCb->inProgress == APP_DISC_SVC_DISC_IN_PROGRESS)
{
/* ignore service changed */
return;
}
/* Prepare to restart service discovery*/
appDiscRestartDiscovery((dmConnId_t) pMsg->hdr.param);
}
/*************************************************************************************************/
/*!
* \brief Read peer's database hash
*
* \param dmConnId_t Connection ID.
*
* \return None.
*/
/*************************************************************************************************/
void AppDiscReadDatabaseHash(dmConnId_t connId)
{
appDiscCb_t *pAppDiscCb = &appDiscCb[connId - 1];
/* Security/bonding not used, rely on database hash for cached handles. */
pAppDiscCb->inProgress = APP_DISC_READ_DBH_IN_PROGRESS;
/* Read the database hash. */
AttcReadByTypeReq(connId, ATT_HANDLE_START, ATT_HANDLE_MAX, ATT_16_UUID_LEN,
(uint8_t *)attGattDbhChUuid, FALSE);
}
/*************************************************************************************************/
/*!
* \brief Get the handle range of the latest service discovery operation.
*
* May be called after receiving a \ref APP_DISC_CMPL event, but before calling AppDiscComplete().
*
* \param connId connection identifier.
* \param pStartHdl output parameter for start handle.
* \param pEndHdl output parameter for end handle.
*
* \return \ref TRUE if handles were set, \ref FALSE otherwise.
*/
/*************************************************************************************************/
bool_t AppDiscGetHandleRange(dmConnId_t connId, uint16_t *pStartHdl, uint16_t *pEndHdl)
{
appDiscCb_t *pAppDiscCb = &appDiscCb[connId - 1];
if (pAppDiscCb->pDiscCb != NULL)
{
*pStartHdl = pAppDiscCb->pDiscCb->svcStartHdl;
*pEndHdl = pAppDiscCb->pDiscCb->svcEndHdl;
return TRUE;
}
return FALSE;
}
@@ -0,0 +1,456 @@
/*************************************************************************************************/
/*!
* \file
*
* \brief Application framework main module.
*
* Copyright (c) 2011-2019 Arm Ltd.
*
* Copyright (c) 2019 Packetcraft, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/*************************************************************************************************/
#include <string.h>
#include "wsf_types.h"
#include "wsf_msg.h"
#include "sec_api.h"
#include "wsf_trace.h"
#include "wsf_timer.h"
#include "wsf_assert.h"
#include "util/bstream.h"
#include "dm_api.h"
#include "app_api.h"
#include "app_main.h"
#include "app_ui.h"
/**************************************************************************************************
Global Variables
**************************************************************************************************/
/*! Configuration pointer for advertising */
appAdvCfg_t *pAppAdvCfg;
/*! Configuration pointer for extended and periodic advertising */
appExtAdvCfg_t *pAppExtAdvCfg;
/*! Configuration pointer for slave */
appSlaveCfg_t *pAppSlaveCfg;
/*! Configuration pointer for master */
appMasterCfg_t *pAppMasterCfg;
/*! Configuration pointer for extended master */
appExtMasterCfg_t *pAppExtMasterCfg;
/*! Configuration pointer for security */
appSecCfg_t *pAppSecCfg;
/*! Configuration pointer for connection parameter update */
appUpdateCfg_t *pAppUpdateCfg;
/*! Configuration pointer for discovery */
appDiscCfg_t *pAppDiscCfg;
/*! Configuration pointer for application */
appCfg_t *pAppCfg;
/*! Connection control block array */
appConnCb_t appConnCb[DM_CONN_MAX];
/*! WSF handler ID */
wsfHandlerId_t appHandlerId;
/*! Main control block */
appCb_t appCb;
/*! Configuration structure for incoming request actions */
const appReqActCfg_t appReqActCfg =
{
APP_ACT_ACCEPT /*! Action for the remote connection parameter request */
};
/*! Configuration pointer for incoming request actions on master */
appReqActCfg_t *pAppMasterReqActCfg = (appReqActCfg_t *) &appReqActCfg;
/*! Configurable pointer for incoming request actions on slave */
appReqActCfg_t *pAppSlaveReqActCfg = (appReqActCfg_t *) &appReqActCfg;
/*************************************************************************************************/
/*!
* \brief Process messages from the event handler.
*
* \param pMsg Pointer to message.
*
* \return None.
*/
/*************************************************************************************************/
static void appProcMsg(wsfMsgHdr_t *pMsg)
{
switch(pMsg->event)
{
case APP_BTN_POLL_IND:
appUiBtnPoll();
break;
case APP_UI_TIMER_IND:
appUiTimerExpired(pMsg);
break;
default:
break;
}
}
/*************************************************************************************************/
/*!
* \brief Check the bond-by-LTK state of a connection.
*
* \param connId DM connection ID.
*
* \return Bond-by-LTK state.
*/
/*************************************************************************************************/
bool_t appCheckBondByLtk(dmConnId_t connId)
{
WSF_ASSERT((connId > 0) && (connId <= DM_CONN_MAX));
return appConnCb[connId - 1].bondByLtk;
}
/*************************************************************************************************/
/*!
* \brief Return the number of existing connections of the given role.
*
* \param role Connection role
*
* \return Number of connections.
*/
/*************************************************************************************************/
uint8_t appNumConns(uint8_t role)
{
appConnCb_t *pCcb = appConnCb;
uint8_t i, j;
for (i = DM_CONN_MAX, j = 0; i > 0; i--, pCcb++)
{
if ((pCcb->connId != DM_CONN_ID_NONE) && (DmConnRole(pCcb->connId) == role))
{
j++;
}
}
return j;
}
/*************************************************************************************************/
/*!
* \brief Check the bonded state of a connection.
*
* \param connId DM connection ID.
*
* \return Bonded state.
*/
/*************************************************************************************************/
bool_t AppCheckBonded(dmConnId_t connId)
{
WSF_ASSERT((connId > 0) && (connId <= DM_CONN_MAX));
return appConnCb[connId - 1].bonded;
}
/*************************************************************************************************/
/*!
* \brief App framework handler init function called during system initialization.
*
* \param handlerID WSF handler ID for App.
*
* \return None.
*/
/*************************************************************************************************/
void AppHandlerInit(wsfHandlerId_t handlerId)
{
appHandlerId = handlerId;
AppDbInit();
}
/*************************************************************************************************/
/*!
* \brief WSF event handler for app framework.
*
* \param event WSF event mask.
* \param pMsg WSF message.
*
* \return None.
*/
/*************************************************************************************************/
void AppHandler(wsfEventMask_t event, wsfMsgHdr_t *pMsg)
{
if (pMsg != NULL)
{
APP_TRACE_INFO1("App got evt %d", pMsg->event);
if (pMsg->event >= APP_MASTER_MSG_START)
{
/* pass event to master handler */
(*appCb.masterCback)(pMsg);
}
else if (pMsg->event >= APP_SLAVE_MSG_START)
{
/* pass event to slave handler */
(*appCb.slaveCback)(pMsg);
}
else
{
appProcMsg(pMsg);
}
}
else
{
if (event & APP_BTN_DOWN_EVT)
{
AppUiBtnPressed();
}
}
}
/*************************************************************************************************/
/*!
* \brief Handle a passkey request during pairing. If the passkey is to displayed, a
* random passkey is generated and displayed. If the passkey is to be entered
* the user is prompted to enter the passkey.
*
* \param pAuthReq DM authentication requested event structure.
*
* \return None.
*/
/*************************************************************************************************/
void AppHandlePasskey(dmSecAuthReqIndEvt_t *pAuthReq)
{
uint32_t passkey;
uint8_t buf[SMP_PIN_LEN];
if (pAuthReq->display)
{
/* generate random passkey, limit to 6 digit max */
SecRand((uint8_t *) &passkey, sizeof(uint32_t));
passkey %= 1000000;
/* convert to byte buffer */
buf[0] = UINT32_TO_BYTE0(passkey);
buf[1] = UINT32_TO_BYTE1(passkey);
buf[2] = UINT32_TO_BYTE2(passkey);
/* send authentication response to DM */
DmSecAuthRsp((dmConnId_t) pAuthReq->hdr.param, SMP_PIN_LEN, buf);
/* display passkey */
AppUiDisplayPasskey(passkey);
}
else
{
/* prompt user to enter passkey */
AppUiAction(APP_UI_PASSKEY_PROMPT);
}
}
/*************************************************************************************************/
/*!
* \brief Handle a numeric comparison indication during pairing. The confirmation value is
* displayed and the user is prompted to verify that the local and peer confirmation
* values match.
*
* \param pCnfInd DM confirmation indication event structure.
*
* \return None.
*/
/*************************************************************************************************/
void AppHandleNumericComparison(dmSecCnfIndEvt_t *pCnfInd)
{
uint32_t confirm = DmSecGetCompareValue(pCnfInd->confirm);
/* display confirmation value */
AppUiDisplayConfirmValue(confirm);
/* TODO: Verify that local and peer confirmation values match */
DmSecCompareRsp((dmConnId_t)pCnfInd->hdr.param, TRUE);
}
/*************************************************************************************************/
/*!
* \brief Close the connection with the give connection identifier.
*
* \param connId Connection identifier.
*
* \return None.
*/
/*************************************************************************************************/
void AppConnClose(dmConnId_t connId)
{
DmConnClose(DM_CLIENT_ID_APP, connId, HCI_ERR_REMOTE_TERMINATED);
}
/*************************************************************************************************/
/*!
* \brief Get a list of connection identifiers of open connections.
*
* \param pConnIdList Buffer to hold connection IDs (must be DM_CONN_MAX bytes).
*
* \return Number of open connections.
*
*/
/*************************************************************************************************/
uint8_t AppConnOpenList(dmConnId_t *pConnIdList)
{
appConnCb_t *pCcb = appConnCb;
uint8_t i;
uint8_t pos = 0;
memset(pConnIdList, DM_CONN_ID_NONE, DM_CONN_MAX);
for (i = DM_CONN_MAX; i > 0; i--, pCcb++)
{
if (pCcb->connId != DM_CONN_ID_NONE)
{
pConnIdList[pos++] = pCcb->connId;
}
}
return pos;
}
/*************************************************************************************************/
/*!
* \brief Check if a connection is open.
*
* \return Connection ID of open connection or DM_CONN_ID_NONE if no open connections.
*/
/*************************************************************************************************/
dmConnId_t AppConnIsOpen(void)
{
appConnCb_t *pCcb = appConnCb;
uint8_t i;
for (i = DM_CONN_MAX; i > 0; i--, pCcb++)
{
if (pCcb->connId != DM_CONN_ID_NONE)
{
return pCcb->connId;
}
}
return DM_CONN_ID_NONE;
}
/*************************************************************************************************/
/*!
* \brief Get the device database record handle associated with an open connection.
*
* \param connId Connection identifier.
*
* \return Database record handle or APP_DB_HDL_NONE.
*/
/*************************************************************************************************/
appDbHdl_t AppDbGetHdl(dmConnId_t connId)
{
return appConnCb[connId-1].dbHdl;
}
/*************************************************************************************************/
/*!
* \brief Add device to resolving list.
*
* \param pMsg Pointer to DM callback event message.
* \param connId Connection identifier.
*
* \return None.
*/
/*************************************************************************************************/
void AppAddDevToResList(dmEvt_t *pMsg, dmConnId_t connId)
{
dmSecKey_t *pPeerKey;
appDbHdl_t hdl = appConnCb[connId - 1].dbHdl;
/* if LL Privacy is supported and the peer device has distributed its IRK */
if (HciLlPrivacySupported() && ((pPeerKey = AppDbGetKey(hdl, DM_KEY_IRK, NULL))!= NULL))
{
/* add peer device to resolving list. If all-zero local or peer IRK is used then
LL will only use or accept local or peer identity address respectively. */
DmPrivAddDevToResList(pPeerKey->irk.addrType, pPeerKey->irk.bdAddr, pPeerKey->irk.key,
DmSecGetLocalIrk(), TRUE, pMsg->hdr.param);
}
}
/*************************************************************************************************/
/*!
* \brief Clear all bonding information.
*
* \return None.
*
* \Note This API should not be used when:
* - Advertising (other than periodic advertising) is enabled,
* - Scanning is enabled, or
* - (Extended) Create connection or Create Sync command is outstanding.
*
* Otherwise, clearing the resolving list in the Controller may fail.
*/
/*************************************************************************************************/
void AppClearAllBondingInfo(void)
{
APP_TRACE_INFO0("Clear bonding info");
/* clear bonded device info */
AppDbDeleteAllRecords();
/* if LL Privacy is supported */
if (HciLlPrivacySupported())
{
/* if LL Privacy has been enabled */
if (DmLlPrivEnabled())
{
/* make sure LL Privacy is disabled before clearing resolving list */
DmPrivSetAddrResEnable(FALSE);
}
/* clear resolving list */
DmPrivClearResList();
}
}
/*************************************************************************************************/
/*!
* \brief Update privacy mode for a given peer device.
*
* \param hdl Database record handle.
*
* \return None.
*/
/*************************************************************************************************/
void AppUpdatePrivacyMode(appDbHdl_t hdl)
{
/* if peer device's been added to resolving list but RPA Only attribute not found on peer device */
if ((hdl != APP_DB_HDL_NONE) && AppDbGetPeerAddedToRl(hdl) && !AppDbGetPeerRpao(hdl))
{
dmSecKey_t *pPeerKey = AppDbGetKey(hdl, DM_KEY_IRK, NULL);
if (pPeerKey != NULL)
{
/* set device privacy mode for this peer device */
DmPrivSetPrivacyMode(pPeerKey->irk.addrType, pPeerKey->irk.bdAddr, DM_PRIV_MODE_DEVICE);
/* make sure resolving list flag cleared */
AppDbSetPeerAddedToRl(hdl, FALSE);
}
}
}
@@ -0,0 +1,196 @@
/*************************************************************************************************/
/*!
* \file
*
* \brief Application framework main module.
*
* Copyright (c) 2011-2019 Arm Ltd.
*
* Copyright (c) 2019 Packetcraft, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/*************************************************************************************************/
#ifndef APP_MAIN_H
#define APP_MAIN_H
#include "wsf_os.h"
#include "wsf_timer.h"
#include "dm_api.h"
#include "att_api.h"
#include "app_api.h"
#include "app_db.h"
#include "app_cfg.h"
#ifdef __cplusplus
extern "C" {
#endif
/**************************************************************************************************
Macros
**************************************************************************************************/
/*! \brief No security record handle */
#define APP_DB_HDL_NONE NULL
/*! \brief Scanning mode types */
#define APP_SCAN_MODE_LEG 0 /*!< Legacy scanning mode */
#define APP_SCAN_MODE_EXT 1 /*!< Extended scanning mode */
#define APP_SCAN_MODE_NONE 255 /*!< Unknown scanning mode */
/*! \brief App WSF handler event bitmasks */
#define APP_BTN_DOWN_EVT 0x10 /*!< Button pressed down event */
/*! \brief App WSF message event starting values */
#define APP_MSG_START 0x00
#define APP_SLAVE_MSG_START 0x10
#define APP_MASTER_MSG_START 0x20
/*! \brief App WSF message event enumeration */
enum
{
APP_BTN_POLL_IND = APP_MSG_START, /*! Button poll timer expired */
APP_UI_TIMER_IND /*! UI timer expired */
};
/*! App slave WSF message event enumeration */
enum
{
APP_CONN_UPDATE_TIMEOUT_IND = APP_SLAVE_MSG_START /*! Connection parameter update timer expired */
};
/**************************************************************************************************
Data Types
**************************************************************************************************/
/*! \brief Message handling function type */
typedef void (*appMsgHandler_t)(wsfMsgHdr_t *pMsg);
/*! \brief Advertising callback function type */
typedef void(*appAdvCback_t)(dmEvt_t *pMsg);
/*! \brief Master control block */
typedef struct
{
appDevInfo_t scanResults[APP_SCAN_RESULT_MAX]; /*! Scan result storage */
uint8_t numScanResults; /*! Number of scan results */
uint8_t idx; /*! Index of address being resolved in scan result list */
appDbHdl_t dbHdl; /*! Database record handle for address being resolved */
bool_t inProgress; /*! TRUE if address resolution is in progress */
uint8_t scanMode; /*! Scan and connect mode in use */
} appMasterCb_t;
/*! Slave control block */
typedef struct
{
uint8_t *pAdvData[DM_NUM_ADV_SETS][APP_NUM_DATA_LOCATIONS]; /*! Advertising data pointers */
uint16_t advDataLen[DM_NUM_ADV_SETS][APP_NUM_DATA_LOCATIONS]; /*! Advertising data lengths */
uint16_t advDataBufLen[DM_NUM_ADV_SETS][APP_NUM_DATA_LOCATIONS]; /*! Length of advertising data buffer maintained by Application */
uint16_t advDataOffset[DM_NUM_ADV_SETS][APP_NUM_DATA_LOCATIONS]; /*! Advertising data offsets */
uint16_t maxAdvDataLen[DM_NUM_ADV_SETS]; /*! Maximum advertising data length supported by Controller */
bool_t bondable; /*! TRUE if in bondable mode */
bool_t advDataSynced[DM_NUM_ADV_SETS]; /*! TRUE if advertising/scan data is synced */
uint8_t advState[DM_NUM_ADV_SETS]; /*! Advertising state */
uint8_t advType[DM_NUM_ADV_SETS]; /*! Advertising type */
bool_t advTypeChanged[DM_NUM_ADV_SETS]; /*! TRUE if advertising type is changed */
uint8_t discMode; /*! Discoverable/connectable mode */
bdAddr_t peerAddr[DM_NUM_ADV_SETS]; /*! Peer address */
uint8_t peerAddrType[DM_NUM_ADV_SETS]; /*! Peer address type */
bool_t findLtk; /*! TRUE if LTK request received while resolving address */
appDbHdl_t dbHdl; /*! Database record handle for address being resolved */
bool_t inProgress; /*! TRUE if address resolution is in progress */
bool_t advDirected; /*! TRUE if legacy directed advertising is in progress */
appAdvCback_t advStopCback; /*! Advertising stopped callback */
appAdvCback_t advRestartCback; /*! Advertising restart callback */
} appSlaveCb_t;
/*! Connection control block */
typedef struct
{
appDbHdl_t dbHdl; /*! Device database handle */
dmConnId_t connId; /*! Connection ID */
bool_t bonded; /*! TRUE if bonded with peer device */
bool_t bondByLtk; /*! TRUE if bonded state being determined by LTK */
bool_t bondByPairing; /*! TRUE if bonded state being determined by pairing */
bool_t initiatingSec; /*! TRUE if initiating security */
bool_t setConnectable; /*! TRUE if switching to connectable mode */
bool_t connWasIdle; /*! TRUE if connection was idle at last check */
uint8_t rcvdKeys; /*! Bitmask of keys received during pairing */
uint8_t attempts; /*! Connection parameter update attempts */
uint8_t updateState; /*! Connection Update State */
wsfTimer_t updateTimer; /*! Connection parameter update timer */
} appConnCb_t;
/*! Main control block */
typedef struct
{
appMsgHandler_t slaveCback; /*! Slave message handler callback */
appMsgHandler_t masterCback; /*! Slave message handler callback */
} appCb_t;
/**************************************************************************************************
Global Variables
**************************************************************************************************/
/*! \brief Connection control block array */
extern appConnCb_t appConnCb[DM_CONN_MAX];
/*! \brief WSF handler ID */
extern wsfHandlerId_t appHandlerId;
/*! \brief Main control block */
extern appCb_t appCb;
/*! \brief Master control block */
extern appMasterCb_t appMasterCb;
/*! \brief Slave control block */
extern appSlaveCb_t appSlaveCb;
/**************************************************************************************************
Function Declarations
**************************************************************************************************/
bool_t AppCheckBonded(dmConnId_t connId);
bool_t appCheckBondByLtk(dmConnId_t connId);
uint8_t appNumConns(uint8_t role);
void appUiBtnPoll(void);
void appUiTimerExpired(wsfMsgHdr_t *pMsg);
/* slave utility functions */
extern void appSlaveResetAdvDataOffset(uint8_t advHandle);
extern void appAdvStart(uint8_t numSets, uint8_t *pAdvHandles, uint16_t *pInterval, uint16_t *pDuration,
uint8_t *pMaxEaEvents, bool_t cfgAdvParam);
extern void appAdvSetData(uint8_t advHandle, uint8_t location, uint16_t len, uint8_t *pData, uint16_t bufLen,
uint16_t maxLen);
extern void appSlaveAdvStart(uint8_t numSets, uint8_t *pAdvHandles, uint16_t *pInterval, uint16_t *pDuration,
uint8_t *pMaxEaEvents, bool_t cfgAdvParam, uint8_t mode);
extern void appAdvStop(uint8_t numSets, uint8_t *pAdvHandles);
extern bool_t appAdvSetAdValue(uint8_t advHandle, uint8_t location, uint8_t adType, uint8_t len,
uint8_t *pValue);
extern void appSetAdvType(uint8_t advHandle, uint8_t advType, uint16_t interval, uint16_t duration,
uint8_t maxEaEvents, bool_t cfgAdvParam);
extern dmConnId_t appConnAccept(uint8_t advHandle, uint8_t advType, uint16_t interval, uint16_t duration,
uint8_t maxEaEvents, uint8_t addrType, uint8_t *pAddr, appDbHdl_t dbHdl,
bool_t cfgAdvData);
/* master utility functions */
extern dmConnId_t appConnOpen(uint8_t initPhys, uint8_t addrType, uint8_t *pAddr, appDbHdl_t dbHdl);
#ifdef __cplusplus
};
#endif
#endif /* APP_MAIN_H */
@@ -0,0 +1,921 @@
/*************************************************************************************************/
/*!
* \file
*
* \brief Application framework module for master.
*
* Copyright (c) 2011-2019 Arm Ltd.
*
* Copyright (c) 2019 Packetcraft, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/*************************************************************************************************/
#include "wsf_types.h"
#include "wsf_msg.h"
#include "wsf_timer.h"
#include "wsf_trace.h"
#include "wsf_assert.h"
#include "dm_api.h"
#include "att_api.h"
#include "svc_core.h"
#include "app_api.h"
#include "app_main.h"
#include "app_cfg.h"
#include <stdbool.h>
#include "hci_drv_apollo.h"
#include "dm_api.h"
/**************************************************************************************************
Macros
**************************************************************************************************/
/* Constant used in the address type indicating value not present */
#define APP_ADDR_NONE 0xFF
/**************************************************************************************************
Global Variables
**************************************************************************************************/
/* Master control block */
appMasterCb_t appMasterCb;
/*************************************************************************************************/
/*!
* \brief Initiate security
*
* \param connId Connection ID.
* \param initiatePairing TRUE to initiate pairing.
* \param pCb Connection control block.
*
* \return None.
*/
/*************************************************************************************************/
static void appMasterInitiateSec(dmConnId_t connId, bool_t initiatePairing, appConnCb_t *pCb)
{
uint8_t rKeyDist;
uint8_t secLevel;
dmSecKey_t *pKey;
/* if we have an LTK for peer device */
if ((pCb->dbHdl != APP_DB_HDL_NONE) &&
((pKey = AppDbGetKey(pCb->dbHdl, DM_KEY_PEER_LTK, &secLevel)) != NULL))
{
pCb->bondByLtk = TRUE;
pCb->initiatingSec = TRUE;
/* encrypt with LTK */
DmSecEncryptReq(connId, secLevel, &pKey->ltk);
}
/* no key; initiate pairing only if requested */
else if (initiatePairing)
{
/* store bonding state */
pCb->bondByPairing = (pAppSecCfg->auth & DM_AUTH_BOND_FLAG) == DM_AUTH_BOND_FLAG;
/* if bonding and no device record */
if (pCb->bondByPairing && pCb->dbHdl == APP_DB_HDL_NONE)
{
/* create a device record if none exists */
pCb->dbHdl = AppDbNewRecord(DmConnPeerAddrType(connId), DmConnPeerAddr(connId), TRUE);
}
/* initialize stored keys */
pCb->rcvdKeys = 0;
/* if peer is using random address request IRK */
rKeyDist = pAppSecCfg->rKeyDist;
if (DmConnPeerAddrType(connId) == DM_ADDR_RANDOM)
{
rKeyDist |= DM_KEY_DIST_IRK;
}
pCb->initiatingSec = TRUE;
/* initiate pairing */
DmSecPairReq(connId, pAppSecCfg->oob, pAppSecCfg->auth, pAppSecCfg->iKeyDist, rKeyDist);
}
}
/*************************************************************************************************/
/*!
* \brief Clear all scan results.
*
* \return None.
*/
/*************************************************************************************************/
static void appScanResultsClear(void)
{
uint8_t i;
appDevInfo_t *pDev = appMasterCb.scanResults;
appMasterCb.numScanResults = 0;
for (i = APP_SCAN_RESULT_MAX; i > 0; i--, pDev++)
{
pDev->addrType = APP_ADDR_NONE;
}
/* end address resolution */
appMasterCb.inProgress = FALSE;
}
/*************************************************************************************************/
/*!
* \brief Add a scan report to the scan result list.
*
* \param pMsg Pointer to DM callback event message.
*
* \return None.
*/
/*************************************************************************************************/
static void appScanResultAdd(dmEvt_t *pMsg)
{
uint8_t i;
appDevInfo_t *pDev = appMasterCb.scanResults;
/* see if device is in list already */
for (i = 0; i < APP_SCAN_RESULT_MAX; i++, pDev++)
{
/* if address matches list entry */
if ((pDev->addrType == pMsg->scanReport.addrType) &&
BdaCmp(pDev->addr, pMsg->scanReport.addr))
{
/* device already exists in list; we are done */
break;
}
/* if entry is free end then of list has been reached */
else if (pDev->addrType == APP_ADDR_NONE)
{
/* add device to list */
pDev->addrType = pMsg->scanReport.addrType;
BdaCpy(pDev->addr, pMsg->scanReport.addr);
pDev->directAddrType = pMsg->scanReport.directAddrType;
BdaCpy(pDev->directAddr, pMsg->scanReport.directAddr);
appMasterCb.numScanResults++;
break;
}
}
}
/*************************************************************************************************/
/*!
* \brief Find a scan report in the scan result list.
*
* \param pMsg Pointer to DM callback event message.
*
* \return Index of result in scan result list. APP_SCAN_RESULT_MAX, otherwise.
*/
/*************************************************************************************************/
static uint8_t appScanResultFind(dmEvt_t *pMsg)
{
uint8_t i;
appDevInfo_t *pDev = appMasterCb.scanResults;
/* see if device is in list already */
for (i = 0; i < APP_SCAN_RESULT_MAX; i++, pDev++)
{
/* if address matches list entry */
if ((pDev->addrType == pMsg->scanReport.addrType) &&
BdaCmp(pDev->addr, pMsg->scanReport.addr))
{
/* device already exists in list; we are done */
break;
}
}
return i;
}
/*************************************************************************************************/
/*!
* \brief Handle a DM_SCAN_START_IND event.
*
* \param pMsg Pointer to DM callback event message.
*
* \return None.
*/
/*************************************************************************************************/
static void appMasterScanStart(dmEvt_t *pMsg)
{
if (pMsg->hdr.status == HCI_SUCCESS)
{
/* clear current scan results */
appScanResultsClear();
}
}
/*************************************************************************************************/
/*!
* \brief Handle a DM_SCAN_STOP_IND event.
*
* \param pMsg Pointer to DM callback event message.
*
* \return None.
*/
/*************************************************************************************************/
static void appMasterScanStop(dmEvt_t *pMsg)
{
if (pMsg->hdr.status == HCI_SUCCESS)
{
APP_TRACE_INFO1("Scan results: %d", AppScanGetNumResults());
}
}
/*************************************************************************************************/
/*!
* \brief Handle a DM_SCAN_REPORT_IND event.
*
* \param pMsg Pointer to DM callback event message.
*
* \return None.
*/
/*************************************************************************************************/
static void appMasterScanReport(dmEvt_t *pMsg)
{
/* add to scan result list */
appScanResultAdd(pMsg);
}
/*************************************************************************************************/
/*!
* \brief Handle a DM_CONN_OPEN_IND event.
*
* \param pMsg Pointer to DM callback event message.
* \param pCb Connection control block.
*
* \return None.
*/
/*************************************************************************************************/
static void appMasterConnOpen(dmEvt_t *pMsg, appConnCb_t *pCb)
{
DmReadRemoteFeatures((dmConnId_t) pMsg->hdr.param);
}
/*************************************************************************************************/
/*!
* \brief Handle a DM_CONN_CLOSE_IND event.
*
* \param pMsg Pointer to DM callback event message.
* \param pCb Connection control block.
*
* \return None.
*/
/*************************************************************************************************/
static void appMasterConnClose(dmEvt_t *pMsg, appConnCb_t *pCb)
{
/* update privacy mode for peer device */
AppUpdatePrivacyMode(pCb->dbHdl);
/* clear connection ID */
pCb->connId = DM_CONN_ID_NONE;
/* cancel any address resolution in progress */
appMasterCb.inProgress = FALSE;
}
/*************************************************************************************************/
/*!
* \brief Perform master security procedures on connection open.
*
* \param pMsg Pointer to DM callback event message.
* \param pCb Connection control block.
*
* \return None.
*/
/*************************************************************************************************/
static void appMasterSecConnOpen(dmEvt_t *pMsg, appConnCb_t *pCb)
{
/* initialize state variables */
pCb->bonded = FALSE;
pCb->bondByLtk = FALSE;
pCb->bondByPairing = FALSE;
pCb->initiatingSec = FALSE;
/* if master initiates security on connection open */
appMasterInitiateSec((dmConnId_t) pMsg->hdr.param, pAppSecCfg->initiateSec, pCb);
}
/*************************************************************************************************/
/*!
* \brief Perform security procedures on connection close.
*
* \param pMsg Pointer to DM callback event message.
* \param pCb Connection control block.
*
* \return None.
*/
/*************************************************************************************************/
static void appMasterSecConnClose(dmEvt_t *pMsg, appConnCb_t *pCb)
{
/* if a device record was created check if it is valid */
if (pCb->dbHdl != APP_DB_HDL_NONE)
{
AppDbCheckValidRecord(pCb->dbHdl);
}
}
/*************************************************************************************************/
/*!
* \brief Handle a slave security request.
*
* \param pMsg Pointer to DM callback event message.
* \param pCb Connection control block.
*
* \return None.
*/
/*************************************************************************************************/
static void appMasterSecSlaveReq(dmEvt_t *pMsg, appConnCb_t *pCb)
{
/* if master is not initiating security and not already secure */
if (!pAppSecCfg->initiateSec && !pCb->initiatingSec &&
(DmConnSecLevel((dmConnId_t) pMsg->hdr.param) == DM_SEC_LEVEL_NONE))
{
appMasterInitiateSec((dmConnId_t) pMsg->hdr.param, TRUE, pCb);
}
}
/*************************************************************************************************/
/*!
* \brief Handle set address resolution enable indication.
*
* \param pMsg Pointer to DM callback event message.
*
* \return None.
*/
/*************************************************************************************************/
static void appPrivSetAddrResEnableInd(dmEvt_t *pMsg)
{
if (pMsg->hdr.status == HCI_SUCCESS)
{
SvcCoreGapCentAddrResUpdate(DmLlPrivEnabled());
}
}
/*************************************************************************************************/
/*!
* \brief Handle add device to resolving list indication.
*
* \param pMsg Pointer to DM callback event message.
* \param pCb Connection control block.
*
* \return None.
*/
/*************************************************************************************************/
static void appPrivAddDevToResListInd(dmEvt_t *pMsg, appConnCb_t *pCb)
{
if ((pMsg->hdr.status == HCI_SUCCESS) && (pCb->dbHdl != APP_DB_HDL_NONE))
{
/* peer device's been added to resolving list */
AppDbSetPeerAddedToRl(pCb->dbHdl, TRUE);
}
}
/*************************************************************************************************/
/*!
* \brief Handle remove device from resolving list indication.
*
* \param pMsg Pointer to DM callback event message.
* \param pCb Connection control block.
*
* \return None.
*/
/*************************************************************************************************/
static void appPrivRemDevFromResListInd(dmEvt_t *pMsg, appConnCb_t *pCb)
{
if ((pMsg->hdr.status == HCI_SUCCESS) && (pCb->dbHdl != APP_DB_HDL_NONE))
{
/* peer device's been removed from resolving list */
AppDbSetPeerAddedToRl(pCb->dbHdl, FALSE);
}
}
/*************************************************************************************************/
/*!
* \brief Store security key.
*
* \param pMsg Pointer to DM callback event message.
* \param pCb Connection control block.
*
* \return None.
*/
/*************************************************************************************************/
static void appMasterSecStoreKey(dmEvt_t *pMsg, appConnCb_t *pCb)
{
if (pCb->bondByPairing && pCb->dbHdl != APP_DB_HDL_NONE)
{
/* key was received */
pCb->rcvdKeys |= pMsg->keyInd.type;
/* store key in record */
AppDbSetKey(pCb->dbHdl, &pMsg->keyInd);
}
}
/*************************************************************************************************/
/*!
* \brief Handle pairing complete.
*
* \param pMsg Pointer to DM callback event message.
* \param pCb Connection control block.
*
* \return None.
*/
/*************************************************************************************************/
static void appMasterSecPairCmpl(dmEvt_t *pMsg, appConnCb_t *pCb)
{
/* if bonding */
if (pMsg->pairCmpl.auth & DM_AUTH_BOND_FLAG)
{
/* set bonded state */
pCb->bonded = TRUE;
/* validate record and received keys */
if (pCb->dbHdl != APP_DB_HDL_NONE)
{
AppDbValidateRecord(pCb->dbHdl, pCb->rcvdKeys);
}
/* if bonded, add device to resolving list */
if (pCb->dbHdl != APP_DB_HDL_NONE)
{
AppAddDevToResList(pMsg, pCb->connId);
}
}
pCb->initiatingSec = FALSE;
}
/*************************************************************************************************/
/*!
* \brief Handle pairing failed
*
* \param pMsg Pointer to DM callback event message.
* \param pCb Connection control block.
*
* \return None.
*/
/*************************************************************************************************/
static void appMasterSecPairFailed(dmEvt_t *pMsg, appConnCb_t *pCb)
{
pCb->initiatingSec = FALSE;
return;
}
/*************************************************************************************************/
/*!
* \brief Handle encryption indication
*
* \param pMsg Pointer to DM callback event message.
* \param pCb Connection control block.
*
* \return None.
*/
/*************************************************************************************************/
static void appMasterSecEncryptInd(dmEvt_t *pMsg, appConnCb_t *pCb)
{
/* check if bonding state should be set */
if (pCb->bondByLtk && pMsg->encryptInd.usingLtk)
{
pCb->bonded = TRUE;
pCb->bondByLtk = FALSE;
pCb->initiatingSec = FALSE;
}
}
/*************************************************************************************************/
/*!
* \brief Process app framework messages for a master.
*
* \param pMsg Pointer to message.
*
* \return None.
*/
/*************************************************************************************************/
void appMasterProcMsg(wsfMsgHdr_t *pMsg)
{
switch(pMsg->event)
{
case APP_CONN_UPDATE_TIMEOUT_IND:
break;
default:
break;
}
}
/*************************************************************************************************/
/*!
* \brief Process a received privacy resolved address indication.
*
* \param pMsg Pointer to DM message.
*
* \return None.
*/
/*************************************************************************************************/
static void appMasterResolvedAddrInd(dmEvt_t *pMsg)
{
appDevInfo_t *pDev;
dmSecKey_t *pPeerKey;
/* if address resolution is not in progress */
if (!appMasterCb.inProgress)
{
return;
}
/* get device record */
pDev = &appMasterCb.scanResults[appMasterCb.idx];
/* if RPA resolved */
if (pMsg->hdr.status == HCI_SUCCESS)
{
/* if resolved advertising was directed with an RPA initiator address */
if ((pMsg->hdr.param == APP_RESOLVE_ADV_RPA) && DM_RAND_ADDR_RPA(pDev->directAddr, pDev->directAddrType))
{
/* resolve initiator's RPA to see if directed advertisement was addressed to us */
DmPrivResolveAddr(pDev->directAddr, DmSecGetLocalIrk(), APP_RESOLVE_DIRECT_RPA);
/* not done yet */
return;
}
/* stop scanning */
AppScanStop();
/* connect to peer device using its advertising address */
AppConnOpen(pDev->addrType, pDev->addr, appMasterCb.dbHdl);
}
/* if RPA did not resolve and there're more bonded records to go through */
else if ((pMsg->hdr.status == HCI_ERR_AUTH_FAILURE) && (appMasterCb.dbHdl != APP_DB_HDL_NONE))
{
/* get the next database record */
appMasterCb.dbHdl = AppDbGetNextRecord(appMasterCb.dbHdl);
/* if there's another bond record */
if ((appMasterCb.dbHdl != APP_DB_HDL_NONE) &&
((pPeerKey = AppDbGetKey(appMasterCb.dbHdl, DM_KEY_IRK, NULL)) != NULL))
{
/* resolve RPA using the next stored IRK */
DmPrivResolveAddr(pDev->addr, pPeerKey->irk.key, APP_RESOLVE_ADV_RPA);
/* not done yet */
return;
}
}
/* done with this address resolution */
appMasterCb.inProgress = FALSE;
}
/*************************************************************************************************/
/*!
* \brief Handle a DM_REM_CONN_PARAM_REQ_IND event.
*
* \param pMsg Pointer to DM callback event message.
* \param pCb Connection control block.
*
* \return None.
*/
/*************************************************************************************************/
static void appMasterRemoteConnParamReq(dmEvt_t *pMsg, appConnCb_t *pCb)
{
/* if configured to accept the remote connection parameter request */
if (pAppMasterReqActCfg->remConnParamReqAct == APP_ACT_ACCEPT)
{
hciConnSpec_t connSpec;
connSpec.connIntervalMin = pMsg->remConnParamReq.intervalMin;
connSpec.connIntervalMax = pMsg->remConnParamReq.intervalMax;
connSpec.connLatency = pMsg->remConnParamReq.latency;
connSpec.supTimeout = pMsg->remConnParamReq.timeout;
connSpec.minCeLen = connSpec.maxCeLen = 0;
/* accept the remote device's request to change connection parameters */
DmRemoteConnParamReqReply(pCb->connId, &connSpec);
}
/* if configured to reject the remote connection parameter request */
else if (pAppMasterReqActCfg->remConnParamReqAct == APP_ACT_REJECT)
{
/* reject the remote device's request to change connection parameters */
DmRemoteConnParamReqNegReply(pCb->connId, HCI_ERR_UNSUP_FEAT);
}
/* else - app will handle the remote connection parameter request */
}
/*************************************************************************************************/
/*!
* \brief Initialize app framework master.
*
* \return None.
*/
/*************************************************************************************************/
void AppMasterInit(void)
{
appMasterCb.inProgress = FALSE;
/* initialize scan mode */
appMasterCb.scanMode = APP_SCAN_MODE_NONE;
/* set up callback from main */
appCb.masterCback = appMasterProcMsg;
}
/*************************************************************************************************/
/*!
* \brief Process connection-related DM messages for a master. This function should be called
* from the application's event handler.
*
* \param pMsg Pointer to DM callback event message.
*
* \return None.
*/
/*************************************************************************************************/
void AppMasterProcDmMsg(dmEvt_t *pMsg)
{
appConnCb_t *pCb = NULL;
/* look up app connection control block from DM connection ID */
if (pMsg->hdr.event == DM_CONN_OPEN_IND ||
pMsg->hdr.event == DM_CONN_CLOSE_IND ||
pMsg->hdr.event == DM_REM_CONN_PARAM_REQ_IND)
{
pCb = &appConnCb[pMsg->hdr.param - 1];
}
switch(pMsg->hdr.event)
{
case DM_RESET_CMPL_IND:
/* reset scan mode */
appMasterCb.scanMode = APP_SCAN_MODE_NONE;
break;
case DM_SCAN_START_IND:
appMasterScanStart(pMsg);
break;
case DM_SCAN_STOP_IND:
appMasterScanStop(pMsg);
break;
case DM_SCAN_REPORT_IND:
appMasterScanReport(pMsg);
break;
case DM_CONN_OPEN_IND:
appMasterConnOpen(pMsg, pCb);
break;
case DM_CONN_CLOSE_IND:
appMasterConnClose(pMsg, pCb);
break;
case DM_PRIV_RESOLVED_ADDR_IND:
appMasterResolvedAddrInd(pMsg);
break;
case DM_REM_CONN_PARAM_REQ_IND:
appMasterRemoteConnParamReq(pMsg, pCb);
break;
default:
break;
}
}
/*************************************************************************************************/
/*!
* \brief Process security-related DM messages for a master. This function should be called
* from the application's event handler.
*
* \param pMsg Pointer to DM callback event message.
*
* \return None.
*/
/*************************************************************************************************/
void AppMasterSecProcDmMsg(dmEvt_t *pMsg)
{
appConnCb_t *pCb;
/* look up app connection control block from DM connection ID */
pCb = &appConnCb[pMsg->hdr.param - 1];
switch(pMsg->hdr.event)
{
case DM_CONN_OPEN_IND:
appMasterSecConnOpen(pMsg, pCb);
break;
case DM_CONN_CLOSE_IND:
appMasterSecConnClose(pMsg, pCb);
break;
case DM_SEC_PAIR_CMPL_IND:
appMasterSecPairCmpl(pMsg, pCb);
break;
case DM_SEC_PAIR_FAIL_IND:
appMasterSecPairFailed(pMsg, pCb);
break;
case DM_SEC_ENCRYPT_IND:
appMasterSecEncryptInd(pMsg, pCb);
break;
case DM_SEC_ENCRYPT_FAIL_IND:
break;
case DM_SEC_KEY_IND:
appMasterSecStoreKey(pMsg, pCb);
break;
case DM_SEC_SLAVE_REQ_IND:
appMasterSecSlaveReq(pMsg, pCb);
break;
case DM_PRIV_SET_ADDR_RES_ENABLE_IND:
appPrivSetAddrResEnableInd(pMsg);
break;
case DM_PRIV_ADD_DEV_TO_RES_LIST_IND:
appPrivAddDevToResListInd(pMsg, pCb);
break;
case DM_PRIV_REM_DEV_FROM_RES_LIST_IND:
appPrivRemDevFromResListInd(pMsg, pCb);
break;
case DM_HW_ERROR_IND:
HciDrvRadioBoot(0);
DmDevReset();
break;
default:
break;
}
}
/*************************************************************************************************/
/*!
* \brief Get a stored scan result from the scan result list.
*
* \param idx Index of result in scan result list.
*
* \return Pointer to scan result device info or NULL if index contains no result.
*/
/*************************************************************************************************/
appDevInfo_t *AppScanGetResult(uint8_t idx)
{
if (idx < APP_SCAN_RESULT_MAX && appMasterCb.scanResults[idx].addrType != APP_ADDR_NONE)
{
return &appMasterCb.scanResults[idx];
}
else
{
return NULL;
}
}
/*************************************************************************************************/
/*!
* \brief Get the number of stored scan results.
*
* \return Number of stored scan results.
*/
/*************************************************************************************************/
uint8_t AppScanGetNumResults(void)
{
return appMasterCb.numScanResults;
}
/*************************************************************************************************/
/*!
* \brief Open a connection to a peer device with the given initiator PHYs, and address.
*
* \param initPhys Initiator PHYs.
* \param addrType Address type.
* \param pAddr Peer device address.
* \param dbHdl Device database handle.
*
* \return Connection identifier.
*/
/*************************************************************************************************/
dmConnId_t appConnOpen(uint8_t initPhys, uint8_t addrType, uint8_t *pAddr, appDbHdl_t dbHdl)
{
dmConnId_t connId;
appConnCb_t *pCb;
/* open connection */
connId = DmConnOpen(DM_CLIENT_ID_APP, initPhys, addrType, pAddr);
if (connId != DM_CONN_ID_NONE)
{
/* set up conn. control block */
pCb = &appConnCb[connId - 1];
pCb->connId = connId;
/* if database record handle is in use */
if ((dbHdl != APP_DB_HDL_NONE) && AppDbRecordInUse(dbHdl))
{
pCb->dbHdl = dbHdl;
}
else
{
pCb->dbHdl = AppDbFindByAddr(addrType, pAddr);
}
}
return connId;
}
/*************************************************************************************************/
/*!
* \brief Initiate security as a master device. If there is a stored encryption key
* for the peer device this function will initiate encryption, otherwise it will
* initiate pairing.
*
* \param connId Connection identifier.
*
* \return None.
*/
/*************************************************************************************************/
void AppMasterSecurityReq(dmConnId_t connId)
{
appConnCb_t *pCb;
WSF_ASSERT((connId > 0) && (connId <= DM_CONN_MAX));
/* look up app connection control block from DM connection ID */
pCb = &appConnCb[connId - 1];
/* if master is not initiating security and not already secure */
if (!pAppSecCfg->initiateSec && !pCb->initiatingSec &&
(DmConnSecLevel(connId) == DM_SEC_LEVEL_NONE))
{
appMasterInitiateSec(connId, TRUE, pCb);
}
}
/*************************************************************************************************/
/*!
* \brief Resolve the advertiser's RPA (AdvA) or the initiator's RPA (InitA) of a directed
* advertisement report. If the addresses resolved then a connection will be initiated.
*
* \param pMsg Pointer to DM callback event message.
* \param dbHdl Database record handle.
* \param resolveType Which address in advertising report to be resolved.
*
* \return None.
*/
/*************************************************************************************************/
void AppMasterResolveAddr(dmEvt_t *pMsg, appDbHdl_t dbHdl, uint8_t resolveType)
{
uint8_t idx;
/* if address resolution's in progress or scan record is not found */
if ((appMasterCb.inProgress) || ((idx = appScanResultFind(pMsg)) >= APP_SCAN_RESULT_MAX))
{
return;
}
/* if asked to resolve direct address */
if (resolveType == APP_RESOLVE_DIRECT_RPA)
{
/* resolve initiator's RPA to see if the directed advertisement is addressed to us */
DmPrivResolveAddr(pMsg->scanReport.directAddr, DmSecGetLocalIrk(), APP_RESOLVE_DIRECT_RPA);
/* store scan record index and database record handle for later */
appMasterCb.idx = idx;
appMasterCb.dbHdl = dbHdl;
appMasterCb.inProgress = TRUE;
}
/* if asked to resolve advertiser's address */
else if (resolveType == APP_RESOLVE_ADV_RPA)
{
dmSecKey_t *pPeerKey;
appDbHdl_t hdl = AppDbGetNextRecord(APP_DB_HDL_NONE);
/* if we have any bond records */
if ((hdl != APP_DB_HDL_NONE) && ((pPeerKey = AppDbGetKey(hdl, DM_KEY_IRK, NULL)) != NULL))
{
/* resolve advertiser's RPA to see if we already have a bond with this device */
DmPrivResolveAddr(pMsg->scanReport.addr, pPeerKey->irk.key, APP_RESOLVE_ADV_RPA);
/* store scan record index and database record handle for later */
appMasterCb.idx = idx;
appMasterCb.dbHdl = hdl;
appMasterCb.inProgress = TRUE;
}
}
}
@@ -0,0 +1,190 @@
/*************************************************************************************************/
/*!
* \file
*
* \brief Application framework module for extended master.
*
* Copyright (c) 2016-2018 Arm Ltd.
*
* Copyright (c) 2019 Packetcraft, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/*************************************************************************************************/
#include "wsf_types.h"
#include "wsf_trace.h"
#include "dm_api.h"
#include "app_api.h"
#include "app_main.h"
/*************************************************************************************************/
/*!
* \brief Check if current scanning mode is extended scanning.
*
* \return TRUE if extended scanning mode. FALSE, otherwise.
*/
/*************************************************************************************************/
static bool_t appMasterExtScanMode(void)
{
/* if DM extended scanning */
if (DmScanModeExt())
{
/* if first time since last power-on or reset */
if (appMasterCb.scanMode == APP_SCAN_MODE_NONE)
{
/* set scanning mode to extended */
appMasterCb.scanMode = APP_SCAN_MODE_EXT;
return TRUE;
}
}
if (appMasterCb.scanMode == APP_SCAN_MODE_EXT)
{
return TRUE;
}
APP_TRACE_WARN0("Invalid DM scanning mode; mode configured as legacy");
return FALSE;
}
/*************************************************************************************************/
/*!
* \brief Start scanning. A scan is performed using the given scanner PHYs, discoverability
* mode, scan type, duration, and period.
*
* \param scanPhys Scanner PHYs.
* \param mode Discoverability mode.
* \param pScanType Scan type array.
* \param duration The scan duration, in milliseconds. If set to zero or both duration and
* period set to non-zero, scanning will continue until DmExtScanStop() is
* called.
* \param period The scan period, in 1.28 sec units. Set to zero to disable periodic scanning.
*
* \return None.
*/
/*************************************************************************************************/
void AppExtScanStart(uint8_t scanPhys, uint8_t mode, const uint8_t *pScanType, uint16_t duration,
uint16_t period)
{
uint8_t i; /* scanPhy bit position */
uint8_t idx; /* array index */
uint16_t scanInterval[DM_NUM_PHYS];
uint16_t scanWindow[DM_NUM_PHYS];
uint8_t scanType[DM_NUM_PHYS];
if (appMasterExtScanMode())
{
for (i = 0, idx = 0; (i < 8) && (idx < DM_NUM_PHYS); i++)
{
if (scanPhys & (1 << i))
{
scanInterval[idx] = pAppExtMasterCfg->scanInterval[i];
scanWindow[idx] = pAppExtMasterCfg->scanWindow[i];
scanType[idx] = pScanType[i];
idx++;
}
}
DmScanSetInterval(scanPhys, scanInterval, scanWindow);
DmScanStart(scanPhys, mode, scanType, TRUE, duration, period);
}
}
/*************************************************************************************************/
/*!
* \brief Stop scanning.
*
* \return None.
*/
/*************************************************************************************************/
void AppExtScanStop(void)
{
if (appMasterExtScanMode())
{
/* stop address resolution */
appMasterCb.inProgress = FALSE;
DmScanStop();
}
}
/*************************************************************************************************/
/*!
* \brief Synchronize with periodic advertising from the given advertiser, and start receiving
* periodic advertising packets.
*
* \param advSid Advertising SID.
* \param advAddrType Advertiser address type.
* \param pAdvAddr Advertiser address.
* \param skip Number of periodic advertising packets that can be skipped after
* successful receive.
* \param syncTimeout Synchronization timeout.
*
* \return Sync identifier.
*/
/*************************************************************************************************/
dmSyncId_t AppSyncStart(uint8_t advSid, uint8_t advAddrType, const uint8_t *pAdvAddr, uint16_t skip,
uint16_t syncTimeout)
{
if (appMasterExtScanMode())
{
return DmSyncStart(advSid, advAddrType, pAdvAddr, skip, syncTimeout);
}
/* wrong scan mode */
return DM_SYNC_ID_NONE;
}
/*************************************************************************************************/
/*!
* \brief Stop reception of the periodic advertising identified by the given sync identifier.
*
* \param syncId Sync identifier.
*
* \return None.
*/
/*************************************************************************************************/
void AppSyncStop(dmSyncId_t syncId)
{
if (appMasterExtScanMode())
{
DmSyncStop(syncId);
}
}
/*************************************************************************************************/
/*!
* \brief Open a connection to a peer device with the given address.
*
* \param initPhys Initiator PHYs.
* \param addrType Address type.
* \param pAddr Peer device address.
* \param dbHdl Device database handle.
*
* \return Connection identifier.
*/
/*************************************************************************************************/
dmConnId_t AppExtConnOpen(uint8_t initPhys, uint8_t addrType, uint8_t *pAddr, appDbHdl_t dbHdl)
{
if (appMasterExtScanMode())
{
return appConnOpen(initPhys, addrType, pAddr, dbHdl);
}
/* wrong connect mode */
return DM_CONN_ID_NONE;
}
@@ -0,0 +1,123 @@
/*************************************************************************************************/
/*!
* \file
*
* \brief Application framework module for legacy master. This module can be used with both
* DM legacy and extended scanning and connect.
*
* Copyright (c) 2016-2018 Arm Ltd.
*
* Copyright (c) 2019 Packetcraft, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/*************************************************************************************************/
#include "wsf_types.h"
#include "wsf_trace.h"
#include "dm_api.h"
#include "app_api.h"
#include "app_main.h"
/*************************************************************************************************/
/*!
* \brief Check if current scanning mode is legacy scanning.
*
* \return TRUE if legacy scanning mode. FALSE, otherwise.
*/
/*************************************************************************************************/
static bool_t appMasterScanMode(void)
{
/* legacy master app works with both DM legacy and extended scanning */
/* if first time since last power-on or reset */
if (appMasterCb.scanMode == APP_SCAN_MODE_NONE)
{
/* set scanning mode to legacy */
appMasterCb.scanMode = APP_SCAN_MODE_LEG;
return TRUE;
}
if (appMasterCb.scanMode == APP_SCAN_MODE_LEG)
{
return TRUE;
}
APP_TRACE_WARN0("Invalid DM scanning mode; mode configured as extended");
return FALSE;
}
/*************************************************************************************************/
/*!
* \brief Start scanning. A scan is performed using the given discoverability mode,
* scan type, and duration.
*
* \param mode Discoverability mode.
* \param scanType Scan type.
* \param duration The scan duration, in milliseconds. If set to zero, scanning will
* continue until AppScanStop() is called.
*
* \return None.
*/
/*************************************************************************************************/
void AppScanStart(uint8_t mode, uint8_t scanType, uint16_t duration)
{
if (appMasterScanMode())
{
DmScanSetInterval(HCI_SCAN_PHY_LE_1M_BIT, &pAppMasterCfg->scanInterval, &pAppMasterCfg->scanWindow);
DmScanStart(HCI_SCAN_PHY_LE_1M_BIT, mode, &scanType, TRUE, duration, 0);
}
}
/*************************************************************************************************/
/*!
* \brief Stop scanning.
*
* \return None.
*/
/*************************************************************************************************/
void AppScanStop(void)
{
if (appMasterScanMode())
{
/* stop address resolution */
appMasterCb.inProgress = FALSE;
DmScanStop();
}
}
/*************************************************************************************************/
/*!
* \brief Open a connection to a peer device with the given address.
*
* \param addrType Address type.
* \param pAddr Peer device address.
* \param dbHdl Device database handle.
*
* \return Connection identifier.
*/
/*************************************************************************************************/
dmConnId_t AppConnOpen(uint8_t addrType, uint8_t *pAddr, appDbHdl_t dbHdl)
{
if (appMasterScanMode())
{
return appConnOpen(HCI_INIT_PHY_LE_1M_BIT, addrType, pAddr, dbHdl);
}
/* wrong connect mode */
return DM_CONN_ID_NONE;
}
@@ -0,0 +1,283 @@
/*************************************************************************************************/
/*!
* \file
*
* \brief Application framework module for attribute server.
*
* Copyright (c) 2011-2019 Arm Ltd.
*
* Copyright (c) 2019 Packetcraft, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/*************************************************************************************************/
#include <string.h>
#include "wsf_types.h"
#include "wsf_buf.h"
#include "wsf_msg.h"
#include "wsf_trace.h"
#include "util/wstr.h"
#include "dm_api.h"
#include "att_api.h"
#include "app_api.h"
#include "app_main.h"
#include "svc_core.h"
#include "gatt/gatt_api.h"
/*************************************************************************************************/
/*!
* \brief Set the peer's CSRK and sign counter on this connection.
*
* \param connId DM connection ID.
*
* \return None.
*/
/*************************************************************************************************/
static void appServerSetSigningInfo(dmConnId_t connId)
{
appDbHdl_t dbHdl;
dmSecKey_t *pPeerKey;
/* if peer's CSRK is available */
if (((dbHdl = AppDbGetHdl(connId)) != APP_DB_HDL_NONE) &&
((pPeerKey = AppDbGetKey(dbHdl, DM_KEY_CSRK, NULL)) != NULL))
{
/* set peer's CSRK and sign counter on this connection */
AttsSetCsrk(connId, pPeerKey->csrk.key, FALSE);
AttsSetSignCounter(connId, AppDbGetPeerSignCounter(dbHdl));
}
}
/*************************************************************************************************/
/*!
* \brief ATT connection callback for app framework.
*
* \param pDmEvt DM callback event.
*
* \return None.
*/
/*************************************************************************************************/
void AppServerConnCback(dmEvt_t *pDmEvt)
{
appDbHdl_t dbHdl;
dmConnId_t connId = (dmConnId_t) pDmEvt->hdr.param;
if (pDmEvt->hdr.event == DM_CONN_OPEN_IND)
{
/* apply the peer's CCC table - values are persistant across connection when bonded */
if ((dbHdl = AppDbGetHdl(connId)) != APP_DB_HDL_NONE)
{
uint8_t changeAwareState;
uint8_t *pCsf;
AppDbGetCsfRecord(dbHdl, &changeAwareState, &pCsf);
/* Apply peer's client supported features. */
AttsCsfConnOpen(connId, changeAwareState, pCsf);
AttsCccInitTable(connId, AppDbGetCccTbl(dbHdl));
/* If database has changed and peer configured service indications, send one now. */
if (changeAwareState == ATTS_CLIENT_CHANGE_UNAWARE)
{
GattSendServiceChangedInd(connId, ATT_HANDLE_START, ATT_HANDLE_MAX);
}
}
else
{
/* set up CCC table with uninitialized (all zero) values. */
AttsCccInitTable(connId, NULL);
/* set CSF values to default */
AttsCsfConnOpen(connId, TRUE, NULL);
}
/* set peer's data signing info */
appServerSetSigningInfo(connId);
}
else if (pDmEvt->hdr.event == DM_SEC_PAIR_CMPL_IND)
{
bool_t bonded;
bonded = ((pDmEvt->pairCmpl.auth & DM_AUTH_BOND_FLAG) == DM_AUTH_BOND_FLAG);
/* if a record exists but it is a new record, synchronize ATT CCC info into record. */
if (bonded && (AppCheckBonded(connId) == FALSE) &&
((dbHdl = AppDbGetHdl(connId)) != APP_DB_HDL_NONE))
{
uint8_t tableLen, idx;
uint8_t csf[ATT_CSF_LEN];
tableLen = AttsGetCccTableLen();
for (idx = 0; idx < tableLen; idx++)
{
uint16_t cccValue;
if ((cccValue = AttsCccGet(connId, idx)) != 0)
{
AppDbSetCccTblValue(dbHdl, idx, cccValue);
}
}
/* Store cached CSF information. */
AttsCsfGetFeatures(connId, csf, sizeof(csf));
AppDbSetCsfRecord(dbHdl, AttsCsfGetChangeAwareState(connId), csf);
}
/* set peer's data signing info */
appServerSetSigningInfo(connId);
}
else if (pDmEvt->hdr.event == DM_CONN_CLOSE_IND)
{
/* clear CCC table on connection close */
AttsCccClearTable(connId);
if ((dbHdl = AppDbGetHdl(connId)) != APP_DB_HDL_NONE)
{
/* remember peer's sign counter */
AppDbSetPeerSignCounter(dbHdl, AttsGetSignCounter(connId));
}
}
}
/*************************************************************************************************/
/*!
* \brief Handle a database hash update.
*
* \param pMsg message containing new hash.
*
* \return None.
*/
/*************************************************************************************************/
void appServerHandleDbHashUpdate(attEvt_t *pMsg)
{
uint8_t *pCurrentHash = AppDbGetDbHash();
/* Compare new hash with old. */
if (pCurrentHash != NULL)
{
if (memcmp(pMsg->pValue, pCurrentHash, ATT_DATABASE_HASH_LEN))
{
/* hash has changed, set to NULL. */
pCurrentHash = NULL;
}
}
if (pCurrentHash == NULL)
{
/* Update App database. */
AppDbSetDbHash(pMsg->pValue);
/* Make all bonded clients change-unaware. */
AppDbSetClientsChangeAwareState(APP_DB_HDL_NONE, ATTS_CLIENT_CHANGE_UNAWARE);
/* Make all active clients change-unaware. */
AttsCsfSetClientsChangeAwarenessState(DM_CONN_ID_NONE, ATTS_CLIENT_CHANGE_UNAWARE);
APP_TRACE_INFO0("Database hash updated");
/* Send all connect clients configured to receive Service Changed Indications one now. */
GattSendServiceChangedInd(DM_CONN_ID_NONE, ATT_HANDLE_START, ATT_HANDLE_MAX);
}
}
/*************************************************************************************************/
/*!
* \brief Handle a service change confirmation.
*
* \param pMsg message containing handle value confirmation.
*
* \return None.
*/
/*************************************************************************************************/
void appServerHandleSvcChangeCnf(attEvt_t *pMsg)
{
/* Check if this is a confirmation on the Service Changed Indication. */
if (pMsg->handle == GATT_SC_HDL)
{
appDbHdl_t dbHdl;
dmConnId_t connId = (dmConnId_t)pMsg->hdr.param;
if ((dbHdl = AppDbGetHdl(connId)) != APP_DB_HDL_NONE)
{
/* store update in device database */
AppDbSetClientsChangeAwareState(dbHdl, ATTS_CLIENT_CHANGE_AWARE);
}
/* Client is now change-aware. */
AttsCsfSetClientsChangeAwarenessState(connId, ATTS_CLIENT_CHANGE_AWARE);
}
}
/*************************************************************************************************/
/*!
* \brief Application ATTS client supported features changed callback.
*
* \param connId DM Connection ID
* \param changeAwareState The state of awareness to a change, see ::attClientAwareStates.
* \param pCsf Pointer to the client supported features value.
*
* \return None.
*/
/*************************************************************************************************/
void appServerCsfWriteCback(dmConnId_t connId, uint8_t changeAwareState, uint8_t *pCsf)
{
appDbHdl_t dbHdl;
if ((dbHdl = AppDbGetHdl(connId)) != APP_DB_HDL_NONE)
{
/* store update in device database */
AppDbSetCsfRecord(dbHdl, changeAwareState, pCsf);
}
}
/*************************************************************************************************/
/*!
* \brief Process ATT messages.
*
* \param pMsg message containing event.
*
* \return None.
*/
/*************************************************************************************************/
void AppServerProcAttMsg(wsfMsgHdr_t *pMsg)
{
switch(pMsg->event)
{
case ATTS_DB_HASH_CALC_CMPL_IND:
appServerHandleDbHashUpdate((attEvt_t *)pMsg);
break;
case ATTS_HANDLE_VALUE_CNF:
appServerHandleSvcChangeCnf((attEvt_t *)pMsg);
break;
default:
break;
}
}
/*************************************************************************************************/
/*!
* \brief Application Server initialization routine.
*
* \return None.
*/
/*************************************************************************************************/
void AppServerInit(void)
{
/* register callback with caching state machine */
AttsCsfRegister(appServerCsfWriteCback);
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,749 @@
/*************************************************************************************************/
/*!
* \file
*
* \brief Application framework module for extended slave.
*
* Copyright (c) 2016-2018 Arm Ltd.
*
* Copyright (c) 2019 Packetcraft, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/*************************************************************************************************/
#include <string.h>
#include "wsf_types.h"
#include "wsf_assert.h"
#include "wsf_trace.h"
#include "dm_api.h"
#include "app_api.h"
#include "app_main.h"
/**************************************************************************************************
Data Types
**************************************************************************************************/
/*! Extended slave control block */
typedef struct
{
uint8_t *pPerAdvData[DM_NUM_ADV_SETS]; /*! Periodic advertising data pointers */
uint16_t perAdvDataLen[DM_NUM_ADV_SETS]; /*! Periodic advertising data lengths */
uint16_t perAdvDataBufLen[DM_NUM_ADV_SETS]; /*! Length of periodic advertising data buffer maintained by Application */
uint16_t perAdvDataOffset[DM_NUM_ADV_SETS]; /*! Periodic advertising data offsets */
bool_t perAdvDataSynced[DM_NUM_ADV_SETS]; /*! TRUE if periodic advertising data is synced */
bool_t perAdvParamsCfg[DM_NUM_ADV_SETS]; /*! TRUE if periodic advertising parameters are configured */
uint8_t perAdvState[DM_NUM_ADV_SETS]; /*! Periodic advertising state */
} appExtSlaveCb_t;
/**************************************************************************************************
Local Variables
**************************************************************************************************/
/* Extended slave control block */
static appExtSlaveCb_t appExtSlaveCb;
/*************************************************************************************************/
/*!
* \brief Utility function to start extended advertising.
*
* \param advHandle Advertising handle.
* \param cfgAdvParam Whether to configure advertising parameters
*
* \return None.
*/
/*************************************************************************************************/
static void appSlaveExtAdvStart(uint8_t advHandle, bool_t setAdvParam)
{
appAdvStart(1, &advHandle, pAppExtAdvCfg->advInterval, pAppExtAdvCfg->advDuration,
pAppExtAdvCfg->maxEaEvents, setAdvParam);
}
/*************************************************************************************************/
/*!
* \brief Utility function to handle change in advertising type.
*
* \param pMsg Pointer to DM callback event message.
*
* \return None.
*/
/*************************************************************************************************/
static void appSlaveExtAdvTypeChanged(dmEvt_t *pMsg)
{
/* clear advertising type changed flag */
appSlaveCb.advTypeChanged[pMsg->advSetStop.advHandle] = FALSE;
/* set advertising state */
appSlaveCb.advState[pMsg->advSetStop.advHandle] = APP_ADV_STATE1;
/* restart advertising */
appSlaveExtAdvStart(pMsg->advSetStop.advHandle, TRUE);
}
/*************************************************************************************************/
/*!
* \brief Set the next advertising state.
*
* \param pMsg Pointer to DM callback event message.
*
* \return None.
*/
/*************************************************************************************************/
static void appSlaveNextExtAdvState(dmEvt_t *pMsg)
{
/* if adv hasn't been stopped and all adv/scan data haven't been sent */
if ((appSlaveCb.advState[pMsg->advSetStop.advHandle] != APP_ADV_STOPPED) &&
!appSlaveCb.advDataSynced[pMsg->advSetStop.advHandle])
{
/* set advertising state */
appSlaveCb.advState[pMsg->advSetStop.advHandle] = APP_ADV_STATE1;
/* restart advertising with rest of adv/scan data */
appSlaveExtAdvStart(pMsg->advSetStop.advHandle, FALSE);
return;
}
/* done with this advertising set */
appSlaveCb.advState[pMsg->advSetStop.advHandle] = APP_ADV_STOPPED;
}
/*************************************************************************************************/
/*!
* \brief Handle a DM_ADV_SET_STOP_IND event.
*
* \param pMsg Pointer to DM callback event message.
*
* \return None.
*/
/*************************************************************************************************/
static void appSlaveExtAdvStop(dmEvt_t *pMsg)
{
/* if advertising set was terminated */
if (pMsg->hdr.event == DM_ADV_SET_STOP_IND)
{
/* if advertising was stopped for change to advertising type */
if (appSlaveCb.advTypeChanged[pMsg->advSetStop.advHandle])
{
appSlaveExtAdvTypeChanged(pMsg);
}
/* else if advertising successfully ended with connection being created */
else if (pMsg->hdr.status == HCI_SUCCESS)
{
/* advertising is stopped once a connection is opened */
appSlaveCb.advState[pMsg->advSetStop.advHandle] = APP_ADV_STOPPED;
}
/* else advertising ended for other reasons */
else
{
appSlaveNextExtAdvState(pMsg);
}
}
}
/*************************************************************************************************/
/*!
* \brief Set periodic advertising data fragment.
*
* \param advHandle Advertising handle.
*
* \return None.
*/
/*************************************************************************************************/
static void appSetPerAdvDataFrag(uint8_t advHandle)
{
uint8_t op;
uint16_t fragLen;
uint16_t remainLen;
uint8_t *pAdvData;
bool_t firstFrag = TRUE;
/* get data pointer and remaining data length */
pAdvData = appExtSlaveCb.pPerAdvData[advHandle];
remainLen = appExtSlaveCb.perAdvDataLen[advHandle] - appExtSlaveCb.perAdvDataOffset[advHandle];
/* if remaing data length > max adv data length supported by Controller */
if (remainLen > appSlaveCb.maxAdvDataLen[advHandle])
{
remainLen = appSlaveCb.maxAdvDataLen[advHandle];
}
/* while there remains data to be sent */
while (remainLen > 0)
{
/* if remaing data length > max length of periodic advertising data (per set adv data command) */
if (remainLen > HCI_PER_ADV_DATA_LEN)
{
/* data needs to be fragmented */
fragLen = HCI_PER_ADV_DATA_LEN;
op = firstFrag ? HCI_ADV_DATA_OP_FRAG_FIRST : HCI_ADV_DATA_OP_FRAG_INTER;
}
else
{
/* no fragmentation needed */
fragLen = remainLen;
op = firstFrag ? HCI_ADV_DATA_OP_COMP_FRAG : HCI_ADV_DATA_OP_FRAG_LAST;
}
/* send periodic adv data */
DmPerAdvSetData(advHandle, op, (uint8_t)fragLen,
&(pAdvData[appExtSlaveCb.perAdvDataOffset[advHandle]]));
/* store periodic adv data offset */
appExtSlaveCb.perAdvDataOffset[advHandle] += fragLen;
/* update remaining data length */
remainLen -= fragLen;
firstFrag = FALSE;
}
}
/*************************************************************************************************/
/*!
* \brief Set periodic advertising data.
*
* \param advHandle Advertising handle.
*
* \return None.
*/
/*************************************************************************************************/
static void appSetPerAdvData(uint8_t advHandle)
{
/* set periodic advertising data */
if (appExtSlaveCb.perAdvDataOffset[advHandle] < appExtSlaveCb.perAdvDataLen[advHandle])
{
appSetPerAdvDataFrag(advHandle);
}
/* if all periodic advertising data have been sent */
if ((appExtSlaveCb.perAdvDataOffset[advHandle] >= appExtSlaveCb.perAdvDataLen[advHandle]))
{
appExtSlaveCb.perAdvDataSynced[advHandle] = TRUE;
}
}
/*************************************************************************************************/
/*!
* \brief Utility function to start periodic advertising.
*
* \param avHandle Advertising handles array.
* \param advInterval Advertising interval (in 0.625 ms units).
*
* \return None.
*/
/*************************************************************************************************/
void appPerAdvStart(uint8_t advHandle, uint16_t advInterval)
{
/* if advertising parameters to be configured */
if (!appExtSlaveCb.perAdvParamsCfg[advHandle])
{
/* set min and max interval */
DmPerAdvSetInterval(advHandle, advInterval, advInterval);
/* set advertising parameters */
DmPerAdvConfig(advHandle);
appExtSlaveCb.perAdvParamsCfg[advHandle] = TRUE;
}
/* if periodic adv data to be synced */
if (!appExtSlaveCb.perAdvDataSynced[advHandle])
{
/* set periodic advertising data */
appSetPerAdvData(advHandle);
}
/* start periodic advertising */
DmPerAdvStart(advHandle);
}
/*************************************************************************************************/
/*!
* \brief Set periodic advertising data for a given advertising set.
*
* \param advHandle Advertising handle.
* \param len Length of the data.
* \param pData Pointer to the data.
* \param bufLen Length of the data buffer maintained by Application. Minimum length is
* 31 bytes.
*
* \return None.
*/
/*************************************************************************************************/
void appPerAdvSetData(uint8_t advHandle, uint16_t len, uint8_t *pData, uint16_t bufLen)
{
/* store data for location */
appExtSlaveCb.pPerAdvData[advHandle] = pData;
appExtSlaveCb.perAdvDataLen[advHandle] = len;
/* set length of advertising data buffer maintained by Application */
appExtSlaveCb.perAdvDataBufLen[advHandle] = bufLen;
/* set maximum advertising data length supported by Controller */
appSlaveCb.maxAdvDataLen[advHandle] = HciGetMaxAdvDataLen();
/* reset data offset */
appExtSlaveCb.perAdvDataOffset[advHandle] = 0;
/* Set the data now if we are in the right mode and the data is complete (no fragmentation's required) */
if ((appExtSlaveCb.perAdvState[advHandle] != APP_ADV_STOPPED) &&
(appExtSlaveCb.perAdvParamsCfg[advHandle] == TRUE) &&
(len <= HCI_PER_ADV_DATA_LEN) &&
(len <= appSlaveCb.maxAdvDataLen[advHandle]))
{
appSetPerAdvData(advHandle);
}
/* Otherwise set it when advertising is started or mode changes */
else
{
appExtSlaveCb.perAdvDataSynced[advHandle] = FALSE;
}
}
/*************************************************************************************************/
/*!
* \brief Set the value of an advertising data element in the peroidic advertising data.
* If the element already exists in the data then it is replaced with the new value.
* If the element does not exist in the data it is appended to it, space permitting.
*
* There is special handling for the device name (AD type DM_ADV_TYPE_LOCAL_NAME).
* If the name can only fit in the data if it is shortened, the name is shortened
* and the AD type is changed to DM_ADV_TYPE_SHORT_NAME.
*
* \param advHandle Advertising handle.
* \param adType Advertising data element type.
* \param len Length of the value. Maximum length is 31 bytes.
* \param pValue Pointer to the value.
*
* \return TRUE if the element was successfully added to the data, FALSE otherwise.
*/
/*************************************************************************************************/
bool_t appPerAdvSetAdValue(uint8_t advHandle, uint8_t adType, uint8_t len, uint8_t *pValue)
{
uint8_t *pAdvData;
uint16_t advDataLen;
uint16_t advDataBufLen;
bool_t valueSet;
/* get pointer and length for location */
pAdvData = appExtSlaveCb.pPerAdvData[advHandle];
advDataLen = appExtSlaveCb.perAdvDataLen[advHandle];
advDataBufLen = appExtSlaveCb.perAdvDataBufLen[advHandle];
if (pAdvData != NULL)
{
/* set the new element value in the advertising data */
if (adType == DM_ADV_TYPE_LOCAL_NAME)
{
valueSet = DmAdvSetName(len, pValue, &advDataLen, pAdvData, advDataBufLen);
}
else
{
valueSet = DmAdvSetAdValue(adType, len, pValue, &advDataLen, pAdvData, advDataBufLen);
}
if (valueSet)
{
/* if new value set update periodic advertising data */
appPerAdvSetData(advHandle, advDataLen, pAdvData, advDataBufLen);
return TRUE;
}
}
return FALSE;
}
/*************************************************************************************************/
/*!
* \brief Check if current advertising mode is extended advertising.
*
* \return TRUE if extended advertising mode. FALSE, otherwise.
*/
/*************************************************************************************************/
static bool_t appSlaveExtAdvMode(void)
{
uint8_t i;
/* if DM extended advertising */
if (DmAdvModeExt())
{
/* if first time since last power-on or reset */
if (appSlaveCb.advStopCback == NULL)
{
appSlaveCb.advStopCback = appSlaveExtAdvStop;
appSlaveCb.advRestartCback = NULL;
/* for each advertising set */
for (i = 0; i < DM_NUM_ADV_SETS; i++)
{
/* configure whether to use legacy advertising PDUs */
DmAdvUseLegacyPdu(i, pAppExtAdvCfg->useLegacyPdu[i]);
}
return TRUE;
}
}
if (appSlaveCb.advStopCback == appSlaveExtAdvStop)
{
return TRUE;
}
APP_TRACE_WARN0("Invalid DM advertising mode; mode configured as legacy");
return FALSE;
}
/*************************************************************************************************/
/*!
* \brief Set extended advertising data.
*
* \param advHandle Advertising handle.
* \param location Data location.
* \param len Length of the data. Maximum length is 31 bytes if advertising set uses
* legacy advertising PDUs with extended advertising.
* \param pData Pointer to the data.
* \param bufLen Length of the data buffer maintained by Application. Minimum length is
* 31 bytes.
*
* \return None.
*/
/*************************************************************************************************/
void AppExtAdvSetData(uint8_t advHandle, uint8_t location, uint16_t len, uint8_t *pData, uint16_t bufLen)
{
uint16_t maxLen;
WSF_ASSERT(advHandle < DM_NUM_ADV_SETS);
if (appSlaveExtAdvMode())
{
/* if advertising set uses legacy advertising PDUs with extended advertising */
if (pAppExtAdvCfg->useLegacyPdu[advHandle])
{
/* maximum advertising data length supported by Controller is 31 bytes */
maxLen = HCI_ADV_DATA_LEN;
/* legacy advertising data length cannot exceed 31 bytes */
if (len > HCI_ADV_DATA_LEN)
{
len = HCI_ADV_DATA_LEN;
}
}
else
{
/* get maximum advertising data length supported by Controller */
maxLen = HciGetMaxAdvDataLen();
}
appAdvSetData(advHandle, location, len, pData, bufLen, maxLen);
}
}
/*************************************************************************************************/
/*!
* \brief Start extended advertising using the parameters for the given advertising set and mode.
*
* \param numSets Number of advertising sets.
* \param pAdvHandles Advertising handles array.
* \param mode Discoverable/connectable mode.
*
* \return None.
*/
/*************************************************************************************************/
void AppExtAdvStart(uint8_t numSets, uint8_t *pAdvHandles, uint8_t mode)
{
uint8_t i;
uint16_t advInterval[DM_NUM_ADV_SETS] = {0};
uint16_t advDuration[DM_NUM_ADV_SETS] = {0};
uint8_t maxEaEvents[DM_NUM_ADV_SETS] = {0};
WSF_ASSERT(numSets <= DM_NUM_ADV_SETS);
if (appSlaveExtAdvMode())
{
for (i = 0; i < numSets; i++)
{
uint8_t advHandle = pAdvHandles[i];
WSF_ASSERT(advHandle < DM_NUM_ADV_SETS);
/* initialize advertising state */
appSlaveCb.advState[advHandle] = APP_ADV_STATE1;
if (appSlaveCb.discMode != APP_MODE_NONE)
{
/* start advertising from beginning of advertising data */
appSlaveResetAdvDataOffset(advHandle);
}
/* set maximum advertising data length allowed by Controller for this advertising type */
appSlaveCb.maxAdvDataLen[advHandle] = DmExtMaxAdvDataLen(appSlaveCb.advType[advHandle],
pAppExtAdvCfg->useLegacyPdu[advHandle]);
/* build advertising parameters */
advInterval[i] = pAppExtAdvCfg->advInterval[advHandle];
advDuration[i] = pAppExtAdvCfg->advDuration[advHandle];
maxEaEvents[i] = pAppExtAdvCfg->maxEaEvents[advHandle];
}
appSlaveAdvStart(numSets, pAdvHandles, advInterval, advDuration, maxEaEvents, TRUE, mode);
}
}
/*************************************************************************************************/
/*!
* \brief Stop extended advertising. If the number of sets is set to 0 then all advertising
* sets are disabled.
*
* \param numSets Number of advertising sets.
* \param advHandle Advertising handle array.
*
* \return None.
*/
/*************************************************************************************************/
void AppExtAdvStop(uint8_t numSets, uint8_t *pAdvHandles)
{
WSF_ASSERT(numSets <= DM_NUM_ADV_SETS);
if (appSlaveExtAdvMode())
{
appAdvStop(numSets, pAdvHandles);
}
}
/*************************************************************************************************/
/*!
* \brief Set the value of an advertising data element in the extended advertising or scan
* response data. If the element already exists in the data then it is replaced
* with the new value. If the element does not exist in the data it is appended
* to it, space permitting.
*
* There is special handling for the device name (AD type DM_ADV_TYPE_LOCAL_NAME).
* If the name can only fit in the data if it is shortened, the name is shortened
* and the AD type is changed to DM_ADV_TYPE_SHORT_NAME.
*
* \param advHandle Advertising handle.
* \param location Data location.
* \param adType Advertising data element type.
* \param len Length of the value. Maximum length is 31 bytes if advertising set uses
* legacy advertising PDUs with extended advertising.
* \param pValue Pointer to the value.
*
* \return TRUE if the element was successfully added to the data, FALSE otherwise.
*/
/*************************************************************************************************/
bool_t AppExtAdvSetAdValue(uint8_t advHandle, uint8_t location, uint8_t adType, uint8_t len,
uint8_t *pValue)
{
WSF_ASSERT(advHandle < DM_NUM_ADV_SETS);
if (appSlaveExtAdvMode())
{
return appAdvSetAdValue(advHandle, location, adType, len, pValue);
}
return FALSE;
}
/*************************************************************************************************/
/*!
* \brief Set extended advertising type.
*
* \param advHandle Advertising handle.
* \param advType Advertising type.
*
* \return None.
*/
/*************************************************************************************************/
void AppExtSetAdvType(uint8_t advHandle, uint8_t advType)
{
WSF_ASSERT(advHandle < DM_NUM_ADV_SETS);
if (appSlaveExtAdvMode())
{
/* set maximum advertising data length allowed by Controller for this advertising type */
appSlaveCb.maxAdvDataLen[advHandle] = DmExtMaxAdvDataLen(advType,
pAppExtAdvCfg->useLegacyPdu[advHandle]);
appSetAdvType(advHandle, advType, pAppExtAdvCfg->advInterval[advHandle],
pAppExtAdvCfg->advDuration[advHandle], pAppExtAdvCfg->maxEaEvents[advHandle],
TRUE);
}
}
/*************************************************************************************************/
/*!
* \brief Set advertising peer address and its type for the given advertising set.
*
* \param advHandle Advertising handle.
* \param peerAddrType Peer address type.
* \param pPeerAddr Peer address.
*
* \return None.
*/
/*************************************************************************************************/
void AppExtSetAdvPeerAddr(uint8_t advHandle, uint8_t peerAddrType, uint8_t *pPeerAddr)
{
WSF_ASSERT(advHandle < DM_NUM_ADV_SETS);
appSlaveCb.peerAddrType[advHandle] = peerAddrType;
BdaCpy(appSlaveCb.peerAddr[advHandle], pPeerAddr);
}
/*************************************************************************************************/
/*!
* \brief Accept a connection to a peer device with the given address using a given advertising
* set.
*
* \param advHandle Advertising handle.
* \param advType Advertising type.
* \param addrType Address type.
* \param pAddr Peer device address.
* \param dbHdl Device database handle.
*
* \return Connection identifier.
*/
/*************************************************************************************************/
dmConnId_t AppExtConnAccept(uint8_t advHandle, uint8_t advType, uint8_t addrType, uint8_t *pAddr,
appDbHdl_t dbHdl)
{
WSF_ASSERT(advHandle < DM_NUM_ADV_SETS);
if (appSlaveExtAdvMode())
{
/* set maximum advertising data length allowed by Controller for this advertising type */
appSlaveCb.maxAdvDataLen[advHandle] = DmExtMaxAdvDataLen(advType,
pAppExtAdvCfg->useLegacyPdu[advHandle]);
/* start connectable directed advertising (advertising data is supported only with extended
low-duty cycle connectable directed advertising) */
return appConnAccept(advHandle, advType, pAppExtAdvCfg->advInterval[advHandle],
pAppExtAdvCfg->advDuration[advHandle],
pAppExtAdvCfg->maxEaEvents[advHandle], addrType, pAddr, dbHdl,
(pAppExtAdvCfg->useLegacyPdu[advHandle] ? FALSE : \
(advType == DM_ADV_CONN_DIRECT) ? FALSE : TRUE));
}
/* wrong advertising mode */
return DM_CONN_ID_NONE;
}
/*************************************************************************************************/
/*!
* \brief Set periodic advertising data.
*
* \param advHandle Advertising handle.
* \param len Length of the data.
* \param pData Pointer to the data.
* \param bufLen Length of the data buffer maintained by Application. Minimum length is
* 31 bytes.
*
* \return None.
*/
/*************************************************************************************************/
void AppPerAdvSetData(uint8_t advHandle, uint16_t len, uint8_t *pData, uint16_t bufLen)
{
WSF_ASSERT(advHandle < DM_NUM_ADV_SETS);
if (appSlaveExtAdvMode())
{
// set periodic advertising data
appPerAdvSetData(advHandle, len, pData, bufLen);
}
}
/*************************************************************************************************/
/*!
* \brief Start periodic advertising for the given advertising handle.
*
* \param advHandle Advertising handle.
*
* \return None.
*/
/*************************************************************************************************/
void AppPerAdvStart(uint8_t advHandle)
{
WSF_ASSERT(advHandle < DM_NUM_ADV_SETS);
if (appSlaveExtAdvMode())
{
/* initialize periodic advertising state */
appExtSlaveCb.perAdvState[advHandle] = APP_ADV_STATE1;
/* if entire periodic adv data has been sent to LL */
if (appExtSlaveCb.perAdvDataSynced[advHandle])
{
/* start advertising from beginning of advertising data */
appExtSlaveCb.perAdvDataOffset[advHandle] = 0;
/* force update of advertising data */
appExtSlaveCb.perAdvDataSynced[advHandle] = FALSE;
}
/* start periodic advertising */
appPerAdvStart(advHandle, pAppExtAdvCfg->perAdvInterval[advHandle]);
}
}
/*************************************************************************************************/
/*!
* \brief Stop periodic advertising for the given advertising handle.
*
* \param advHandle Advertising handle.
*
* \return None.
*/
/*************************************************************************************************/
void AppPerAdvStop(uint8_t advHandle)
{
WSF_ASSERT(advHandle < DM_NUM_ADV_SETS);
if (appSlaveExtAdvMode())
{
/* stop periodic advertising */
DmPerAdvStop(advHandle);
appExtSlaveCb.perAdvState[advHandle] = APP_ADV_STOPPED;
}
}
/*************************************************************************************************/
/*!
* \brief Set the value of an advertising data element in the periodic advertising data.
* If the element already exists in the data then it is replaced with the new value.
* If the element does not exist in the data it is appended to it, space permitting.
*
* There is special handling for the device name (AD type DM_ADV_TYPE_LOCAL_NAME).
* If the name can only fit in the data if it is shortened, the name is shortened
* and the AD type is changed to DM_ADV_TYPE_SHORT_NAME.
*
* \param advHandle Advertising handle.
* \param adType Advertising data element type.
* \param len Length of the value. Maximum length is 31 bytes if advertising set uses
* legacy advertising PDUs with extended advertising.
* \param pValue Pointer to the value.
*
* \return TRUE if the element was successfully added to the data, FALSE otherwise.
*/
/*************************************************************************************************/
bool_t AppPerAdvSetAdValue(uint8_t advHandle, uint8_t adType, uint8_t len, uint8_t *pValue)
{
WSF_ASSERT(advHandle < DM_NUM_ADV_SETS);
if (appSlaveExtAdvMode())
{
return appPerAdvSetAdValue(advHandle, adType, len, pValue);
}
return FALSE;
}
@@ -0,0 +1,388 @@
/*************************************************************************************************/
/*!
* \file
*
* \brief Application framework module for legacy slave. This module can be used with both
* DM legacy and extended advertising.
*
* Copyright (c) 2016-2018 Arm Ltd.
*
* Copyright (c) 2019 Packetcraft, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/*************************************************************************************************/
#include <string.h>
#include "wsf_types.h"
#include "wsf_assert.h"
#include "wsf_trace.h"
#include "dm_api.h"
#include "app_api.h"
#include "app_main.h"
/*************************************************************************************************/
/*!
* \brief Utility function to start legacy advertising.
*
* \return None.
*/
/*************************************************************************************************/
static void appSlaveLegAdvStart(void)
{
uint8_t advHandle;
uint8_t maxEaEvents;
uint16_t interval;
interval = pAppAdvCfg->advInterval[appSlaveCb.advState[DM_ADV_HANDLE_DEFAULT]];
/* if this advertising state is being used */
if (interval > 0)
{
advHandle = DM_ADV_HANDLE_DEFAULT;
maxEaEvents = 0;
appAdvStart(1, &advHandle, &interval,
&(pAppAdvCfg->advDuration[appSlaveCb.advState[DM_ADV_HANDLE_DEFAULT]]),
&maxEaEvents, TRUE);
}
else
{
/* done with all advertising states */
appSlaveCb.advState[DM_ADV_HANDLE_DEFAULT] = APP_ADV_STOPPED;
}
}
/*************************************************************************************************/
/*!
* \brief Utility function to handle change in advertising type.
*
* \param pMsg Pointer to DM callback event message.
*
* \return None.
*/
/*************************************************************************************************/
static void appSlaveLegAdvTypeChanged(dmEvt_t *pMsg)
{
/* clear advertising type changed flag */
appSlaveCb.advTypeChanged[DM_ADV_HANDLE_DEFAULT] = FALSE;
/* set advertising state */
appSlaveCb.advState[DM_ADV_HANDLE_DEFAULT] = APP_ADV_STATE1;
/* start advertising */
appSlaveLegAdvStart();
}
/*************************************************************************************************/
/*!
* \brief Set the next advertising state.
*
* \param pMsg Pointer to DM callback event message.
*
* \return None.
*/
/*************************************************************************************************/
static void appSlaveNextLegAdvState(dmEvt_t *pMsg)
{
/* go to next advertising state */
appSlaveCb.advState[DM_ADV_HANDLE_DEFAULT]++;
/* if haven't reached stopped state then start advertising */
if (appSlaveCb.advState[DM_ADV_HANDLE_DEFAULT] < APP_ADV_STOPPED)
{
appSlaveLegAdvStart();
}
}
/*************************************************************************************************/
/*!
* \brief Handle DM_ADV_SET_STOP_IND and DM_ADV_STOP_IND events.
*
* \param pMsg Pointer to DM callback event message.
*
* \return None.
*/
/*************************************************************************************************/
static void appSlaveLegAdvStop(dmEvt_t *pMsg)
{
/* if legacy advertising PDUs are used with advertising extensions feature */
if (pMsg->hdr.event == DM_ADV_SET_STOP_IND)
{
/* if advertising successfully ended with connection being created */
if (pMsg->advSetStop.status == HCI_SUCCESS)
{
/* connection open indication event will determine next advertising state */
return;
}
}
/* if advertising was stopped for change to advertising type */
if (appSlaveCb.advTypeChanged[DM_ADV_HANDLE_DEFAULT])
{
appSlaveLegAdvTypeChanged(pMsg);
}
/* else advertising ended for another reason */
else
{
appSlaveNextLegAdvState(pMsg);
}
}
/*************************************************************************************************/
/*!
* \brief Restart advertising.
*
* \param pMsg Pointer to DM callback event message.
*
* \return None.
*/
/*************************************************************************************************/
static void appSlaveLegAdvRestart(dmEvt_t *pMsg)
{
/* if connection closed */
if (pMsg->hdr.event == DM_CONN_CLOSE_IND)
{
/* if connectable directed advertising failed to establish connection or was cancelled */
if (appSlaveCb.advDirected)
{
appSlaveCb.advDirected = FALSE;
return;
}
}
/* else if connection opened */
else if (pMsg->hdr.event == DM_CONN_OPEN_IND)
{
/* if connectable directed advertising */
if (appSlaveCb.advDirected)
{
appSlaveCb.advDirected = FALSE;
return;
}
/* advertising is stopped once a connection is opened */
appSlaveCb.advState[DM_ADV_HANDLE_DEFAULT] = APP_ADV_STOPPED;
}
/* if advertising stopped restart advertising */
if (appSlaveCb.advState[DM_ADV_HANDLE_DEFAULT] == APP_ADV_STOPPED)
{
/* set advertising state */
appSlaveCb.advState[DM_ADV_HANDLE_DEFAULT] = APP_ADV_STATE1;
/* start advertising */
appSlaveLegAdvStart();
}
}
/*************************************************************************************************/
/*!
* \brief Check if current advertising mode is legacy advertising.
*
* \return TRUE if legacy advertising mode. FALSE, otherwise.
*/
/*************************************************************************************************/
static bool_t appSlaveAdvMode(void)
{
/* legacy app slave works with both DM legacy and extended advertising */
/* if first time since last power-on or reset */
if (appSlaveCb.advStopCback == NULL)
{
appSlaveCb.advStopCback = appSlaveLegAdvStop;
appSlaveCb.advRestartCback = appSlaveLegAdvRestart;
return TRUE;
}
if (appSlaveCb.advStopCback == appSlaveLegAdvStop)
{
return TRUE;
}
APP_TRACE_WARN0("Invalid DM advertising mode; mode configured as extended");
return FALSE;
}
/*************************************************************************************************/
/*!
* \brief Set advertising data.
*
* \param location Data location.
* \param len Length of the data. Maximum length is 31 bytes.
* \param pData Pointer to the data.
*
* \return None.
*/
/*************************************************************************************************/
void AppAdvSetData(uint8_t location, uint8_t len, uint8_t *pData)
{
if (appSlaveAdvMode())
{
/* legacy advertising data length cannot exceed 31 bytes */
if (len > HCI_ADV_DATA_LEN)
{
len = HCI_ADV_DATA_LEN;
}
/* maximum advertising data length supported by Controller is 31 bytes */
appAdvSetData(DM_ADV_HANDLE_DEFAULT, location, len, pData, HCI_ADV_DATA_LEN, HCI_ADV_DATA_LEN);
}
}
/*************************************************************************************************/
/*!
* \brief Start advertising using the parameters for the given mode.
*
* \param mode Discoverable/connectable mode.
*
* \return None.
*/
/*************************************************************************************************/
void AppAdvStart(uint8_t mode)
{
uint8_t advHandle;
uint8_t maxEaEvents;
if (appSlaveAdvMode())
{
advHandle = DM_ADV_HANDLE_DEFAULT;
maxEaEvents = 0;
/* initialize advertising state */
appSlaveCb.advState[DM_ADV_HANDLE_DEFAULT] = APP_ADV_STATE1;
appSlaveAdvStart(1, &advHandle, &(pAppAdvCfg->advInterval[APP_ADV_STATE1]),
&(pAppAdvCfg->advDuration[APP_ADV_STATE1]), &maxEaEvents, TRUE, mode);
}
}
/*************************************************************************************************/
/*!
* \brief Stop advertising.
*
* \return None.
*/
/*************************************************************************************************/
void AppAdvStop(void)
{
uint8_t advHandle;
if (appSlaveAdvMode())
{
advHandle = DM_ADV_HANDLE_DEFAULT;
appAdvStop(1, &advHandle);
}
}
/*************************************************************************************************/
/*!
* \brief Set the value of an advertising data element in the advertising or scan
* response data. If the element already exists in the data then it is replaced
* with the new value. If the element does not exist in the data it is appended
* to it, space permitting.
*
* There is special handling for the device name (AD type DM_ADV_TYPE_LOCAL_NAME).
* If the name can only fit in the data if it is shortened, the name is shortened
* and the AD type is changed to DM_ADV_TYPE_SHORT_NAME.
*
* \param location Data location.
* \param adType Advertising data element type.
* \param len Length of the value. Maximum length is 31 bytes.
* \param pValue Pointer to the value.
*
* \return TRUE if the element was successfully added to the data, FALSE otherwise.
*/
/*************************************************************************************************/
bool_t AppAdvSetAdValue(uint8_t location, uint8_t adType, uint8_t len, uint8_t *pValue)
{
if (appSlaveAdvMode())
{
return appAdvSetAdValue(DM_ADV_HANDLE_DEFAULT, location, adType, len, pValue);
}
return FALSE;
}
/*************************************************************************************************/
/*!
* \brief Set advertising type.
*
* \param advType Advertising type.
*
* \return None.
*/
/*************************************************************************************************/
void AppSetAdvType(uint8_t advType)
{
if (appSlaveAdvMode())
{
appSetAdvType(DM_ADV_HANDLE_DEFAULT, advType,
pAppAdvCfg->advInterval[appSlaveCb.advState[DM_ADV_HANDLE_DEFAULT]],
pAppAdvCfg->advDuration[appSlaveCb.advState[DM_ADV_HANDLE_DEFAULT]], 0, TRUE);
}
}
/*************************************************************************************************/
/*!
* \brief Set advertising peer address and its type.
*
* \param peerAddrType Peer address type.
* \param pPeerAddr Peer address.
*
* \return None.
*/
/*************************************************************************************************/
void AppSetAdvPeerAddr(uint8_t peerAddrType, uint8_t *pPeerAddr)
{
appSlaveCb.peerAddrType[DM_ADV_HANDLE_DEFAULT] = peerAddrType;
BdaCpy(appSlaveCb.peerAddr[DM_ADV_HANDLE_DEFAULT], pPeerAddr);
}
/*************************************************************************************************/
/*!
* \brief Accept a connection to a peer device with the given address.
*
* \param advType Advertising type.
* \param addrType Address type.
* \param pAddr Peer device address.
* \param dbHdl Device database handle.
*
* \return Connection identifier.
*/
/************************************************************************************************/
dmConnId_t AppConnAccept(uint8_t advType, uint8_t addrType, uint8_t *pAddr, appDbHdl_t dbHdl)
{
dmConnId_t connId;
if (appSlaveAdvMode())
{
/* advertising data is not supported with legacy directed advertising */
connId = appConnAccept(DM_ADV_HANDLE_DEFAULT, advType, pAppAdvCfg->advInterval[APP_ADV_STATE1],
pAppAdvCfg->advDuration[APP_ADV_STATE1], 0, addrType, pAddr, dbHdl,
FALSE);
if (connId != DM_CONN_ID_NONE)
{
appSlaveCb.advDirected = TRUE;
}
}
else
{
/* wrong advertising mode */
connId = DM_CONN_ID_NONE;
}
return connId;
}
@@ -0,0 +1,204 @@
/*************************************************************************************************/
/*!
* \file
*
* \brief Terminal handler.
*
* Copyright (c) 2015-2019 Arm Ltd.
*
* Copyright (c) 2019 Packetcraft, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/*************************************************************************************************/
#include <stdlib.h>
#include <string.h>
#include <stdarg.h>
#include "util/terminal.h"
#include "app_ui.h"
#include "util/print.h"
#include "wsf_types.h"
#include "wsf_assert.h"
#include "wsf_trace.h"
#include "wsf_bufio.h"
#include "util/bstream.h"
#include "dm_api.h"
/**************************************************************************************************
Local Function Prototypes
**************************************************************************************************/
/*! \brief Button Command Handler */
static uint8_t appTerminalCommandBtnHandler(uint32_t argc, char **argv);
/*! \brief Security Pin Code Command Handler. */
static uint8_t appTerminalPinCodeHandler(uint32_t argc, char **argv);
/**************************************************************************************************
Local Variables
**************************************************************************************************/
/*! \brief Button command. */
static terminalCommand_t appTerminalButtonPress = { NULL, "btn", "btn <ID> <s|m|l|x>", appTerminalCommandBtnHandler };
/*! \brief Security Pin Code commands. */
static terminalCommand_t appTerminalPinCode = { NULL, "pin", "pin <ConnID> <Pin Code>", appTerminalPinCodeHandler };
/*************************************************************************************************/
/*!
* \brief Initialize terminal.
*
* \return None.
*/
/*************************************************************************************************/
void AppTerminalInit(void)
{
wsfHandlerId_t handlerId;
/* Initialize Serial Communication. */
// WsfBufIoUartRegister(TerminalRx);
// TerminalRegisterUartTxFunc(WsfBufIoWrite);
handlerId = WsfOsSetNextHandler(TerminalHandler);
TerminalInit(handlerId);
/* Register commands. */
TerminalRegisterCommand(&appTerminalButtonPress);
TerminalRegisterCommand(&appTerminalPinCode);
}
/*************************************************************************************************/
/*!
* \brief Handler for a button press terminal command.
*
* \param argc The number of arguments passed to the command.
* \param argv The array of arguments; the 0th argument is the command.
*
* \return Error code.
*/
/*************************************************************************************************/
static uint8_t appTerminalCommandBtnHandler(uint32_t argc, char **argv)
{
if (argc < 3)
{
return TERMINAL_ERROR_TOO_FEW_ARGUMENTS;
}
else if (argc == 3)
{
uint8_t btnIndx;
uint8_t event;
if (strcmp(argv[1], "1") == 0)
{
btnIndx = 1;
}
else if (strcmp(argv[1], "2") == 0)
{
btnIndx = 2;
}
else
{
return TERMINAL_ERROR_BAD_ARGUMENTS;
}
if (strcmp(argv[2], "d") == 0)
{
TerminalTxPrint("Button %s Press" TERMINAL_STRING_NEW_LINE, argv[1]);
event = (btnIndx == 1? APP_UI_BTN_1_DOWN : APP_UI_BTN_2_DOWN);
}
else if (strcmp(argv[2], "s") == 0)
{
TerminalTxPrint("Short Button %s Press" TERMINAL_STRING_NEW_LINE, argv[1]);
event = (btnIndx == 1? APP_UI_BTN_1_SHORT : APP_UI_BTN_2_SHORT);
}
else if (strcmp(argv[2], "m") == 0)
{
TerminalTxPrint("Medium Button %s Press" TERMINAL_STRING_NEW_LINE, argv[1]);
event = (btnIndx == 1? APP_UI_BTN_1_MED : APP_UI_BTN_2_MED);
}
else if (strcmp(argv[2], "l") == 0)
{
TerminalTxPrint("Long Button %s Press" TERMINAL_STRING_NEW_LINE, argv[1]);
event = (btnIndx == 1? APP_UI_BTN_1_LONG : APP_UI_BTN_2_LONG);
}
else if (strcmp(argv[2], "x") == 0)
{
TerminalTxPrint("Medium Button %s Press" TERMINAL_STRING_NEW_LINE, argv[1]);
event = (btnIndx == 1? APP_UI_BTN_1_EX_LONG : APP_UI_BTN_2_EX_LONG);
}
else
{
return TERMINAL_ERROR_BAD_ARGUMENTS;
}
AppUiBtnTest(event);
}
else
{
return TERMINAL_ERROR_TOO_MANY_ARGUMENTS;
}
return TERMINAL_ERROR_OK;
}
/*************************************************************************************************/
/*!
* \brief Handler for a pin code terminal command.
*
* \param argc The number of arguments passed to the command.
* \param argv The array of arguments; the 0th argument is the command.
*
* \return Error code.
*/
/*************************************************************************************************/
static uint8_t appTerminalPinCodeHandler(uint32_t argc, char **argv)
{
if (argc < 2)
{
return TERMINAL_ERROR_TOO_FEW_ARGUMENTS;
}
else if (argc == 3)
{
uint32_t passkey;
uint8_t buf[SMP_PIN_LEN];
uint8_t connId;
passkey = atoi(argv[2]);
connId = atoi(argv[1]);
if (connId < 1 || connId > DM_CONN_MAX)
{
TerminalTxPrint("ConnID must be in the range [1 .. %d]\n", DM_CONN_MAX);
return TERMINAL_ERROR_BAD_ARGUMENTS;
}
passkey %= 1000000;
/* convert to byte buffer */
buf[0] = UINT32_TO_BYTE0(passkey);
buf[1] = UINT32_TO_BYTE1(passkey);
buf[2] = UINT32_TO_BYTE2(passkey);
/* send authentication response to DM */
DmSecAuthRsp((dmConnId_t) connId, SMP_PIN_LEN, buf);
}
else
{
return TERMINAL_ERROR_TOO_MANY_ARGUMENTS;
}
return TERMINAL_ERROR_OK;
}
@@ -0,0 +1,873 @@
/*************************************************************************************************/
/*!
* \file
*
* \brief Application framework device database example, using simple RAM-based storage.
*
* Copyright (c) 2011-2019 Arm Ltd.
*
* Copyright (c) 2019 Packetcraft, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/*************************************************************************************************/
#include <string.h>
#include "wsf_types.h"
#include "wsf_assert.h"
#include "util/bda.h"
#include "app_api.h"
#include "app_main.h"
#include "app_db.h"
#include "app_cfg.h"
/**************************************************************************************************
Data Types
**************************************************************************************************/
/*! Database record */
typedef struct
{
/*! Common for all roles */
bdAddr_t peerAddr; /*! Peer address */
uint8_t addrType; /*! Peer address type */
dmSecIrk_t peerIrk; /*! Peer IRK */
dmSecCsrk_t peerCsrk; /*! Peer CSRK */
uint8_t keyValidMask; /*! Valid keys in this record */
bool_t inUse; /*! TRUE if record in use */
bool_t valid; /*! TRUE if record is valid */
bool_t peerAddedToRl; /*! TRUE if peer device's been added to resolving list */
bool_t peerRpao; /*! TRUE if RPA Only attribute's present on peer device */
/*! For slave local device */
dmSecLtk_t localLtk; /*! Local LTK */
uint8_t localLtkSecLevel; /*! Local LTK security level */
bool_t peerAddrRes; /*! TRUE if address resolution's supported on peer device (master) */
/*! For master local device */
dmSecLtk_t peerLtk; /*! Peer LTK */
uint8_t peerLtkSecLevel; /*! Peer LTK security level */
/*! for ATT server local device */
uint16_t cccTbl[APP_DB_NUM_CCCD]; /*! Client characteristic configuration descriptors */
uint32_t peerSignCounter; /*! Peer Sign Counter */
uint8_t changeAwareState; /*! Peer client awareness to state change in database */
uint8_t csf[ATT_CSF_LEN]; /*! Peer client supported features record */
/*! for ATT client */
bool_t cacheByHash; /*! TRUE if cached handles are maintained by comparing database hash */
uint8_t dbHash[ATT_DATABASE_HASH_LEN]; /*! Peer database hash */
uint16_t hdlList[APP_DB_HDL_LIST_LEN]; /*! Cached handle list */
uint8_t discStatus; /*! Service discovery and configuration status */
bool_t master_role; /*! True if local device is master for this record */
} appDbRec_t;
/*! Database type */
typedef struct
{
appDbRec_t rec[APP_DB_NUM_RECS]; /*! Device database records */
char devName[ATT_DEFAULT_PAYLOAD_LEN]; /*! Device name */
uint8_t devNameLen; /*! Device name length */
uint8_t dbHash[ATT_DATABASE_HASH_LEN]; /*! Device GATT database hash */
} appDb_t;
/**************************************************************************************************
Local Variables
**************************************************************************************************/
/*! Database */
static appDb_t appDb;
/*! When all records are allocated use this index to determine which to overwrite */
static appDbRec_t *pAppDbNewRec = appDb.rec;
/*************************************************************************************************/
/*!
* \brief Initialize the device database.
*
* \return None.
*/
/*************************************************************************************************/
void AppDbInit(void)
{
return;
}
/*************************************************************************************************/
/*!
* \brief Create a new device database record.
*
* \param addrType Address type.
* \param pAddr Peer device address.
*
* \return Database record handle.
*/
/*************************************************************************************************/
appDbHdl_t AppDbNewRecord(uint8_t addrType, uint8_t *pAddr, bool_t master_role)
{
appDbRec_t *pRec = appDb.rec;
uint8_t i;
/* find a free record */
for (i = APP_DB_NUM_RECS; i > 0; i--, pRec++)
{
if (!pRec->inUse)
{
break;
}
}
/* if all records were allocated */
if (i == 0)
{
/* overwrite a record */
pRec = pAppDbNewRec;
/* get next record to overwrite */
pAppDbNewRec++;
if (pAppDbNewRec == &appDb.rec[APP_DB_NUM_RECS])
{
pAppDbNewRec = appDb.rec;
}
}
/* initialize record */
memset(pRec, 0, sizeof(appDbRec_t));
pRec->inUse = TRUE;
pRec->addrType = addrType;
BdaCpy(pRec->peerAddr, pAddr);
pRec->peerAddedToRl = FALSE;
pRec->peerRpao = FALSE;
pRec->master_role = master_role;
return (appDbHdl_t) pRec;
}
/*************************************************************************************************/
/*!
* \brief Get the next database record for a given record. For the first record, the function
* should be called with 'hdl' set to 'APP_DB_HDL_NONE'.
*
* \param hdl Database record handle.
*
* \return Next record handle found. APP_DB_HDL_NONE, otherwise.
*/
/*************************************************************************************************/
appDbHdl_t AppDbGetNextRecord(appDbHdl_t hdl)
{
appDbRec_t *pRec;
/* if first record is requested */
if (hdl == APP_DB_HDL_NONE)
{
pRec = appDb.rec;
}
/* if valid record passed in */
else if (AppDbRecordInUse(hdl))
{
pRec = (appDbRec_t *)hdl;
pRec++;
}
/* invalid record passed in */
else
{
return APP_DB_HDL_NONE;
}
/* look for next valid record */
while (pRec < &appDb.rec[APP_DB_NUM_RECS])
{
/* if record is in use */
if (pRec->inUse && pRec->valid)
{
/* record found */
return (appDbHdl_t)pRec;
}
/* look for next record */
pRec++;
}
/* end of records */
return APP_DB_HDL_NONE;
}
/*************************************************************************************************/
/*!
* \brief Delete a new device database record.
*
* \param hdl Database record handle.
*
* \return None.
*/
/*************************************************************************************************/
void AppDbDeleteRecord(appDbHdl_t hdl)
{
((appDbRec_t *) hdl)->inUse = FALSE;
}
/*************************************************************************************************/
/*!
* \brief Validate a new device database record. This function is called when pairing is
* successful and the devices are bonded.
*
* \param hdl Database record handle.
* \param keyMask Bitmask of keys to validate.
*
* \return None.
*/
/*************************************************************************************************/
void AppDbValidateRecord(appDbHdl_t hdl, uint8_t keyMask)
{
((appDbRec_t *) hdl)->valid = TRUE;
((appDbRec_t *) hdl)->keyValidMask = keyMask;
}
/*************************************************************************************************/
/*!
* \brief Check if a record has been validated. If it has not, delete it. This function
* is typically called when the connection is closed.
*
* \param hdl Database record handle.
*
* \return None.
*/
/*************************************************************************************************/
void AppDbCheckValidRecord(appDbHdl_t hdl)
{
if (((appDbRec_t *) hdl)->valid == FALSE)
{
AppDbDeleteRecord(hdl);
}
}
/*************************************************************************************************/
/*!
* \brief Check if a database record is in use.
* \param hdl Database record handle.
*
* \return TURE if record in use. FALSE, otherwise.
*/
/*************************************************************************************************/
bool_t AppDbRecordInUse(appDbHdl_t hdl)
{
appDbRec_t *pRec = appDb.rec;
uint8_t i;
/* see if record is in database record list */
for (i = APP_DB_NUM_RECS; i > 0; i--, pRec++)
{
if (pRec->inUse && pRec->valid && (pRec == ((appDbRec_t *)hdl)))
{
return TRUE;
}
}
return FALSE;
}
/*************************************************************************************************/
/*!
* \brief Check if there is a stored bond with any device.
*
* \param hdl Database record handle.
*
* \return TRUE if a bonded device is found, FALSE otherwise.
*/
/*************************************************************************************************/
bool_t AppDbCheckBonded(void)
{
appDbRec_t *pRec = appDb.rec;
uint8_t i;
/* find a record */
for (i = APP_DB_NUM_RECS; i > 0; i--, pRec++)
{
if (pRec->inUse && !pRec->master_role)
{
return TRUE;
}
}
return FALSE;
}
/*************************************************************************************************/
/*!
* \brief Delete all database records.
*
* \return None.
*/
/*************************************************************************************************/
void AppDbDeleteAllRecords(void)
{
appDbRec_t *pRec = appDb.rec;
uint8_t i;
/* set in use to false for all records */
for (i = APP_DB_NUM_RECS; i > 0; i--, pRec++)
{
pRec->inUse = FALSE;
}
}
/*************************************************************************************************/
/*!
* \brief Find a device database record by peer address.
*
* \param addrType Address type.
* \param pAddr Peer device address.
*
* \return Database record handle or APP_DB_HDL_NONE if not found.
*/
/*************************************************************************************************/
appDbHdl_t AppDbFindByAddr(uint8_t addrType, uint8_t *pAddr)
{
appDbRec_t *pRec = appDb.rec;
uint8_t peerAddrType = DmHostAddrType(addrType);
uint8_t i;
/* find matching record */
for (i = APP_DB_NUM_RECS; i > 0; i--, pRec++)
{
if (pRec->inUse && (pRec->addrType == peerAddrType) && BdaCmp(pRec->peerAddr, pAddr))
{
return (appDbHdl_t) pRec;
}
}
return APP_DB_HDL_NONE;
}
/*************************************************************************************************/
/*!
* \brief Find a device database record by data in an LTK request.
*
* \param encDiversifier Encryption diversifier associated with key.
* \param pRandNum Pointer to random number associated with key.
*
* \return Database record handle or APP_DB_HDL_NONE if not found.
*/
/*************************************************************************************************/
appDbHdl_t AppDbFindByLtkReq(uint16_t encDiversifier, uint8_t *pRandNum)
{
appDbRec_t *pRec = appDb.rec;
uint8_t i;
/* find matching record */
for (i = APP_DB_NUM_RECS; i > 0; i--, pRec++)
{
if (pRec->inUse && (pRec->localLtk.ediv == encDiversifier) &&
(memcmp(pRec->localLtk.rand, pRandNum, SMP_RAND8_LEN) == 0))
{
return (appDbHdl_t) pRec;
}
}
return APP_DB_HDL_NONE;
}
/*************************************************************************************************/
/*!
* \brief Get a key from a device database record.
*
* \param hdl Database record handle.
* \param type Type of key to get.
* \param pSecLevel If the key is valid, the security level of the key.
*
* \return Pointer to key if key is valid or NULL if not valid.
*/
/*************************************************************************************************/
dmSecKey_t *AppDbGetKey(appDbHdl_t hdl, uint8_t type, uint8_t *pSecLevel)
{
dmSecKey_t *pKey = NULL;
/* if key valid */
if ((type & ((appDbRec_t *) hdl)->keyValidMask) != 0)
{
switch(type)
{
case DM_KEY_LOCAL_LTK:
*pSecLevel = ((appDbRec_t *) hdl)->localLtkSecLevel;
pKey = (dmSecKey_t *) &((appDbRec_t *) hdl)->localLtk;
break;
case DM_KEY_PEER_LTK:
*pSecLevel = ((appDbRec_t *) hdl)->peerLtkSecLevel;
pKey = (dmSecKey_t *) &((appDbRec_t *) hdl)->peerLtk;
break;
case DM_KEY_IRK:
pKey = (dmSecKey_t *)&((appDbRec_t *)hdl)->peerIrk;
break;
case DM_KEY_CSRK:
pKey = (dmSecKey_t *)&((appDbRec_t *)hdl)->peerCsrk;
break;
default:
break;
}
}
return pKey;
}
/*************************************************************************************************/
/*!
* \brief Set a key in a device database record.
*
* \param hdl Database record handle.
* \param pKey Key data.
*
* \return None.
*/
/*************************************************************************************************/
void AppDbSetKey(appDbHdl_t hdl, dmSecKeyIndEvt_t *pKey)
{
switch(pKey->type)
{
case DM_KEY_LOCAL_LTK:
((appDbRec_t *) hdl)->localLtkSecLevel = pKey->secLevel;
((appDbRec_t *) hdl)->localLtk = pKey->keyData.ltk;
break;
case DM_KEY_PEER_LTK:
((appDbRec_t *) hdl)->peerLtkSecLevel = pKey->secLevel;
((appDbRec_t *) hdl)->peerLtk = pKey->keyData.ltk;
break;
case DM_KEY_IRK:
((appDbRec_t *)hdl)->peerIrk = pKey->keyData.irk;
/* make sure peer record is stored using its identity address */
((appDbRec_t *)hdl)->addrType = pKey->keyData.irk.addrType;
BdaCpy(((appDbRec_t *)hdl)->peerAddr, pKey->keyData.irk.bdAddr);
break;
case DM_KEY_CSRK:
((appDbRec_t *)hdl)->peerCsrk = pKey->keyData.csrk;
/* sign counter must be initialized to zero when CSRK is generated */
((appDbRec_t *)hdl)->peerSignCounter = 0;
break;
default:
break;
}
}
/*************************************************************************************************/
/*!
* \brief Get the peer's database hash.
*
* \param hdl Database record handle.
*
* \return Pointer to database hash.
*/
/*************************************************************************************************/
uint8_t *AppDbGetPeerDbHash(appDbHdl_t hdl)
{
return ((appDbRec_t *) hdl)->dbHash;
}
/*************************************************************************************************/
/*!
* \brief Set a new peer database hash.
*
* \param hdl Database record handle.
* \param pDbHash Pointer to new hash.
*
* \return None.
*/
/*************************************************************************************************/
void AppDbSetPeerDbHash(appDbHdl_t hdl, uint8_t *pDbHash)
{
WSF_ASSERT(pDbHash != NULL);
memcpy(((appDbRec_t *) hdl)->dbHash, pDbHash, ATT_DATABASE_HASH_LEN);
}
/*************************************************************************************************/
/*!
* \brief Check if cached handles' validity is determined by reading the peer's database hash
*
* \param hdl Database record handle.
*
* \return \ref TRUE if peer's database hash must be read to verify handles have not changed.
*/
/*************************************************************************************************/
bool_t AppDbIsCacheCheckedByHash(appDbHdl_t hdl)
{
return ((appDbRec_t *) hdl)->cacheByHash;
}
/*************************************************************************************************/
/*!
* \brief Set if cached handles' validity is determined by reading the peer's database hash.
*
* \param hdl Database record handle.
* \param cacheByHash \ref TRUE if peer's database must be read to verify cached handles.
*
* \return None.
*/
/*************************************************************************************************/
void AppDbSetCacheByHash(appDbHdl_t hdl, bool_t cacheByHash)
{
((appDbRec_t *) hdl)->cacheByHash = cacheByHash;
}
/*************************************************************************************************/
/*!
* \brief Get the client characteristic configuration descriptor table.
*
* \param hdl Database record handle.
*
* \return Pointer to client characteristic configuration descriptor table.
*/
/*************************************************************************************************/
uint16_t *AppDbGetCccTbl(appDbHdl_t hdl)
{
return ((appDbRec_t *) hdl)->cccTbl;
}
/*************************************************************************************************/
/*!
* \brief Set a value in the client characteristic configuration table.
*
* \param hdl Database record handle.
* \param idx Table index.
* \param value client characteristic configuration value.
*
* \return None.
*/
/*************************************************************************************************/
void AppDbSetCccTblValue(appDbHdl_t hdl, uint16_t idx, uint16_t value)
{
WSF_ASSERT(idx < APP_DB_NUM_CCCD);
((appDbRec_t *) hdl)->cccTbl[idx] = value;
}
/*************************************************************************************************/
/*!
* \brief Get the client supported features record.
*
* \param hdl Database record handle.
* \param pChangeAwareState Pointer to peer client's change aware status to a change in the database.
* \param pCsf Pointer to csf value pointer.
*
* \return None.
*/
/*************************************************************************************************/
void AppDbGetCsfRecord(appDbHdl_t hdl, uint8_t *pChangeAwareState, uint8_t **pCsf)
{
*pChangeAwareState = ((appDbRec_t *)hdl)->changeAwareState;
*pCsf = ((appDbRec_t *) hdl)->csf;
}
/*************************************************************************************************/
/*!
* \brief Set a client supported features record.
*
* \param hdl Database record handle.
* \param changeAwareState The state of awareness to a change, see ::attClientAwareStates.
* \param pCsf Pointer to the client supported features value.
*
* \return None.
*/
/*************************************************************************************************/
void AppDbSetCsfRecord(appDbHdl_t hdl, uint8_t changeAwareState, uint8_t *pCsf)
{
if ((pCsf != NULL) && (hdl != APP_DB_HDL_NONE))
{
((appDbRec_t *) hdl)->changeAwareState = changeAwareState;
memcpy(&((appDbRec_t *) hdl)->csf, pCsf, ATT_CSF_LEN);
}
}
/*************************************************************************************************/
/*!
* \brief Set client's state of awareness to a change in the database.
*
* \param hdl Database record handle. If \ref hdl == \ref NULL, state is set for all
* clients.
* \param state The state of awareness to a change, see ::attClientAwareStates.
*
* \return None.
*/
/*************************************************************************************************/
void AppDbSetClientsChangeAwareState(appDbHdl_t hdl, uint8_t state)
{
if (hdl == APP_DB_HDL_NONE)
{
appDbRec_t *pRec = appDb.rec;
uint8_t i;
/* Set all clients status to change-unaware. */
for (i = APP_DB_NUM_RECS; i > 0; i--, pRec++)
{
pRec->changeAwareState = state;
}
}
else
{
((appDbRec_t *) hdl)->changeAwareState = state;
}
}
/*************************************************************************************************/
/*!
* \brief Get device's GATT database hash.
*
* \return Pointer to database hash.
*/
/*************************************************************************************************/
uint8_t *AppDbGetDbHash(void)
{
return appDb.dbHash;
}
/*************************************************************************************************/
/*!
* \brief Set device's GATT database hash.
*
* \param pHash GATT database hash to store.
*
* \return None.
*/
/*************************************************************************************************/
void AppDbSetDbHash(uint8_t *pHash)
{
if (pHash != NULL)
{
memcpy(appDb.dbHash, pHash, ATT_DATABASE_HASH_LEN);
}
}
/*************************************************************************************************/
/*!
* \brief Get the discovery status.
*
* \param hdl Database record handle.
*
* \return Discovery status.
*/
/*************************************************************************************************/
uint8_t AppDbGetDiscStatus(appDbHdl_t hdl)
{
return ((appDbRec_t *) hdl)->discStatus;
}
/*************************************************************************************************/
/*!
* \brief Set the discovery status.
*
* \param hdl Database record handle.
* \param state Discovery status.
*
* \return None.
*/
/*************************************************************************************************/
void AppDbSetDiscStatus(appDbHdl_t hdl, uint8_t status)
{
((appDbRec_t *) hdl)->discStatus = status;
}
/*************************************************************************************************/
/*!
* \brief Get the cached handle list.
*
* \param hdl Database record handle.
*
* \return Pointer to handle list.
*/
/*************************************************************************************************/
uint16_t *AppDbGetHdlList(appDbHdl_t hdl)
{
return ((appDbRec_t *) hdl)->hdlList;
}
/*************************************************************************************************/
/*!
* \brief Set the cached handle list.
*
* \param hdl Database record handle.
* \param pHdlList Pointer to handle list.
*
* \return None.
*/
/*************************************************************************************************/
void AppDbSetHdlList(appDbHdl_t hdl, uint16_t *pHdlList)
{
memcpy(((appDbRec_t *) hdl)->hdlList, pHdlList, sizeof(((appDbRec_t *) hdl)->hdlList));
}
/*************************************************************************************************/
/*!
* \brief Get the device name.
*
* \param pLen Returned device name length.
*
* \return Pointer to UTF-8 string containing device name or NULL if not set.
*/
/*************************************************************************************************/
char *AppDbGetDevName(uint8_t *pLen)
{
/* if first character of name is NULL assume it is uninitialized */
if (appDb.devName[0] == 0)
{
*pLen = 0;
return NULL;
}
else
{
*pLen = appDb.devNameLen;
return appDb.devName;
}
}
/*************************************************************************************************/
/*!
* \brief Set the device name.
*
* \param len Device name length.
* \param pStr UTF-8 string containing device name.
*
* \return None.
*/
/*************************************************************************************************/
void AppDbSetDevName(uint8_t len, char *pStr)
{
/* check for maximum device length */
len = (len <= sizeof(appDb.devName)) ? len : sizeof(appDb.devName);
memcpy(appDb.devName, pStr, len);
}
/*************************************************************************************************/
/*!
* \brief Get address resolution attribute value read from a peer device.
*
* \param hdl Database record handle.
*
* \return TRUE if address resolution is supported in peer device. FALSE, otherwise.
*/
/*************************************************************************************************/
bool_t AppDbGetPeerAddrRes(appDbHdl_t hdl)
{
return ((appDbRec_t *)hdl)->peerAddrRes;
}
/*************************************************************************************************/
/*!
* \brief Set address resolution attribute value for a peer device.
*
* \param hdl Database record handle.
* \param addrRes Address resolution attribue value.
*
* \return None.
*/
/*************************************************************************************************/
void AppDbSetPeerAddrRes(appDbHdl_t hdl, uint8_t addrRes)
{
((appDbRec_t *)hdl)->peerAddrRes = addrRes;
}
/*************************************************************************************************/
/*!
* \brief Get sign counter for a peer device.
*
* \param hdl Database record handle.
*
* \return Sign counter for peer device.
*/
/*************************************************************************************************/
uint32_t AppDbGetPeerSignCounter(appDbHdl_t hdl)
{
return ((appDbRec_t *)hdl)->peerSignCounter;
}
/*************************************************************************************************/
/*!
* \brief Set sign counter for a peer device.
*
* \param hdl Database record handle.
* \param signCounter Sign counter for peer device.
*
* \return None.
*/
/*************************************************************************************************/
void AppDbSetPeerSignCounter(appDbHdl_t hdl, uint32_t signCounter)
{
((appDbRec_t *)hdl)->peerSignCounter = signCounter;
}
/*************************************************************************************************/
/*!
* \brief Get the peer device added to resolving list flag value.
*
* \param hdl Database record handle.
*
* \return TRUE if peer device's been added to resolving list. FALSE, otherwise.
*/
/*************************************************************************************************/
bool_t AppDbGetPeerAddedToRl(appDbHdl_t hdl)
{
return ((appDbRec_t *)hdl)->peerAddedToRl;
}
/*************************************************************************************************/
/*!
* \brief Set the peer device added to resolving list flag to a given value.
*
* \param hdl Database record handle.
* \param peerAddedToRl Peer device added to resolving list flag value.
*
* \return None.
*/
/*************************************************************************************************/
void AppDbSetPeerAddedToRl(appDbHdl_t hdl, bool_t peerAddedToRl)
{
((appDbRec_t *)hdl)->peerAddedToRl = peerAddedToRl;
}
/*************************************************************************************************/
/*!
* \brief Get the resolvable private address only attribute flag for a given peer device.
*
* \param hdl Database record handle.
*
* \return TRUE if RPA Only attribute is present on peer device. FALSE, otherwise.
*/
/*************************************************************************************************/
bool_t AppDbGetPeerRpao(appDbHdl_t hdl)
{
return ((appDbRec_t *)hdl)->peerRpao;
}
/*************************************************************************************************/
/*!
* \brief Set the resolvable private address only attribute flag for a given peer device.
*
* \param hdl Database record handle.
* \param peerRpao Resolvable private address only attribute flag value.
*
* \return None.
*/
/*************************************************************************************************/
void AppDbSetPeerRpao(appDbHdl_t hdl, bool_t peerRpao)
{
((appDbRec_t *)hdl)->peerRpao = peerRpao;
}
@@ -0,0 +1,483 @@
/*************************************************************************************************/
/*!
* \file
*
* \brief Application framework hardware interfaces.
*
* Copyright (c) 2011-2018 Arm Ltd.
*
* Copyright (c) 2019 Packetcraft, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/*************************************************************************************************/
#include <string.h>
#include "wsf_types.h"
#include "wsf_os.h"
#include "wsf_trace.h"
#include "util/bstream.h"
#include "app_hw.h"
#include "svc_ch.h"
/**************************************************************************************************
Macros
**************************************************************************************************/
/*! Simulated measurement RR interval, converted from bpm to 1/1024 sec units */
#define APP_HR_MEAS_SIM_RR(heartRate) (((uint16_t) 1024 * 60) / (heartRate))
/*! Number of simulated blood pressure measurements */
#define APP_NUM_BPM 3 /* measurements */
#define APP_NUM_INT_BPM 3 /* intermediate measurements */
/*! Number of simulated weight scale measurements */
#define APP_NUM_WSM 3
/*! Number of simulated temperature measurements */
#define APP_NUM_TM 6
/**************************************************************************************************
Data Types
**************************************************************************************************/
/*! Blood pressure simulated measurement structure */
typedef struct
{
uint16_t systolic; /*! Systolic pressure */
uint16_t diastolic; /*! Diastolic pressure */
uint16_t map; /*! Mean arterial pressure */
uint16_t pulseRate; /*! Pulse rate */
uint16_t measStatus; /*! Measurement status */
} appHwSimBpm_t;
/**************************************************************************************************
Local Variables
**************************************************************************************************/
/* battery level */
static uint8_t appHwBattLevel = 100;
/* heart rate */
static uint8_t appHwHeartRate = 78;
/* rr intervals */
static uint16_t appHwRrInterval[2];
/* simluated blood pressure measurements */
static const appHwSimBpm_t appHwBpm[APP_NUM_BPM] =
{
{
SFLT_TO_UINT16(140, 0), /*! Systolic pressure */
SFLT_TO_UINT16(80, 0), /*! Diastolic pressure */
SFLT_TO_UINT16(100, 0), /*! Mean arterial pressure */
SFLT_TO_UINT16(79, 0), /*! Pulse rate */
0 /*! Measurement status */
},
{
SFLT_TO_UINT16(141, 0), /*! Systolic pressure */
SFLT_TO_UINT16(81, 0), /*! Diastolic pressure */
SFLT_TO_UINT16(101, 0), /*! Mean arterial pressure */
SFLT_TO_UINT16(80, 0), /*! Pulse rate */
CH_BPM_MS_FLAG_MOVEMENT /*! Measurement status */
},
{
SFLT_TO_UINT16(142, 0), /*! Systolic pressure */
SFLT_TO_UINT16(82, 0), /*! Diastolic pressure */
SFLT_TO_UINT16(102, 0), /*! Mean arterial pressure */
SFLT_TO_UINT16(81, 0), /*! Pulse rate */
CH_BPM_MS_FLAG_CUFF_FIT_LOOSE /*! Measurement status */
}
};
/* simluated intermediate blood pressure measurements */
static const appHwSimBpm_t appHwIntBpm[APP_NUM_INT_BPM] =
{
{
SFLT_TO_UINT16(103, 0), /*! Current cuff pressure */
CH_SFLOAT_NAN, /*! Unused */
CH_SFLOAT_NAN, /*! Unused */
SFLT_TO_UINT16(76, 0), /*! Pulse rate */
CH_BPM_MS_FLAG_IRR_PULSE /*! Measurement status */
},
{
SFLT_TO_UINT16(104, 0), /*! Current cuff pressure */
CH_SFLOAT_NAN, /*! Unused */
CH_SFLOAT_NAN, /*! Unused */
SFLT_TO_UINT16(77, 0), /*! Pulse rate */
CH_BPM_MS_FLAG_MEAS_POS_ERR /*! Measurement status */
},
{
SFLT_TO_UINT16(105, 0), /*! Current cuff pressure */
CH_SFLOAT_NAN, /*! Unused */
CH_SFLOAT_NAN, /*! Unused */
SFLT_TO_UINT16(78, 0), /*! Pulse rate */
CH_BPM_MS_FLAG_CUFF_FIT_LOOSE /*! Measurement status */
}
};
/* simulated measurement indexes */
static uint8_t appHwBpmIdx = 0;
static uint8_t appHwIntBpmIdx = 0;
/* timestamp */
static appDateTime_t appHwDateTime =
{
2014, /*! Year */
1, /*! Month */
27, /*! Day */
13, /*! Hour */
10, /*! Minutes */
0 /*! Seconds */
};
/* simulated weight scale measurements in lbs */
static const uint16_t appHwWeightLbs[APP_NUM_WSM] =
{
15010,
15120,
15230
};
/* simulated weight scale measurements in kg */
static const uint16_t appHwWeightKg[APP_NUM_WSM] =
{
6823,
6873,
6923
};
/* simulated measurement index */
static uint8_t appHwWsmIdx = 0;
/* simulated temperature measurements in C */
static const uint32_t appHwTempC[APP_NUM_TM] =
{
FLT_TO_UINT32(369, -1),
FLT_TO_UINT32(370, -1),
FLT_TO_UINT32(371, -1),
FLT_TO_UINT32(372, -1),
FLT_TO_UINT32(373, -1),
FLT_TO_UINT32(374, -1)
};
/* simulated temperature measurements in F */
static const uint32_t appHwTempF[APP_NUM_TM] =
{
FLT_TO_UINT32(985, -1),
FLT_TO_UINT32(986, -1),
FLT_TO_UINT32(987, -1),
FLT_TO_UINT32(988, -1),
FLT_TO_UINT32(989, -1),
FLT_TO_UINT32(990, -1)
};
/* simulated measurement index */
static uint8_t appHwTmIdx = 0;
/* temperature units */
static uint8_t appHwTmUnits = CH_TM_FLAG_UNITS_C;
/* weight units */
static uint8_t appHwWmUnits = CH_WSM_FLAG_UNITS_LBS;
/* pulse oximeter continuous measurement */
static const appPlxCm_t appHwPlxCm =
{
0, /*! Flags */
SFLT_TO_UINT16(98, 0), /*! SpO2PR-Spot-Check - SpO2 */
SFLT_TO_UINT16(60, 0), /*! SpO2PR-Spot-Check - Pulse Rate */
SFLT_TO_UINT16(98, 0), /*! SpO2PR-Spot-Check Fast - SpO2 */
SFLT_TO_UINT16(60, 0), /*! SpO2PR-Spot-Check Fast - Pulse Rate */
SFLT_TO_UINT16(98, 0), /*! SpO2PR-Spot-Check Slow - SpO2 */
SFLT_TO_UINT16(60, 0), /*! SpO2PR-Spot-Check Slow - Pulse Rate */
0, /*! Measurement Status */
0, /*! Device and Sensor Status */
SFLT_TO_UINT16(60, 0) /*! Pulse Amplitude Index */
};
/* pulse oximeter spot check measurement */
static const appPlxScm_t appHwPlxScm =
{
0, /*! Flags */
SFLT_TO_UINT16(98, 0), /*! SpO2PR-Spot-Check - SpO2 */
SFLT_TO_UINT16(60, 0), /*! SpO2PR-Spot-Check - Pulse Rate */
{
2016, /*! Year */
7, /*! Month */
18, /*! Day */
13, /*! Hour */
10, /*! Minutes */
0 /*! Seconds */
},
0, /*! Measurement Status */
0, /*! Device and Sensor Status */
SFLT_TO_UINT16(60, 0) /*! Pulse Amplitude Index */
};
/*************************************************************************************************/
/*!
* \brief Read the battery level. The battery level value returned in pLevel is the
* percentage of remaining battery capacity (0-100%).
*
* \param pLevel Battery level return value.
*
* \return None.
*/
/*************************************************************************************************/
void AppHwBattRead(uint8_t *pLevel)
{
*pLevel = appHwBattLevel;
}
/*************************************************************************************************/
/*!
* \brief Set the battery level, for test purposes.
*
* \param level Battery level (0-100%).
*
* \return None.
*/
/*************************************************************************************************/
void AppHwBattTest(uint8_t level)
{
appHwBattLevel = level;
}
/*************************************************************************************************/
/*!
* \brief Perform a heart rate measurement. Return the heart rate along with any RR interval
* data.
*
* \param pHrm Heart rate measurement return value.
*
* \return None.
*/
/*************************************************************************************************/
void AppHwHrmRead(appHrm_t *pHrm)
{
pHrm->heartRate = appHwHeartRate;
/* calculate simulated RR intervals from heart rate */
appHwRrInterval[0] = APP_HR_MEAS_SIM_RR(appHwHeartRate);
appHwRrInterval[1] = APP_HR_MEAS_SIM_RR(appHwHeartRate);
pHrm->pRrInterval = appHwRrInterval;
pHrm->numIntervals = 2;
}
/*************************************************************************************************/
/*!
* \brief Set the heart rate, for test purposes.
*
* \param heartRate Heart rate.
*
* \return None.
*/
/*************************************************************************************************/
void AppHwHrmTest(uint8_t heartRate)
{
appHwHeartRate = heartRate;
}
/*************************************************************************************************/
/*!
* \brief Perform a blood pressure measurement. Return the measurement data.
*
* \param intermed TRUE if this is an intermediate measurement.
* \param pHrm Blood pressure measurement return value.
*
* \return None.
*/
/*************************************************************************************************/
void AppHwBpmRead(bool_t intermed, appBpm_t *pBpm)
{
appHwSimBpm_t *pMeas;
/* set pointer to simulated measurement data */
if (intermed == TRUE)
{
pMeas = (appHwSimBpm_t *) &appHwIntBpm[appHwIntBpmIdx];
/* increment index to set up for next measurement */
if (++appHwIntBpmIdx == APP_NUM_INT_BPM)
{
appHwIntBpmIdx = 0;
}
}
else
{
pMeas = (appHwSimBpm_t *) &appHwBpm[appHwBpmIdx];
/* increment index to set up for next measurement */
if (++appHwBpmIdx == APP_NUM_BPM)
{
appHwBpmIdx = 0;
}
}
/* set measurement values */
pBpm->diastolic = pMeas->diastolic;
pBpm->systolic = pMeas->systolic;
pBpm->map = pMeas->map;
pBpm->pulseRate = pMeas->pulseRate;
pBpm->measStatus = pMeas->measStatus;
/* set the timestamp */
pBpm->timestamp = appHwDateTime;
/* increment seconds field to simulate time */
appHwDateTime.sec += 2;
if (appHwDateTime.sec > 59)
{
appHwDateTime.sec = 0;
}
}
/*************************************************************************************************/
/*!
* \brief Perform a weight scale measurement. Return the measurement data.
*
* \param pWsm Weight scale measurement return value.
*
* \return None.
*/
/*************************************************************************************************/
void AppHwWsmRead(appWsm_t *pWsm)
{
/* set measurement values */
if (appHwWmUnits == CH_WSM_FLAG_UNITS_LBS)
{
pWsm->weight = appHwWeightLbs[appHwWsmIdx];
}
else
{
pWsm->weight = appHwWeightKg[appHwWsmIdx];
}
/* set the timestamp */
pWsm->timestamp = appHwDateTime;
/* increment minutes field to simulate time */
appHwDateTime.min++;
if (appHwDateTime.min > 59)
{
appHwDateTime.min = 0;
}
/* increment index to set up for next simulated measurement */
if (++appHwWsmIdx == APP_NUM_BPM)
{
appHwWsmIdx = 0;
}
}
/*************************************************************************************************/
/*!
* \brief Perform a temperature measurement. Return the measurement data.
*
* \param intermed TRUE if this is an intermediate measurement.
* \param pBpm Temperature measurement return value.
*
* \return None.
*/
/*************************************************************************************************/
void AppHwTmRead(bool_t intermed, appTm_t *pTm)
{
/* set measurement value */
if (appHwTmUnits == CH_TM_FLAG_UNITS_C)
{
pTm->temperature = appHwTempC[appHwTmIdx];
}
else
{
pTm->temperature = appHwTempF[appHwTmIdx];
}
/* increment index to set up for next measurement */
if (++appHwTmIdx == APP_NUM_TM)
{
appHwTmIdx = 0;
}
/* set the temperature type */
pTm->tempType = CH_TT_BODY;
/* set the timestamp */
pTm->timestamp = appHwDateTime;
/* increment seconds field to simulate time */
appHwDateTime.sec += 2;
if (appHwDateTime.sec > 59)
{
appHwDateTime.sec = 0;
}
}
/*************************************************************************************************/
/*!
* \brief Set the temperature measurement units.
*
* \param units CH_TM_FLAG_UNITS_C or CH_TM_FLAG_UNITS_F.
*
* \return None.
*/
/*************************************************************************************************/
void AppHwTmSetUnits(uint8_t units)
{
appHwTmUnits = units;
}
/*************************************************************************************************/
/*!
* \brief Set the weight measurement units.
*
* \param units CH_WSM_FLAG_UNITS_KG or CH_WSM_FLAG_UNITS_LBS.
*
* \return None.
*/
/*************************************************************************************************/
void AppHwWmSetUnits(uint8_t units)
{
appHwWmUnits = units;
}
/*************************************************************************************************/
/*!
* \brief Perform a pulse oximeter measurement.
*
* \param pPlxcm Pulse Oximeter measurement return value.
*
* \return None.
*/
/*************************************************************************************************/
void AppHwPlxcmRead(appPlxCm_t *pPlxcm)
{
memcpy(pPlxcm, &appHwPlxCm, sizeof(appPlxCm_t));
}
/*************************************************************************************************/
/*!
* \brief Perform a pulse oximeter spot check measurement.
*
* \param pPlxcm Pulse Oximeter spot check measurement return value.
*
* \return None.
*/
/*************************************************************************************************/
void AppHwPlxscmRead(appPlxScm_t *pPlxscm)
{
memcpy(pPlxscm, &appHwPlxScm, sizeof(appPlxScm_t));
}
@@ -0,0 +1,355 @@
/*************************************************************************************************/
/*!
* \file
*
* \brief Application framework user interface.
*
* Copyright (c) 2011-2018 Arm Ltd.
*
* Copyright (c) 2019 Packetcraft, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/*************************************************************************************************/
#include "wsf_types.h"
#include "wsf_os.h"
#include "wsf_trace.h"
#include "app_ui.h"
/**************************************************************************************************
Global Variables
**************************************************************************************************/
/*! \brief Callback struct */
appUiCback_t appUiCbackTbl;
/*************************************************************************************************/
/*!
* \brief Perform a user interface action based on the event value passed to the function.
*
* \param event User interface event value.
*
* \return None.
*/
/*************************************************************************************************/
void AppUiAction(uint8_t event)
{
switch (event)
{
case APP_UI_NONE:
/* no action */
break;
case APP_UI_RESET_CMPL:
APP_TRACE_INFO0(">>> Reset complete <<<");
break;
case APP_UI_ADV_START:
APP_TRACE_INFO0(">>> Advertising started <<<");
break;
case APP_UI_ADV_STOP:
APP_TRACE_INFO0(">>> Advertising stopped <<<");
break;
case APP_UI_SCAN_START:
APP_TRACE_INFO0(">>> Scanning started <<<");
break;
case APP_UI_SCAN_STOP:
APP_TRACE_INFO0(">>> Scanning stopped <<<");
break;
case APP_UI_SCAN_REPORT:
APP_TRACE_INFO0(">>> Scan data received from peer <<<");
break;
case APP_UI_CONN_OPEN:
APP_TRACE_INFO0(">>> Connection opened <<<");
break;
case APP_UI_CONN_CLOSE:
APP_TRACE_INFO0(">>> Connection closed <<<");
break;
case APP_UI_SEC_PAIR_CMPL:
APP_TRACE_INFO0(">>> Pairing completed successfully <<<");
break;
case APP_UI_SEC_PAIR_FAIL:
APP_TRACE_INFO0(">>> Pairing failed <<<");
break;
case APP_UI_SEC_ENCRYPT:
APP_TRACE_INFO0(">>> Connection encrypted <<<");
break;
case APP_UI_SEC_ENCRYPT_FAIL:
APP_TRACE_INFO0(">>> Encryption failed <<<");
break;
case APP_UI_PASSKEY_PROMPT:
APP_TRACE_INFO0(">>> Prompt user to enter passkey <<<");
break;
case APP_UI_ALERT_CANCEL:
APP_TRACE_INFO0(">>> Cancel a low or high alert <<<");
break;
case APP_UI_ALERT_LOW:
APP_TRACE_INFO0(">>> Low alert <<<");
break;
case APP_UI_ALERT_HIGH:
APP_TRACE_INFO0(">>> High alert <<<");
break;
case APP_UI_ADV_SET_START_IND:
APP_TRACE_INFO0(">>> Advertising sets started <<<");
break;
case APP_UI_ADV_SET_STOP_IND:
APP_TRACE_INFO0(">>> Advertising sets stopped <<<");
break;
case APP_UI_SCAN_REQ_RCVD_IND:
APP_TRACE_INFO0(">>> Scan request received <<<");
break;
case APP_UI_EXT_SCAN_START_IND:
APP_TRACE_INFO0(">>> Extended scanning started <<<");
break;
case APP_UI_EXT_SCAN_STOP_IND:
APP_TRACE_INFO0(">>> Extended scanning stopped <<<");
break;
case APP_UI_PER_ADV_SET_START_IND:
APP_TRACE_INFO0(">>> Periodic advertising set started <<<");
break;
case APP_UI_PER_ADV_SET_STOP_IND:
APP_TRACE_INFO0(">>> Periodic advertising set stopped <<<");
break;
case APP_UI_PER_ADV_SYNC_EST_IND:
APP_TRACE_INFO0(">>> Periodic advertising sync established <<<");
break;
case APP_UI_PER_ADV_SYNC_LOST_IND:
APP_TRACE_INFO0(">>> Periodic advertising sync lost <<<");
break;
default:
break;
}
if (appUiCbackTbl.actionCback)
{
(*appUiCbackTbl.actionCback)(event);
}
}
/*************************************************************************************************/
/*!
* \brief Display a passkey.
*
* \param passkey Passkey to display.
*
* \return None.
*/
/*************************************************************************************************/
void AppUiDisplayPasskey(uint32_t passkey)
{
APP_TRACE_INFO1(">>> Passkey: %d <<<", passkey);
}
/*************************************************************************************************/
/*!
* \brief Display a confirmation value.
*
* \param confirm Confirm value to display.
*
* \return None.
*/
/*************************************************************************************************/
void AppUiDisplayConfirmValue(uint32_t confirm)
{
APP_TRACE_INFO1(">>> Confirm Value: %d <<<", confirm);
}
/*************************************************************************************************/
/*!
* \brief Display an RSSI value.
*
* \param rssi Rssi value to display.
*
* \return None.
*/
/*************************************************************************************************/
void AppUiDisplayRssi(int8_t rssi)
{
APP_TRACE_INFO1(">>> RSSI: %d dBm <<<", rssi);
}
/*************************************************************************************************/
/*!
* \brief Handle a UI timer expiration event.
*
* \param pMsg Pointer to message.
*
* \return None.
*/
/*************************************************************************************************/
void appUiTimerExpired(wsfMsgHdr_t *pMsg)
{
}
/*************************************************************************************************/
/*!
* \brief Perform button press polling. This function is called to handle WSF
* message APP_BTN_POLL_IND.
*
* \return None.
*/
/*************************************************************************************************/
void appUiBtnPoll(void)
{
if (appUiCbackTbl.btnPollCback)
{
(*appUiCbackTbl.btnPollCback)();
}
}
/*************************************************************************************************/
/*!
* \brief Handle a hardware button press. This function is called to handle WSF
* event APP_BTN_DOWN_EVT.
*
* \return None.
*/
/*************************************************************************************************/
void AppUiBtnPressed(void)
{
}
/*************************************************************************************************/
/*!
* \brief Register a callback function to receive application button press events.
*
* \return None.
*
* \note Registered by application to receive button events
*/
/*************************************************************************************************/
void AppUiBtnRegister(appUiBtnCback_t btnCback)
{
appUiCbackTbl.btnCback = btnCback;
}
/*************************************************************************************************/
/*!
* \brief Register a callback function to receive action events.
*
* \return None.
*
* \note Registered by platform
*/
/*************************************************************************************************/
void AppUiActionRegister(appUiActionCback_t actionCback)
{
appUiCbackTbl.actionCback = actionCback;
}
/*************************************************************************************************/
/*!
* \brief Register a callback function to receive APP_BTN_POLL_IND events.
*
* \return None.
*
* \note Registered by platform
*/
/*************************************************************************************************/
void AppUiBtnPollRegister(appUiBtnPollCback_t btnPollCback)
{
appUiCbackTbl.btnPollCback = btnPollCback;
}
/*************************************************************************************************/
/*!
* \brief Play a sound.
*
* \param pSound Pointer to sound tone/duration array.
*
* \return None.
*/
/*************************************************************************************************/
void AppUiSoundPlay(const appUiSound_t *pSound)
{
}
/*************************************************************************************************/
/*!
* \brief Stop the sound that is currently playing.
*
* \return None.
*/
/*************************************************************************************************/
void AppUiSoundStop(void)
{
}
/*************************************************************************************************/
/*
* \fn AppUiLedStart
*
* \brief Start LED blinking.
*/
/*************************************************************************************************/
void AppUiLedStart(const appUiLed_t *pLed)
{
}
/*************************************************************************************************/
/*
* \fn AppUiLedStop
*
* \brief Stop LED blinking.
*/
/*************************************************************************************************/
void AppUiLedStop(void)
{
}
/*************************************************************************************************/
/*!
* \brief Button test function-- for test purposes only.
*
* \return None.
*/
/*************************************************************************************************/
void AppUiBtnTest(uint8_t btn)
{
if (appUiCbackTbl.btnCback)
{
(*appUiCbackTbl.btnCback)(btn);
}
}
@@ -0,0 +1,265 @@
/*************************************************************************************************/
/*!
* \file
*
* \brief User Interface - Console
*
* Copyright (c) 2017-2019 Arm Ltd.
*
* Copyright (c) 2019 Packetcraft, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/*************************************************************************************************/
#include <string.h>
#include "wsf_types.h"
#include "wsf_assert.h"
#include "util/bda.h"
#include "app_api.h"
#include "app_main.h"
#include "app_db.h"
#include "app_cfg.h"
#include "ui_api.h"
#include "wsf_trace.h"
/**************************************************************************************************
Function Prototypes
**************************************************************************************************/
/*! Action Function Prototypes */
static void uiConsoleDispSplash(const UiSplashScreen_t *pSplash);
static void uiConsoleDispMenu(const UiMenu_t *pMenu);
static void uiConsoleDispDialog(const UiDialog_t *pDialog);
static void uiConsoleProcessKey(uint8_t input);
/**************************************************************************************************
Local Variables
**************************************************************************************************/
/* Console Control Block */
struct
{
int8_t alphaNumOffset;
} uiConsoleCb;
/* Console display action functions */
UiActionTbl_t uiConsoleActionTbl =
{
uiConsoleDispSplash,
uiConsoleDispMenu,
uiConsoleDispDialog,
uiConsoleProcessKey
};
/*************************************************************************************************/
/*!
* \brief Process a key press from the user
*
* \param key User keypress.
*
* \return None
*/
/*************************************************************************************************/
static void uiConsoleProcessKey(uint8_t key)
{
UiDialog_t *pDialog;
switch(UiCb.activeScreenType)
{
case UI_SCREEN_SPLASH:
if ((key >= '0' && key <= '9') || (key == '\r'))
{
/* Jump to main menu on any key */
UiLoadMenu(UiCb.pMainMenu);
}
break;
case UI_SCREEN_MENU:
if (key >= '0' && key <= '9')
{
UiSelection(key - '0');
}
break;
case UI_SCREEN_DIALOG:
pDialog = (UiDialog_t*) UiCb.pActiveScreen;
if (pDialog->type == UI_DLG_TYPE_INPUT_SELECT)
{
if (key > '0' && key <= '9')
{
/* User pressed a number from 1 - 9 for selection */
UiSelection(key - '0');
}
else if (key == '\r')
{
/* User pressed enter on pause screen */
UiSelection(0);
}
}
else
{
if (uiConsoleCb.alphaNumOffset < pDialog->entryMaxLen)
{
/* TODO: Add backspace key? */
/* User in process of entering alpha numeric input */
pDialog->pEntry[uiConsoleCb.alphaNumOffset++] = key;
}
else
{
pDialog->pEntry[uiConsoleCb.alphaNumOffset] = '\0';
uiConsoleCb.alphaNumOffset = 0;
/* Notify callback of selection */
UiSelection(0);
/* Exit to parent */
UiLoadMenu(pDialog->base.pParentMenu);
}
}
break;
default:
break;
}
}
/*************************************************************************************************/
/*!
* \brief Display a splash screen on a Console
*
* \param pSplash Pointer to the splash screen object to display.
*
* \return None
*/
/*************************************************************************************************/
static void uiConsoleDispSplash(const UiSplashScreen_t *pSplash)
{
/* Print the splash widget identifier */
UiConsolePrintLn("{Splash}");
/* Print splash screen */
UiConsolePrint(pSplash->pAppName);
UiConsolePrint(", ");
UiConsolePrintLn(pSplash->pAppVer);
UiConsolePrintLn(pSplash->pCopyright);
UiConsoleFlush();
}
/*************************************************************************************************/
/*!
* \brief Display a menu on a Console
*
* \param pMenu Pointer to the menu object to display.
*
* \return None
*/
/*************************************************************************************************/
static void uiConsoleDispMenu(const UiMenu_t *pMenu)
{
int8_t i;
char ch[2];
/* Print the menu widget identifier */
UiConsolePrint("\r\n");
UiConsolePrintLn("{Menu}");
/* Print the title to the Console */
UiConsolePrintLn(pMenu->pTitle);
/* Print the menu items */
for (i = 0; i < pMenu->numItems; i++)
{
UiConsolePrint(" ");
ch[0] = '1' + i;
ch[1] = '\0';
UiConsolePrint(ch);
UiConsolePrint(". ");
UiConsolePrintLn(pMenu->pItems[i]);
}
UiConsolePrint("\r\n");
UiConsolePrint("Choice? ");
UiConsoleFlush();
}
/*************************************************************************************************/
/*!
* \brief Display a dialog on a Console
*
* \param pDialog Pointer to the dialog object to display.
*
* \return None
*/
/*************************************************************************************************/
static void uiConsoleDispDialog(const UiDialog_t *pDialog)
{
int8_t i;
char ch[2];
/* Print the dialog widget identifier */
UiConsolePrint("\r\n");
UiConsolePrintLn("{Dialog}");
/* Print the title to the Console */
UiConsolePrintLn(pDialog->pTitle);
/* Print the message to the Console */
UiConsolePrintLn(pDialog->pMsg);
if (pDialog->type == UI_DLG_TYPE_INPUT_SELECT)
{
if (pDialog->numSelectItems == 0)
{
UiConsolePrintLn("ENTER to continue");
}
else
{
/* Print the dialog items */
for (i = 0; i < pDialog->numSelectItems; i++)
{
UiConsolePrint(" ");
ch[0] = '1' + i;
ch[1] = '\0';
UiConsolePrint(ch);
UiConsolePrint(". ");
UiConsolePrintLn(pDialog->pSelectItems[i]);
}
}
}
else
{
/* Print prompt */
UiConsolePrint("> ");
UiConsolePrint(pDialog->pEntry);
}
UiConsoleFlush();
}
/*************************************************************************************************/
/*!
* \brief Initialize the Console User Interface
*
* \return None
*/
/*************************************************************************************************/
void UiConsoleInit(void)
{
uiConsoleCb.alphaNumOffset = 0;
UiRegisterDisplay(uiConsoleActionTbl, UI_DISPLAY_CONSOLE);
}
@@ -0,0 +1,744 @@
/*************************************************************************************************/
/*!
* \file
*
* \brief User Interface - LCD
*
* Copyright (c) 2017-2019 Arm Ltd.
*
* Copyright (c) 2019 Packetcraft, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/*************************************************************************************************/
#include <string.h>
#include "wsf_types.h"
#include "wsf_assert.h"
#include "util/bda.h"
#include "app_api.h"
#include "app_main.h"
#include "app_db.h"
#include "app_cfg.h"
#include "ui_api.h"
/**************************************************************************************************
Macros
**************************************************************************************************/
/* Get these values from driver? */
#define LCD_NUM_LINES 4
#define LCD_LINE_LEN 20
#define LCD_SEL_COL_WIDTH 2
/* Map button press to LCD actions */
#define LCD_KEYPRESS_UP UI_INPUT_BTN_UP
#define LCD_KEYPRESS_DOWN UI_INPUT_BTN_DOWN
#define LCD_KEYPRESS_SELECT UI_INPUT_BTN_SELECT
/* Scroll timeout in ms */
#define LCD_SCROLL_LONG_TO 1000
#define LCD_SCROLL_SHORT_TO 250
#define LCD_PROMPT_STR ">>"
#define LCD_RO_PROMPT_STR "*>"
/**************************************************************************************************
Function Prototypes
**************************************************************************************************/
/*! LCD display action function prototypes */
static void uiLcdDispSplash(const UiSplashScreen_t *pSplash);
static void uiLcdDispMenu(const UiMenu_t *pMenu);
static void uiLcdDispDialog(const UiDialog_t *pDialog);
static void uiLcdProcessKey(uint8_t key);
/*! Process Key action functions */
static void uiLcdProcessSplashKey(uint8_t key);
static void uiLcdProcessMenuKey(uint8_t key);
static void uiLcdProcessDialogKey(uint8_t key);
/**************************************************************************************************
Local Variables
**************************************************************************************************/
/* LCD Control Block */
struct
{
int8_t scrollOffset;
int8_t alphaNumOffset;
} uiLcdCb;
/* LCD Process Key action functions */
UiKeyPress_t uiLcdProcessKeyTbl[] =
{
uiLcdProcessSplashKey,
uiLcdProcessMenuKey,
uiLcdProcessDialogKey
};
/* LCD display action functions */
static UiActionTbl_t uiLcdActionTbl =
{
uiLcdDispSplash,
uiLcdDispMenu,
uiLcdDispDialog,
uiLcdProcessKey
};
/*************************************************************************************************/
/*!
* \brief Process a key press from the user
*
* \param key User keypress.
*
* \return None
*/
/*************************************************************************************************/
static void uiLcdProcessKey(uint8_t key)
{
uiLcdProcessKeyTbl[UiCb.activeScreenType](key);
}
/*************************************************************************************************/
/*!
* \brief Process a key press from the user - Splash screens
*
* \param key User keypress.
*
* \return None
*/
/*************************************************************************************************/
static void uiLcdProcessSplashKey(uint8_t key)
{
switch(key)
{
case LCD_KEYPRESS_UP:
case LCD_KEYPRESS_DOWN:
case LCD_KEYPRESS_SELECT:
default:
/* Proceed to main menu */
UiLoadMenu(UiCb.pMainMenu);
break;
}
}
/*************************************************************************************************/
/*!
* \brief Process a key press from the user - Menu screens
*
* \param key User keypress.
*
* \return None
*/
/*************************************************************************************************/
static void uiLcdProcessMenuKey(uint8_t key)
{
UiMenu_t *pMenu = (UiMenu_t*) UiCb.pActiveScreen;
switch(key)
{
case LCD_KEYPRESS_UP:
/* Move the highlight up or wrap down if at top */
uiLcdCb.scrollOffset = 0;
pMenu->highlight = (pMenu->highlight > 0) ? (pMenu->highlight - 1) : (pMenu->numItems - 1);
UiRefresh();
break;
case LCD_KEYPRESS_DOWN:
/* Move the highlight down or wrap up if at bottom */
uiLcdCb.scrollOffset = 0;
pMenu->highlight = (pMenu->highlight + 1) % (pMenu->numItems);
UiRefresh();
break;
case LCD_KEYPRESS_SELECT:
/* Select the highlighted item */
uiLcdCb.scrollOffset = 0;
UiSelection(pMenu->highlight + 1);
break;
default:
break;
}
}
/*************************************************************************************************/
/*!
* \brief Select the next character depending on the type of dialog and if the up or down button
* was pressed.
*
* \param pDialog Pointer to the dialog
* \param up TRUE if up key is pressed, else the down key
*
* \return The next character to display
*/
/*************************************************************************************************/
static char uiLcdAlphaNumNextChar(const UiDialog_t *pDialog, bool_t up)
{
uint8_t offset = uiLcdCb.alphaNumOffset;
char ch = pDialog->pEntry[offset];
if (pDialog->type == UI_DLG_TYPE_INPUT_NUM)
{
/* Cycle through 0-9 */
if (up)
{
ch = (ch < '9') ? ((ch >= '0') ? ch + 1 : '0'): '0';
}
else
{
ch = (ch > '0') ? ((ch <= '9') ? ch - 1 : '9') : '9';
}
}
else
{
/* Cycle through space, 0-9, underscore, A-Z */
if (up)
{
ch = (ch == ' ') ? '0' : (ch == '9') ? '_' : (ch == '_') ? 'A' : (ch == 'Z') ? ' ' : (ch < ' ' || ch > '_') ? ' ' : ch + 1;
}
else
{
ch = (ch == ' ') ? 'Z' : (ch == 'A') ? '_' : (ch == '_') ? '9' : (ch == '0') ? ' ' : (ch < ' ' || ch > '_') ? 'Z' :ch - 1;
}
}
return ch;
}
/*************************************************************************************************/
/*!
* \brief Process a key press from the user - Numeric or alpha-numeric dialog screens
*
* \param key User keypress.
*
* \return None
*/
/*************************************************************************************************/
static void uiLcdProcessDialogAlphaNum(uint8_t key)
{
UiDialog_t *pDialog = (UiDialog_t*) UiCb.pActiveScreen;
uint8_t offset = uiLcdCb.alphaNumOffset;
switch(key)
{
case LCD_KEYPRESS_UP:
if (offset < pDialog->entryMaxLen)
{
/* offset in range, change character */
pDialog->pEntry[offset] = uiLcdAlphaNumNextChar(pDialog, TRUE);
}
else if (pDialog->highlight > 0)
{
/* offset out of range, move selection */
pDialog->highlight--;
}
break;
case LCD_KEYPRESS_DOWN:
if (offset < pDialog->entryMaxLen)
{
/* offset in range, change character */
pDialog->pEntry[offset] = uiLcdAlphaNumNextChar(pDialog, FALSE);
}
else if (pDialog->highlight < 1)
{
/* offset out of range, move selection */
pDialog->highlight++;
}
break;
case LCD_KEYPRESS_SELECT:
if (offset < pDialog->entryMaxLen)
{
/* offset in range, move lower carrot to next character */
uiLcdCb.alphaNumOffset++;
}
else
{
/* offset out of range, select the highlight */
if (pDialog->highlight == 0)
{
uiLcdCb.alphaNumOffset = 0;
}
else
{
/* User is done */
uiLcdCb.alphaNumOffset = 0;
pDialog->highlight = 0;
/* Notify callback of selection */
UiSelection(0);
/* Exit to parent */
UiLoadMenu(pDialog->base.pParentMenu);
}
}
break;
}
UiRefresh();
}
/*************************************************************************************************/
/*!
* \brief Process a key press from the user - Select Dialog Screens
*
* \param key User keypress.
*
* \return None
*/
/*************************************************************************************************/
static void uiLcdProcessDialogSelect(uint8_t key)
{
UiDialog_t *pDialog = (UiDialog_t*) UiCb.pActiveScreen;
switch(key)
{
case LCD_KEYPRESS_UP:
/* Move the highlight up */
if (pDialog->highlight > 0)
{
uiLcdCb.scrollOffset = 0;
pDialog->highlight--;
UiRefresh();
}
break;
case LCD_KEYPRESS_DOWN:
/* Move the highlight down */
if (pDialog->highlight < pDialog->numSelectItems - 1)
{
uiLcdCb.scrollOffset = 0;
pDialog->highlight++;
UiRefresh();
}
break;
case LCD_KEYPRESS_SELECT:
/* Select the highlighted item */
uiLcdCb.scrollOffset = 0;
UiSelection(pDialog->highlight + 1);
break;
default:
break;
}
}
/*************************************************************************************************/
/*!
* \brief Process a key press from the user - Dialog Screens
*
* \param key User keypress.
*
* \return None
*/
/*************************************************************************************************/
static void uiLcdProcessDialogKey(uint8_t key)
{
UiDialog_t *pDialog = (UiDialog_t*) UiCb.pActiveScreen;
/* process input based on dialog type (select vs alpha numeric input) */
switch(pDialog->type)
{
case UI_DLG_TYPE_INPUT_NUM:
case UI_DLG_TYPE_INPUT_ALPHANUM:
uiLcdProcessDialogAlphaNum(key);
break;
case UI_DLG_TYPE_INPUT_SELECT:
uiLcdProcessDialogSelect(key);
break;
default:
break;
}
}
/*************************************************************************************************/
/*!
* \brief Process a timer tick from the UI layer
*
* \return None
*/
/*************************************************************************************************/
static void uiLcdScrollCallback(void)
{
if (UiCb.activeScreenType == UI_SCREEN_MENU)
{
UiMenu_t *pMenu = (UiMenu_t *) UiCb.pActiveScreen;
/* scroll the higlighted menu item if necessary */
if (++uiLcdCb.scrollOffset > (int8_t) (strlen(pMenu->pItems[pMenu->highlight]) - (LCD_LINE_LEN - LCD_SEL_COL_WIDTH)))
{
uiLcdCb.scrollOffset = 0;
}
UiRefresh();
}
else if (UiCb.activeScreenType == UI_SCREEN_DIALOG)
{
UiDialog_t *pDialog = (UiDialog_t *) UiCb.pActiveScreen;
/* Scroll the highlighted select items if necessary */
if (++uiLcdCb.scrollOffset > (int8_t) (strlen(pDialog->pSelectItems[pDialog->highlight]) - (LCD_LINE_LEN - LCD_SEL_COL_WIDTH)))
{
uiLcdCb.scrollOffset = 0;
}
UiRefresh();
}
}
/*************************************************************************************************/
/*!
* \brief Display a splash screen on an LCD
*
* \param pSplash Pointer to the splash screen object to display.
*
* \return None
*/
/*************************************************************************************************/
static void uiLcdDispSplash(const UiSplashScreen_t *pSplash)
{
/* Display the splash screen */
UiLcdWriteLine(0, pSplash->pAppName);
UiLcdWriteLine(1, pSplash->pAppVer);
UiLcdWriteLine(2, "");
UiLcdWriteLine(3, pSplash->pCopyright);
UiLcdSetDataPrepared();
UiLcdFlush();
}
/*************************************************************************************************/
/*!
* \brief Display a title on an LCD with brackets around it
*
* \param title Title to display.
*
* \return None
*/
/*************************************************************************************************/
static void uiLcdDispTitle(const char *title)
{
char line[LCD_LINE_LEN + 1];
memset(line, '\0', LCD_LINE_LEN+1);
/* Put brackets around the title */
line[0] = '[';
strncpy(line+1, title, LCD_LINE_LEN-2);
strcat(line, "]");
/* Print the title to the LCD */
UiLcdWriteLine(0, line);
}
/*************************************************************************************************/
/*!
* \brief Calculate first and last item displayed on the screen
*
* \param pStart Pointer hold the first item ID
* \param pEnd Pointer hold the last item ID (one greater than last)
* \param numLines Number of lines to display items on the screen
* \param highlight ID of the highlighted item
* \param numItems Total number of items
*
* \return None
*/
/*************************************************************************************************/
static void appCalcFirstAndLastItem(int8_t *pStart, int8_t *pEnd, uint8_t numLines,
uint8_t highlight, uint8_t numItems)
{
int8_t start, end;
/* Assume the highligh will be in the center */
start = highlight - (numLines - 1) / 2;
/* If centering the highlight puts the start below zero, start at the beginning */
if (start < 0)
{
start = 0;
}
/* Assume the end is the start plus the number of screen lines */
end = start + numLines;
/* Check to see if the end overflows */
if (end > numItems)
{
end = numItems;
/* The last item should be as close to the bottom of the screen as possible */
/* Adjust the start to accomodate this */
start = end - numLines;
if (start < 0)
{
start = 0;
}
}
/* Save the result */
*pStart = start;
*pEnd = end;
}
/*************************************************************************************************/
/*!
* \brief process the scrolling of long highlight items
*
* \param pText Pointer to the highlighted text.
* \param offset The offset of the scroll.
*
* \return None
*/
/*************************************************************************************************/
static bool_t uiLcdProcessScroll(const char *pText, uint8_t offset)
{
bool_t scrolling = FALSE;
uint8_t len = (uint8_t) strlen(pText);
/* Check if the item length is grater than the LCD (minus space for the select carrot) */
if (len > LCD_LINE_LEN - LCD_SEL_COL_WIDTH)
{
uint16_t timeout = LCD_SCROLL_SHORT_TO;
scrolling = TRUE;
/* Delay longer at the beginning and the end of the scroll */
if ((offset == 0) || (offset == (len - (LCD_LINE_LEN - LCD_SEL_COL_WIDTH))))
{
timeout = LCD_SCROLL_LONG_TO;
}
/* Start the tick timer */
UiScrollTimerStart(uiLcdScrollCallback, timeout);
}
return scrolling;
}
/*************************************************************************************************/
/*!
* \brief Display a menu on an LCD
*
* \param pMenu Pointer to the menu object to display.
*
* \return None
*/
/*************************************************************************************************/
static void uiLcdDispMenu(const UiMenu_t *pMenu)
{
int8_t i, start, end, pos = 1;
bool_t scrolling = FALSE;
WSF_ASSERT(pMenu);
WSF_ASSERT(pMenu->pTitle);
/* Print the title to the LCD */
uiLcdDispTitle(pMenu->pTitle);
/* Calculate first and last item displayed on the screen */
appCalcFirstAndLastItem(&start, &end, LCD_NUM_LINES-1, pMenu->highlight, pMenu->numItems);
/* Display menu items */
for (i = start; i < end; i++)
{
int8_t offset = 0;
char line[LCD_LINE_LEN+1];
line[0] = ' ';
line[1] = ' ';
if (i == pMenu->highlight)
{
/* Add the highlight to the line */
if (pMenu->readOnlyMask & (1<<i))
{
strncpy(line, LCD_RO_PROMPT_STR, sizeof(line));
}
else
{
strncpy(line, LCD_PROMPT_STR, sizeof(line));
}
/* Check if we need to scroll through a long item */
offset = uiLcdCb.scrollOffset;
scrolling = uiLcdProcessScroll(pMenu->pItems[i], offset);
}
strncpy(line+LCD_SEL_COL_WIDTH, pMenu->pItems[i] + offset, LCD_LINE_LEN-LCD_SEL_COL_WIDTH);
UiLcdWriteLine(pos++, line);
}
/* Clear unused lines */
for (i = pos; i < LCD_NUM_LINES; i++)
{
UiLcdWriteLine(i, "");
}
if (!scrolling)
{
UiScrollTimerStop();
}
UiLcdSetDataPrepared();
UiLcdFlush();
}
/*************************************************************************************************/
/*!
* \brief Complete display of dialog on an LCD - Dialog type alphanumeric or numeric
*
* \param pDialog Pointer to the dialog object to display.
*
* \return None
*/
/*************************************************************************************************/
static void uiLcdDispDialogAlphaNum(const UiDialog_t *pDialog)
{
char line[LCD_LINE_LEN+1];
char cmdLine[LCD_LINE_LEN+1];
line[0] = ' ';
line[1] = ' ';
WSF_ASSERT(pDialog->pEntry);
strncpy(line + LCD_SEL_COL_WIDTH, pDialog->pEntry, LCD_LINE_LEN - LCD_SEL_COL_WIDTH);
memset(cmdLine, ' ', LCD_LINE_LEN);
cmdLine[LCD_LINE_LEN] = '\0';
if (uiLcdCb.alphaNumOffset < pDialog->entryMaxLen)
{
/* Print a carrot under the active character */
cmdLine[uiLcdCb.alphaNumOffset + LCD_SEL_COL_WIDTH] = '^';
}
else
{
/* After the user presses select on the last character, add a 'Done' entry item */
strcpy(cmdLine, " Done");
if (pDialog->highlight == 0)
{
strncpy(line, LCD_PROMPT_STR, sizeof(line));
}
else
{
strncpy(cmdLine, LCD_PROMPT_STR, sizeof(line));
}
}
/* Print to the LCD */
UiLcdWriteLine(2, line);
UiLcdWriteLine(3, cmdLine);
}
/*************************************************************************************************/
/*!
* \brief Complete display of dialog on an LCD - Dialog type select
*
* \param pDialog Pointer to the dialog object to display.
*
* \return None
*/
/*************************************************************************************************/
static void uiLcdDispDialogSelect(const UiDialog_t *pDialog)
{
int8_t i, start, end, pos = 2;
bool_t scrolling = FALSE;
/* Calculate first and last item displayed on the screen */
appCalcFirstAndLastItem(&start, &end, LCD_NUM_LINES-2, pDialog->highlight, pDialog->numSelectItems);
/* Display dialog items */
for (i = start; i < end; i++)
{
int8_t offset = 0;
char line[LCD_LINE_LEN+1];
line[0] = ' ';
line[1] = ' ';
if (i == pDialog->highlight)
{
/* Add the highlight to the line */
strncpy(line, LCD_PROMPT_STR, sizeof(line));
/* Check if we need to scroll through a long item */
offset = uiLcdCb.scrollOffset;
scrolling = uiLcdProcessScroll(pDialog->pSelectItems[i], offset);
}
strncpy(line+LCD_SEL_COL_WIDTH, pDialog->pSelectItems[i] + offset, LCD_LINE_LEN - LCD_SEL_COL_WIDTH);
UiLcdWriteLine(pos++, line);
}
if (!scrolling)
{
UiScrollTimerStop();
}
/* Clear unused lines */
for (i=pos; i<LCD_NUM_LINES; i++)
{
UiLcdWriteLine(i, "");
}
}
/*************************************************************************************************/
/*!
* \brief Display a dialog on an LCD
*
* \param pDialog Pointer to the dialog object to display.
*
* \return None
*/
/*************************************************************************************************/
static void uiLcdDispDialog(const UiDialog_t *pDialog)
{
/* Print the title to the LCD */
uiLcdDispTitle(pDialog->pTitle);
/* Print the message to the LCD */
UiLcdWriteLine(1, pDialog->pMsg);
if (pDialog->type == UI_DLG_TYPE_INPUT_SELECT)
{
uiLcdDispDialogSelect(pDialog);
}
else
{
uiLcdDispDialogAlphaNum(pDialog);
}
UiLcdSetDataPrepared();
UiLcdFlush();
}
/*************************************************************************************************/
/*!
* \brief Initialize the LCD User Interface
*
* \return None
*/
/*************************************************************************************************/
void UiLcdInit(void)
{
UiRegisterDisplay(uiLcdActionTbl, UI_DISPLAY_LCD);
}
@@ -0,0 +1,113 @@
/*************************************************************************************************/
/*!
* \file
*
* \brief Interface between UI and platform drivers.
*
* Copyright (c) 2016-2019 Arm Ltd.
*
* Copyright (c) 2019 Packetcraft, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/*************************************************************************************************/
#include <string.h>
#include "wsf_types.h"
#include "wsf_trace.h"
#include "wsf_msg.h"
#include "wsf_bufio.h"
/**************************************************************************************************
External Functions
**************************************************************************************************/
/*************************************************************************************************/
/*!
* \brief Print a string to the console.
*
* \param pLine String with text to print
*
* \return None
*/
/*************************************************************************************************/
void UiConsolePrint(const char *pLine)
{
// WsfBufIoWrite((const uint8_t *) pLine, strlen(pLine));
}
/*************************************************************************************************/
/*!
* \brief Print a string to the console followed by a new line.
*
* \param pLine String with text to print
*
* \return None
*/
/*************************************************************************************************/
void UiConsolePrintLn(const char *pLine)
{
UiConsolePrint(pLine);
UiConsolePrint("\r\n");
}
/*************************************************************************************************/
/*!
* \brief Flush the contents of the Console buffer to the display.
*
* \return None
*/
/*************************************************************************************************/
void UiConsoleFlush()
{
/* Take no action. */
}
/*************************************************************************************************/
/*!
* \brief Write a line on the LCD
*
* \param line Line number
* \param pLine String with text to write to LCD.
*
* \return None
*/
/*************************************************************************************************/
void UiLcdWriteLine(uint8_t line, const char *pLine)
{
/* Take no action. */
}
/*************************************************************************************************/
/*!
* \brief Flush the contents of the LCD buffer to the display
*
* \return None
*/
/*************************************************************************************************/
void UiLcdFlush(void)
{
/* Take no action. */
}
/*************************************************************************************************/
/*!
* \brief Set flag UiDataPrepared to TRUE
*
* \return None
*/
/*************************************************************************************************/
void UiLcdSetDataPrepared(void)
{
/* Take no action. */
}
@@ -0,0 +1,209 @@
/*************************************************************************************************/
/*!
* \file
*
* \brief User Interface main module - WSF timer implementation.
*
* Copyright (c) 2018 - 2019 Arm Ltd.
*
* Copyright (c) 2019 Packetcraft, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/*************************************************************************************************/
#include <string.h>
#include "wsf_types.h"
#include "wsf_trace.h"
#include "wsf_timer.h"
#include "wsf_assert.h"
#include "ui_api.h"
/**************************************************************************************************
Macros
**************************************************************************************************/
/*! Max number of active timers */
#define UI_TIMER_WSF_MAX_TIMERS 3
/*! Unused timer ID */
#define UI_TIMER_WSF_UNUSED 0
/**************************************************************************************************
Local Variables
**************************************************************************************************/
/* UI Timers */
static wsfTimer_t uiTimerList[UI_TIMER_WSF_MAX_TIMERS];
/* UI timer task handler ID */
wsfHandlerId_t iuTimerWsfHandlerId;
/*************************************************************************************************/
/*!
* \brief Add a WSF timer for a UI event.
*
* \param event Event to pass to UiProcEvent on timer expiration.
*
* \return WSF timer or NULL if all timers are in use.
*/
/*************************************************************************************************/
static wsfTimer_t *uiTimerAddTimer(uint8_t event)
{
wsfTimer_t *pTimer = uiTimerList;
int8_t i;
for (i = 0; i < UI_TIMER_WSF_MAX_TIMERS; i++, pTimer++)
{
if (pTimer->msg.event == UI_TIMER_WSF_UNUSED)
{
pTimer->msg.event = event;
pTimer->handlerId = iuTimerWsfHandlerId;
return pTimer;
}
}
/* If ASSERT happens, increase UI_TIMER_WSF_MAX_TIMERS to needed number of active timers. */
WSF_ASSERT(0);
return NULL;
}
/*************************************************************************************************/
/*!
* \brief Get the WSF timer for a UI event.
*
* \param event Timer event.
* \param addTimer TRUE to add a timer if the event doesn't exist.
*
* \return WSF timer or NULL if timer for event does not exist.
*/
/*************************************************************************************************/
static wsfTimer_t *uiTimerGetTimerByEvent(uint8_t event, bool_t addTimer)
{
wsfTimer_t *pTimer = uiTimerList;
int8_t i;
for (i = 0; i < UI_TIMER_WSF_MAX_TIMERS; i++, pTimer++)
{
if (pTimer->msg.event == event)
{
return pTimer;
}
}
if (addTimer)
{
return uiTimerAddTimer(event);
}
return NULL;
}
/*************************************************************************************************/
/*!
* \brief Application handler init function - for internal test.
*
* \param handlerID WSF handler ID.
*
* \return None.
*/
/*************************************************************************************************/
void uiTimerWsfHandlerInit(wsfHandlerId_t handlerId)
{
iuTimerWsfHandlerId = handlerId;
}
/*************************************************************************************************/
/*!
* \brief WSF event handler for application - For internal test.
*
* \param event WSF event mask.
* \param pMsg WSF message.
*
* \return None.
*/
/*************************************************************************************************/
static void uiTimerWsfHandler(wsfEventMask_t event, wsfMsgHdr_t *pMsg)
{
uint8_t timerEvent = pMsg->event;
wsfTimer_t *pTimer = uiTimerGetTimerByEvent(timerEvent, FALSE);
if (pTimer)
{
/* Free the timer */
pTimer->msg.event = UI_TIMER_WSF_UNUSED;
/* Process the timer event */
UiProcEvent(timerEvent);
}
}
/*************************************************************************************************/
/*!
* \brief Start a UI timer.
*
* \param event Event to pass to UiProcEvent on timer expiration.
* \param ms Time in milliseconds until timer expiration.
*
* \return None.
*/
/*************************************************************************************************/
void UiTimerStart(uint8_t event, uint32_t ms)
{
wsfTimer_t *pTimer = uiTimerGetTimerByEvent(event, TRUE);
if (pTimer)
{
WsfTimerStartMs(pTimer, ms);
}
}
/*************************************************************************************************/
/*!
* \brief Stop a UI timer.
*
* \param event Event to pass to UiProcEvent on timer expiration.
*
* \return None.
*/
/*************************************************************************************************/
void UiTimerStop(uint8_t event)
{
wsfTimer_t *pTimer = uiTimerGetTimerByEvent(event, FALSE);
if (pTimer)
{
/* Stop and free the timer */
WsfTimerStop(pTimer);
pTimer->msg.event = UI_TIMER_WSF_UNUSED;
}
}
/*************************************************************************************************/
/*!
* \brief Initialize the UI Timer subsystem.
*
* \return None.
*/
/*************************************************************************************************/
void UiTimerInit(void)
{
/* Clear timers */
memset(uiTimerList, 0, sizeof(uiTimerList));
/* Start timer handler */
iuTimerWsfHandlerId = WsfOsSetNextHandler(uiTimerWsfHandler);
uiTimerWsfHandlerInit(iuTimerWsfHandlerId);
}