/** **************************************************************************************** * * @file prf_otas.c * * @brief OTA Service - Server Role Implementation. * * < If want to modify it, recommend to copy the file to 'user porject'/src > **************************************************************************************** */ #if (PRF_OTAS) /* * INCLUDE FILES **************************************************************************************** */ #include "prf.h" #include "prf_otas.h" #if (DBG_OTAS) #include "dbg.h" #define DEBUG(format, ...) debug("<%s,%d>" format "\r\n", __MODULE__, __LINE__, ##__VA_ARGS__) #else #define DEBUG(format, ...) #define debugHex(dat, len) #endif /* * DEFINITIONS **************************************************************************************** */ /// Max length of received once #if !defined(OTA_RXD_MAX_LEN) #define OTA_RXD_MAX_LEN (20) #endif /// Max number of notify/indicate pkt #if !defined(OTA_NB_PKT_MAX) #define OTA_NB_PKT_MAX (5) #endif /// Version String for OTA_IDX_READ_VAL Read #define OTA_VERS_STR ("bleOTA v1.0") #define OTA_VERS_STR_LEN (sizeof(OTA_VERS_STR) - 1) /// Macro for Client Config value operation #define OTA_NTF_CFG_GET(conidx) \ ((otas_env.ntf_bits >> (conidx*2)) & (PRF_CLI_START_NTF | PRF_CLI_START_IND)) #define OTA_NTF_CFG_CLR(conidx) \ otas_env.ntf_bits &= ~((PRF_CLI_START_NTF | PRF_CLI_START_IND) << (conidx*2)) #define OTA_NTF_CFG_SET(conidx, conf) \ otas_env.ntf_bits = (otas_env.ntf_bits & ~((PRF_CLI_START_NTF | PRF_CLI_START_IND) << (conidx*2))) | ((conf) << (conidx*2)) /** **************************************************************************************** * @section ENVIRONMENT DEFINITION **************************************************************************************** */ /// Server Environment Variable typedef struct otas_env_tag { // Service Start Handle uint16_t start_hdl; // Client Config of peer devices - each 2Bits(NTF & IND), so max_peer=8. uint16_t ntf_bits; // Number of notify pkt uint8_t nb_pkt; } otas_env_t; /// Global Variable Declarations otas_env_t otas_env; /** **************************************************************************************** * @section ATTRIBUTES DEFINITION **************************************************************************************** */ /// Attributes Index enum ota_att_idx { // Service Declaration, *MUST* Start at 0 OTA_IDX_SVC, // Serial TXD Char. OTA_IDX_TXD_CHAR, OTA_IDX_TXD_VAL, OTA_IDX_TXD_NTF_CFG, // Serial RXD Char. OTA_IDX_RXD_CHAR, OTA_IDX_RXD_VAL, //5 #if (OTA_READ_SUP) // Serial READ Char. OTA_IDX_READ_CHAR, OTA_IDX_READ_VAL, #endif // Max Index, *NOTE* Minus 1(Svc Decl) is .nb_att OTA_IDX_NB, }; #if (OTA_UUID_128) /// Characteristic Base UUID128 (User Customize) #define OTA_ATT_UUID128(uuid) { 0xFC, 0x12, 0x41, 0x2A, 0xD2, 0xDE, 0x7E, 0x1D, \ 0x4D, 0x47, 0xA2, 0x09, (uuid) & 0xFF, (uuid >> 8) & 0xFF, 0x00, 0x00 } /// Serial Service UUID128 const uint8_t ota_svc_uuid[] = OTA_ATT_UUID128(0xFF50); /// Serial Notify UUID128 const uint8_t ota_char_txd_notify[] = OTA_ATT_UUID128(0xFF51); /// Serial Write Command UUID128 const uint8_t ota_char_rxd_write[] = OTA_ATT_UUID128(0xFF52); /// Serial Read Command UUID128 const uint8_t ota_char_val_read[] = OTA_ATT_UUID128(0xFF53); /// Attributes Description const att_decl_t ota_atts[] = { // Serial Notify Char. Declaration and Value and Client Char. Configuration Descriptor ATT_ELMT_DECL_CHAR( OTA_IDX_TXD_CHAR ), ATT_ELMT128( OTA_IDX_TXD_VAL, ota_char_txd_notify, PROP_NTF | PROP_IND, 0 ), ATT_ELMT_DESC_CLI_CHAR_CFG( OTA_IDX_TXD_NTF_CFG ), // Serial Write Command Char. Declaration and Value ATT_ELMT_DECL_CHAR( OTA_IDX_RXD_CHAR ), ATT_ELMT128( OTA_IDX_RXD_VAL, ota_char_rxd_write, PROP_WC | PROP_WR, OTA_RXD_MAX_LEN ), #if (OTA_READ_SUP) // Serial Read Command Char. Declaration and Value ATT_ELMT_DECL_CHAR( OTA_IDX_READ_CHAR ), ATT_ELMT128( OTA_IDX_READ_VAL, ota_char_val_read, PROP_RD, 0 ), #endif //(OTA_READ_SUP) }; /// Service Description const struct svc_decl ota_svc_db = { .uuid128 = ota_svc_uuid, .info = SVC_UUID(128), .atts = ota_atts, .nb_att = OTA_IDX_NB - 1, }; #else //(OTAS_UUID16) /// Serial Service UUID #define OTA_SVC_UUID ATT_UUID16(0xFF50) /// Serial Notify UUID #define OTA_CHAR_TXD_NOTIFY ATT_UUID16(0xFF51) /// Serial Write Command UUID #define OTA_CHAR_RXD_WRITE ATT_UUID16(0xFF52) /// Serial Read Command UUID #define OTA_CHAR_VAL_READ ATT_UUID16(0xFF53) /// Attributes Description const att_decl_t ota_atts[] = { // Serial Notify Char. Declaration and Value and CCC Descriptor ATT_ELMT_DECL_CHAR( OTA_IDX_TXD_CHAR ), ATT_ELMT( OTA_IDX_TXD_VAL, OTA_CHAR_TXD_NOTIFY, PROP_NTF | PROP_IND, 0 ), ATT_ELMT_DESC_CLI_CHAR_CFG( OTA_IDX_TXD_NTF_CFG ), // Serial Write Command Char. Declaration and Value ATT_ELMT_DECL_CHAR( OTA_IDX_RXD_CHAR ), ATT_ELMT( OTA_IDX_RXD_VAL, OTA_CHAR_RXD_WRITE, PROP_WC | PROP_WR, OTA_RXD_MAX_LEN ), #if (OTA_READ_SUP) // Serial Read Command Char. Declaration and Value ATT_ELMT_DECL_CHAR( OTA_IDX_READ_CHAR ), ATT_ELMT( OTA_IDX_READ_VAL, OTA_CHAR_VAL_READ, PROP_RD, 0 ), #endif //(OTA_READ_SUP) }; /// Service Description const struct svc_decl ota_svc_db = { .uuid = OTA_SVC_UUID, .info = SVC_UUID(16), .atts = ota_atts, .nb_att = OTA_IDX_NB - 1, }; #endif /* * FUNCTION DECLARATIONS **************************************************************************************** */ /** **************************************************************************************** * @section SVC FUNCTIONS **************************************************************************************** */ /// Retrieve attribute handle from index (@see ota_att_idx) static uint16_t otas_get_att_handle(uint8_t att_idx) { ASSERT_ERR(att_idx < OTA_IDX_NB); return att_idx + otas_env.start_hdl; } /// Retrieve attribute index form handle static uint8_t otas_get_att_idx(uint16_t handle) { ASSERT_ERR((handle >= otas_env.start_hdl) && (handle < otas_env.start_hdl + OTA_IDX_NB)); return handle - otas_env.start_hdl; } /// Handles reception of the atts request from peer device static void otas_svc_func(uint8_t conidx, uint8_t opcode, uint16_t handle, const void *param) { uint8_t att_idx = otas_get_att_idx(handle); DEBUG("svc_func(cid:%d,op:0x%x,hdl:0x%x,att:%d)", conidx, opcode, handle, att_idx); switch (opcode) { case ATTS_READ_REQ: { if (att_idx == OTA_IDX_TXD_NTF_CFG) { // retrieve notification config uint16_t cli_cfg = OTA_NTF_CFG_GET(conidx); DEBUG(" read_cfm(txd_ntf:%d)", cli_cfg); gatt_read_cfm(conidx, LE_SUCCESS, handle, sizeof(uint16_t), (uint8_t *)&cli_cfg); break; } #if (OTA_READ_SUP) if (att_idx == OTA_IDX_READ_VAL) { otas_cb_rdv(conidx, att_idx, handle); break; } #endif //(OTA_READ_SUP) // Send error response gatt_read_cfm(conidx, PRF_ERR_APP_ERROR, handle, 0, NULL); } break; case ATTS_WRITE_REQ: { const struct atts_write_ind *ind = param; DEBUG(" write_req(hdl:0x%x,att:%d,wr:0x%x,len:%d)", handle, att_idx, ind->wrcode, ind->length); if (att_idx == OTA_IDX_RXD_VAL) { // Send write conform first! if (!ind->more) gatt_write_cfm(conidx, LE_SUCCESS, handle); // Next to process data received otas_cb_rxd(conidx, ind->length, ind->value); break; } if (att_idx == OTA_IDX_TXD_NTF_CFG) { if ((!ind->more) && (ind->length == sizeof(uint16_t))) { uint16_t cli_cfg = read16p(ind->value); // update configuration if value for stop or NTF/IND start if (cli_cfg <= PRF_CLI_START_IND) { DEBUG(" set txd_ntf(cid:%d,cfg:%d)", conidx, cli_cfg); OTA_NTF_CFG_SET(conidx, cli_cfg); // Send write conform quickly! gatt_write_cfm(conidx, LE_SUCCESS, handle); #if (OTA_CLI_CFG) // Next to process cli_cfg changed otas_cb_ccc(conidx, cli_cfg); #endif //(OTA_CLI_CFG) break; } } } // Send write conform with error! gatt_write_cfm(conidx, PRF_ERR_APP_ERROR, handle); } break; case ATTS_INFO_REQ: { uint8_t status = LE_SUCCESS; uint16_t length = 0; if (att_idx == OTA_IDX_RXD_VAL) { length = OTA_RXD_MAX_LEN; // accepted length } else if (att_idx == OTA_IDX_TXD_NTF_CFG) { length = sizeof(uint16_t); // CCC attribute } else { status = ATT_ERR_WRITE_NOT_PERMITTED; } // Send length-info confirm for prepWR judging. DEBUG(" info_cfm(hdl:0x%x,att:%d,sta:0x%X,len:%d)", handle, att_idx, status, length); gatt_info_cfm(conidx, status, handle, length); } break; case ATTS_CMP_EVT: { const struct atts_cmp_evt *evt = param; otas_env.nb_pkt++; // release DEBUG(" cmp_evt(op:0x%x,sta:0x%x,nb:%d)", evt->operation, evt->status, otas_env.nb_pkt); // add 'if' to avoid warning #117-D: "evt" never referenced if (evt->operation == GATT_NOTIFY) { // Notify result } } break; default: { // nothing to do } break; } } /** **************************************************************************************** * @section API FUNCTIONS **************************************************************************************** */ /** **************************************************************************************** * @brief Add Serial Service Profile in the DB. * Customize via pre-define @see OTA_START_HDL * * @return Result status, LE_SUCCESS or Error Reason **************************************************************************************** */ uint8_t otas_svc_init(void) { uint8_t status = LE_SUCCESS; // Init Environment otas_env.start_hdl = OTA_START_HDL; otas_env.nb_pkt = OTA_NB_PKT_MAX; otas_env.ntf_bits = 0; // Create Service in database status = attmdb_svc_create(&otas_env.start_hdl, NULL, &ota_svc_db, otas_svc_func); DEBUG("svc_init(sta:0x%X,shdl:%d,nb_pkt:%d,ntf_bits:0x%X)", status, otas_env.start_hdl, otas_env.nb_pkt, otas_env.ntf_bits); return status; } /** **************************************************************************************** * @brief Enable setting client configuration characteristics * * @param[in] conidx Connection index * @param[in] cli_cfg Client configuration @see prf_cli_conf **************************************************************************************** */ void otas_set_ccc(uint8_t conidx, uint8_t cli_cfg) { //if (gapc_get_conhdl(conidx) != GAP_INVALID_CONHDL) { // update configuration OTA_NTF_CFG_SET(conidx, cli_cfg); } } /** **************************************************************************************** * @brief Transmit data to peer device via NTF or IND * * @param[in] conidx peer device connection index * @param[in] len Length of data * @param[in] data pointer of buffer * * @return Status of the operation @see prf_err **************************************************************************************** */ uint8_t otas_txd_send(uint8_t conidx, uint16_t len, const uint8_t* data) { uint8_t status = PRF_ERR_REQ_DISALLOWED; if ((len > 0) && (otas_env.nb_pkt > 0)) { uint8_t ntf_cfg = OTA_NTF_CFG_GET(conidx); if (ntf_cfg != PRF_CLI_STOP_NTFIND) { status = LE_SUCCESS; gatt_ntf_send(conidx, otas_get_att_handle(OTA_IDX_TXD_VAL), len, data); otas_env.nb_pkt--; // allocate DEBUG("txd_send(len:%d,nb:%d)", len, otas_env.nb_pkt); } else { status = PRF_ERR_NTF_DISABLED; } } return status; } /** **************************************************************************************** * @brief Callback on received data from peer device via WC or WQ (__weak func) * * @param[in] conidx peer device connection index * @param[in] len Length of data * @param[in] data pointer of buffer **************************************************************************************** */ __weak void otas_cb_rxd(uint8_t conidx, uint16_t len, const uint8_t *data) { debugHex(data, len); // Loopback to txd, just test. //otas_txd_send(conidx, len, data); } #if (OTA_READ_SUP) /** **************************************************************************************** * @brief Callback to response 'READ' from peer device (__weak func) * * @param[in] conidx peer device connection index * @param[in] attidx SESS attribute index, converted with 'handle' * @param[in] handle SESS attribute handle to send read cfm * * @return Length of value been READ **************************************************************************************** */ __weak void otas_cb_rdv(uint8_t conidx, uint8_t attidx, uint16_t handle) { uint16_t length = OTA_VERS_STR_LEN; const uint8_t *p_data = (const uint8_t *)OTA_VERS_STR; DEBUG(" read_cfm(att:%d, len:%d)", attidx, length); gatt_read_cfm(conidx, LE_SUCCESS, handle, length, p_data); } #endif //(OTA_READ_SUP) #if (OTA_CLI_CFG) /** **************************************************************************************** * @brief Callback on enabled client config from peer device via WQ (__weak func) * * @param[in] conidx Connection index * @param[in] cli_cfg Client configuration @see prf_cli_conf **************************************************************************************** */ __weak void otas_cb_ccc(uint8_t conidx, uint8_t cli_cfg) { // user override } #endif //(OTA_CLI_CFG) #endif //PRF_OTAS