This post is older than 2 years and might not be relevant anymore
More Info: Consider searching for newer posts

Cannot read UART service data from nRF52 DK with gatttool on Raspberry Pi 4, writing to CCCD handle causes disconnect (HCI_TIMEOUT)

Hi, I am using nRF52 DK with s112 and SDK V15.2.0. I have written an application based off of the uart example, and expanded it rather substantially to use the SAADC and read from various gpio inputs. I have developed a mobile application with flutter that can read/write data from this application no problem. My nordic application will be flashed onto a custom board with the nRF52810 which will read sensor data. I need a ble receiver that can accept sensor data for long periods of time (weeks-months) for testing/evaluating sensor performance, so I thought a raspberry pi would be great for this. 

I ideally want to use bluepy to write a python script that can scan, connect, read data from the uart service, then log data in a csv file on the pi. I had some issues using bluepy, so i took a step back and tried to see if I can successfully connect just using bluetoothctl or gatttool on the raspberry pi. I am currently using the latest version of raspberry os lite (no DE installed), with the latest bluepy, bluez, etc libraries all installed. The problem I am having is that when using gatttool or bluetoothctl, my nRF52 DK always disconnects due to HCI TIMEOUT when I try and enable notifications or read data on my pi.

I can successfully connect to the nRF52 DK with gatttool. The connection is good until I try to read data on the characteristic, or set the CCCD handle to enable notifications. I can stay connected to the nRF52 DK for 10 seconds, 30 seconds, even 5 minutes, but the second I try to read a characteristic or set the CCCD handle I experience a disconnection. The reason is always "connection timeout", and I get p_ble_evt->evt.gap_evt.params.disconnected.reason == BLE_HCI_CONNECTION_TIMEOUT within my ble_evt_handler function on the DK. The DK says that pairing is successful, the MTU length is set, everything seems good, yet the hci_timeout still occurs.

The handle for CCCD is 0x0010, with UUID 00002902-0000-1000-8000-00805f9b34fb. When I read the handle with char-read-hnd 0x0010 using gatttool, I can see the value is 0000 which means notifications are not enabled. Then, when I use gatttool to set the handle value to enable notifications (char-write-req 0x0010 0100), I see handle is updated, can see one packet of data and then immediately disconnect due to HCI TIMEOUT. Attached are my commands that I run, as well as output from hcidump during the process:

First connection:

pi@raspberrypi:~ $ gatttool -t random -b CE:29:72:08:BE:04 -I 
[CE:29:72:08:BE:04][LE]> connect
Attempting to connect to CE:29:72:08:BE:04
Connection successful
[CE:29:72:08:BE:04][LE]> char-write-req 0x0010 0100
Characteristic value was written successfully
Notification handle = 0x000f value: 30 30 30 30 2c 30 35 31 32 2c 31 30 36 32 2c 30 32 39 36 0a
[CE:29:72:08:BE:04][LE]>
(gatttool:7074): GLib-WARNING **: 08:28:29.582: Invalid file descriptor.

Second attempt at connection: 

pi@raspberrypi:~ $ gatttool -t random -b CE:29:72:08:BE:04 -I 
[CE:29:72:08:BE:04][LE]> connect
Attempting to connect to CE:29:72:08:BE:04
Connection successful
[CE:29:72:08:BE:04][LE]> char-write-req 0x0010 0100
Characteristic value was written successfully
notification handle = 0x000f value: 30 30 30 30 2c 30 34 30 38 2c 30 38 39 31 2c 30 32 38 37 0a
[CE:29:72:08:BE:04][LE]>
(gatttool:7074): GLib-WARNING **: 08:28:29.582: Invalid file descriptor.

Third attempt at connection:

pi@raspberrypi:~ $ gatttool -t random -b CE:29:72:08:BE:04 -I 
[CE:29:72:08:BE:04][LE]> connect
Attempting to connect to CE:29:72:08:BE:04
Connection successful
[CE:29:72:08:BE:04][LE]> char-write-req 0x0010 0100
Characteristic value was written successfully
Notification handle = 0x000f value: 30 30 30 30 2c 30 33 39 31 2c 30 39 37 34 2c 30 33 34 33 0a
[CE:29:72:08:BE:04][LE]>
(gatttool:7074): GLib-WARNING **: 08:28:29.582: Invalid file descriptor.

You can see that the value changes every time - the packet is formatted as XXXX,XXXX,XXXX,XXXX ended with 0x10. You can see the "XXXX" values in between 0x2c (commas) changes, which is expected as my nRF52 DK is reading variable sensor data. Thus the data is being sent, i can read the data once, but then immediately disconnect. I've also done searching online and found that the final warning "GLib-WARNING **" is not cause for concern (see here).

Here is my hcidump:

HCI sniffer - Bluetooth packet analyzer ver 5.50
device: hci0 snap_len: 1500 filter: 0xffffffff
< HCI Command: LE Set Scan Parameters (0x08|0x000b) plen 7
    type 0x00 (passive)
    interval 60.000ms window 30.000ms
    own address: 0x00 (Public) policy: white list only
> HCI Event: Command Complete (0x0e) plen 4
    LE Set Scan Parameters (0x08|0x000b) ncmd 1
    status 0x00
< HCI Command: LE Set Scan Enable (0x08|0x000c) plen 2
    value 0x01 (scanning enabled)
    filter duplicates 0x01 (enabled)
> HCI Event: Command Complete (0x0e) plen 4
    LE Set Scan Enable (0x08|0x000c) ncmd 1
    status 0x00
> HCI Event: LE Meta Event (0x3e) plen 32
    LE Advertising Report
      ADV_IND - Connectable undirected advertising (0)
      bdaddr CE:29:72:08:BE:04 (Random)
      Flags: 0x05
      Complete local name: 'Lura_Health_Dan'
      RSSI: -34
< HCI Command: LE Set Scan Enable (0x08|0x000c) plen 2
    value 0x00 (scanning disabled)
    filter duplicates 0x00 (disabled)
> HCI Event: Command Complete (0x0e) plen 4
    LE Set Scan Enable (0x08|0x000c) ncmd 1
    status 0x00
< HCI Command: LE Create Connection (0x08|0x000d) plen 25
    bdaddr CE:29:72:08:BE:04 type 1
    interval 96 window 96 initiator_filter 0
    own_bdaddr_type 0 min_interval 24 max_interval 40
    latency 0 supervision_to 42 min_ce 0 max_ce 0
> HCI Event: Command Status (0x0f) plen 4
    LE Create Connection (0x08|0x000d) status 0x00 ncmd 1
> HCI Event: LE Meta Event (0x3e) plen 19
    LE Connection Complete
      status 0x00 handle 64, role master
      bdaddr CE:29:72:08:BE:04 (Random)
< HCI Command: LE Read Remote Used Features (0x08|0x0016) plen 2
> HCI Event: Command Status (0x0f) plen 4
    LE Read Remote Used Features (0x08|0x0016) status 0x00 ncmd 1
> HCI Event: Command Complete (0x0e) plen 14
    LE Read Remote Used Features (0x08|0x0016) ncmd 1
> ACL data: handle 64 flags 0x02 dlen 7
    ATT: MTU req (0x02)
      client rx mtu 247
> HCI Event: LE Meta Event (0x3e) plen 12
    LE Read Remote Used Features Complete
      status 0x00 handle 64
      Features: 0x05 0x00 0x00 0x00 0x00 0x00 0x00 0x00
< ACL data: handle 64 flags 0x00 dlen 9
    ATT: Error (0x01)
      Error: Request not supported (6)
      MTU req (0x02) on handle 0x0000
> HCI Event: Number of Completed Packets (0x13) plen 5
    handle 64 packets 1
< ACL data: handle 64 flags 0x00 dlen 9
    ATT: Write req (0x12)
      handle 0x0010 value  0x01 0x00
> ACL data: handle 64 flags 0x02 dlen 9
    ATT: Error (0x01)
      Error: Insufficient authentication (5)
      Write req (0x12) on handle 0x0010
< ACL data: handle 64 flags 0x00 dlen 11
    SMP: Pairing Request (0x01)
      capability 0x03 oob 0x00 auth req 0x29
      max key size 0x10 init key dist 0x0d resp key dist 0x0f
      Capability: NoInputNoOutput (OOB data not present)
      Authentication: Bonding (No MITM Protection)
      Initiator Key Distribution:  LTK  CSRK
      Responder Key Distribution:  LTK IRK CSRK
> HCI Event: Number of Completed Packets (0x13) plen 5
    handle 64 packets 2
> ACL data: handle 64 flags 0x02 dlen 11
    SMP: Pairing Response (0x02)
      capability 0x03 oob 0x00 auth req 0x00
      max key size 0x10 init key dist 0x00 resp key dist 0x00
      Capability: NoInputNoOutput (OOB data not present)
      Authentication: No Bonding (No MITM Protection)
      Initiator Key Distribution:    
      Responder Key Distribution:    
< ACL data: handle 64 flags 0x00 dlen 21
    SMP: Pairing Confirm (0x03)
      key ce95948cd155a0799bbedef734611a59
> ACL data: handle 64 flags 0x02 dlen 21
    SMP: Pairing Confirm (0x03)
      key a3f6ff42c057da57a7998d10470337fe
< ACL data: handle 64 flags 0x00 dlen 21
    SMP: Pairing Random (0x04)
      random b58cea6faf65147e39ce74d61057f9c6
> HCI Event: Number of Completed Packets (0x13) plen 5
    handle 64 packets 2
> ACL data: handle 64 flags 0x02 dlen 21
    SMP: Pairing Random (0x04)
      random e598003d1958ed62a8df058466a3c602
< HCI Command: LE Start Encryption (0x08|0x0019) plen 28
> HCI Event: Command Status (0x0f) plen 4
    LE Start Encryption (0x08|0x0019) status 0x00 ncmd 1
> HCI Event: Encrypt Change (0x08) plen 4
    status 0x00 handle 64 encrypt 0x01
< ACL data: handle 64 flags 0x00 dlen 9
    ATT: Write req (0x12)
      handle 0x0010 value  0x01 0x00
> HCI Event: Number of Completed Packets (0x13) plen 5
    handle 64 packets 1
> ACL data: handle 64 flags 0x02 dlen 5
    ATT: Write resp (0x13)
> ACL data: handle 64 flags 0x02 dlen 27
    ATT: Handle notify (0x1b)
      handle 0x000f
      value 0x30 0x30 0x30 0x30 0x2c 0x30 0x34 0x34 0x32 0x2c 0x30 0x39 0x39 0x33 0x2c 0x30 0x34 0x30 0x31 0x0a 
> HCI Event: Disconn Complete (0x05) plen 4
    status 0x00 handle 64 reason 0x08
    Reason: Connection Timeout

I do see "ATT: Error (0x01) Error: Request not supported (6) MTU req (0x02) on handle 0x0000" on line 52, and "ATT: Error (0x01) Error: Insufficient authentication (5) Write req (0x12) on handle 0x0010" on line 62. I imagine these may be a source of this issue I am having, but i cannot figure out for the life of me why this is happening. Also attached is my main.c I use for my nRF52 DK application. It is rather long and spaghetty-ish right now, I sincerely apologize but it may be helpful. Briefly, I advertise and then once a connection is made I start a short timer. When the timer ends, I read some data with SAADC, make a packet, and send the packet. I repeat this process throughout the entire connection time. There are some other functions in here for calibrating sensor data and handling data sent from the central, but that is not relevant for this issue. I also set my connection and security params as attached below.

Here is the output from my nRF52 DK when using the debugger:

<info> app: ENTERED PM_EVT_HANDLER
<info> app: evt id: 1

<info> app: ENTERED PM_EVT_HANDLER
<info> app: evt id: 5

<info> app: INSIDE BLE EVT HANDLER

<info> app: evt id: 19

<info> app: BLE_GAP_EVT_SEC_PARAMS_REQUEST
<info> app: BLUETOOTH DATA SENT

<info> app: INSIDE BLE EVT HANDLER

<info> app: evt id: 26

<info> app: ENTERED PM_EVT_HANDLER
<info> app: evt id: 2

<info> peer_manager_handler: Connection secured: role: Peripheral, conn_handle: 0, procedure: Pairing
<info> app: PM_EVT_CONN_SEC_SUCCEEDED
<info> app: INSIDE BLE EVT HANDLER

<info> app: evt id: 25

<info> app: BLE_GAP_EVT_AUTH_STATUS: status=0x0 bond=0x0 lv4: 0 kdist_own:0x0 kdist_peer:0x0
<info> app: INSIDE BLE EVT HANDLER

<info> app: evt id: 80

<info> app: BLUETOOTH DATA SENT

<info> app: INSIDE BLE EVT HANDLER

<info> app: evt id: 87

<info> app: BLUETOOTH DATA SENT

<info> app: INSIDE BLE EVT HANDLER

<info> app: evt id: 17

<info> app: connection timeout

<info> app: DISCONNECTED

Disclaimer is that in this firmware application, I send data in the "gatt_evt_handler" function after the MTU ATT exchange is completed, and don't call APP_ERROR_CHECK when I call ble_nus_data_send. I know this is bad practice, and I plan to change it, but I've tried placing the function where I send data inside nus_data_handler after p_evt->type == BLE_NUS_EVT_COMM_STARTED occurs, but I experience the same behavior.

Here is my main.c and some of the relevant functions where I set connection parameters for the nordic app:

/** General defines used for relevant application settings **/

#define APP_ADV_INTERVAL                510                                         /**< The advertising interval (in units of 0.625 ms. This value corresponds to 40 ms). */
#define APP_ADV_DURATION                18000                                       /**< The advertising duration (180 seconds) in units of 10 milliseconds. */

#define MIN_CONN_INTERVAL               MSEC_TO_UNITS(20, UNIT_1_25_MS)             /**< Minimum acceptable connection interval (20 ms), Connection interval uses 1.25 ms units. */
#define MAX_CONN_INTERVAL               MSEC_TO_UNITS(200, UNIT_1_25_MS)             /**< Maximum acceptable connection interval (200 ms), Connection interval uses 1.25 ms units. */
#define SLAVE_LATENCY                   0                                           /**< Slave latency. */
#define CONN_SUP_TIMEOUT                MSEC_TO_UNITS(4000, UNIT_10_MS)             /**< Connection supervisory timeout (4 seconds), Supervision Timeout uses 10 ms units. */
#define FIRST_CONN_PARAMS_UPDATE_DELAY  APP_TIMER_TICKS(5000)                       /**< Time from initiating event (connect or start of notification) to first time sd_ble_gap_conn_param_update is called (5 seconds). */
#define NEXT_CONN_PARAMS_UPDATE_DELAY   APP_TIMER_TICKS(30000)                      /**< Time between each call to sd_ble_gap_conn_param_update after the first call (30 seconds). */
#define MAX_CONN_PARAMS_UPDATE_COUNT    3  

#define SEC_PARAM_BOND                  0                                           /**< Perform bonding. */
#define SEC_PARAM_MITM                  0                                           /**< Man In The Middle protection not required. */
#define SEC_PARAM_LESC                  0                                           /**< LE Secure Connections not enabled. */
#define SEC_PARAM_KEYPRESS              0                                           /**< Keypress notifications not enabled. */
#define SEC_PARAM_IO_CAPABILITIES       BLE_GAP_IO_CAPS_NONE                        /**< No I/O capabilities. */
#define SEC_PARAM_OOB                   0                                           /**< Out Of Band data not available. */
#define SEC_PARAM_MIN_KEY_SIZE          7                                           /**< Minimum encryption key size. */
#define SEC_PARAM_MAX_KEY_SIZE          16 

/**@brief Function for the Peer Manager initialization.
 */
static void peer_manager_init(void)
{
    ble_gap_sec_params_t sec_param;
    ret_code_t           err_code;

    err_code = pm_init();
    APP_ERROR_CHECK(err_code);

    memset(&sec_param, 0, sizeof(ble_gap_sec_params_t));

    // Security parameters to be used for all security procedures.
    sec_param.bond           = SEC_PARAM_BOND;
    sec_param.mitm           = SEC_PARAM_MITM;
    sec_param.lesc           = SEC_PARAM_LESC;
    sec_param.keypress       = SEC_PARAM_KEYPRESS;
    sec_param.io_caps        = SEC_PARAM_IO_CAPABILITIES;
    sec_param.oob            = SEC_PARAM_OOB;
    sec_param.min_key_size   = SEC_PARAM_MIN_KEY_SIZE;
    sec_param.max_key_size   = SEC_PARAM_MAX_KEY_SIZE;
    sec_param.kdist_own.enc  = 0;
    sec_param.kdist_own.id   = 0;
    sec_param.kdist_peer.enc = 0;
    sec_param.kdist_peer.id  = 0;

    err_code = pm_sec_params_set(&sec_param);
    APP_ERROR_CHECK(err_code);

    err_code = pm_register(pm_evt_handler);
    APP_ERROR_CHECK(err_code);
}

/**@brief Function for initializing the Connection Parameters module.
 */
static void conn_params_init(void)
{
    uint32_t               err_code;
    ble_conn_params_init_t cp_init;

    memset(&cp_init, 0, sizeof(cp_init));

    cp_init.p_conn_params                  = NULL;
    cp_init.first_conn_params_update_delay  = FIRST_CONN_PARAMS_UPDATE_DELAY;
    cp_init.next_conn_params_update_delay  = NEXT_CONN_PARAMS_UPDATE_DELAY;
    cp_init.max_conn_params_update_count   = MAX_CONN_PARAMS_UPDATE_COUNT;
    cp_init.start_on_notify_cccd_handle    = BLE_GATT_HANDLE_INVALID;
    cp_init.disconnect_on_fail             = false;
    cp_init.evt_handler                    = on_conn_params_evt;
    cp_init.error_handler                  = conn_params_error_handler;

    err_code = ble_conn_params_init(&cp_init);
    APP_ERROR_CHECK(err_code);
}

/**
 * Copyright (c) 2014 - 2018, Nordic Semiconductor ASA
 *
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without modification,
 * are permitted provided that the following conditions are met:
 *
 * 1. Redistributions of source code must retain the above copyright notice, this
 *    list of conditions and the following disclaimer.
 *
 * 2. Redistributions in binary form, except as embedded into a Nordic
 *    Semiconductor ASA integrated circuit in a product or a software update for
 *    such product, must reproduce the above copyright notice, this list of
 *    conditions and the following disclaimer in the documentation and/or other
 *    materials provided with the distribution.
 *
 * 3. Neither the name of Nordic Semiconductor ASA nor the names of its
 *    contributors may be used to endorse or promote products derived from this
 *    software without specific prior written permission.
 *
 * 4. This software, with or without modification, must only be used with a
 *    Nordic Semiconductor ASA integrated circuit.
 *
 * 5. Any software provided in binary form under this license must not be reverse
 *    engineered, decompiled, modified and/or disassembled.
 *
 * THIS SOFTWARE IS PROVIDED BY NORDIC SEMICONDUCTOR ASA "AS IS" AND ANY EXPRESS
 * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 * OF MERCHANTABILITY, NONINFRINGEMENT, AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL NORDIC SEMICONDUCTOR ASA OR CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
 * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
 * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 */
/** @file
 *
 * @defgroup ble_sdk_uart_over_ble_main main.c
 * @{
 * @ingroup  ble_sdk_app_nus_eval
 * @brief    UART over BLE application main file.
 *
 * This file contains the source code for a sample application that uses the 
 * Nordic UART service.
 * This application uses the @ref srvlib_conn_params module.
 */


#include <stdint.h>
#include <string.h>
#include <time.h>
#include <stdlib.h>
#include <math.h>
#include "nordic_common.h"
#include "boards.h"

#include "nrf.h"
#include "nrf_sdh.h"
#include "nrf_sdh_soc.h"
#include "nrf_sdh_ble.h"
#include "nrf_ble_qwr.h"
#include "nrf_pwr_mgmt.h"
#include "nrf_ble_gatt.h"
#include "nrf_saadc.h"
#include "nrf_drv_clock.h"
#include "nrf_delay.h"
#include "nrf_drv_gpiote.h"
#include "nrf_drv_saadc.h"
#include "nrf_drv_ppi.h"
#include "nrf_drv_timer.h"

#include "nrfx_ppi.h"
#include "nrf_timer.h"
#include "nrfx_saadc.h"

#include "ble_nus.h"
#include "ble_hci.h"
#include "ble_advdata.h"
#include "ble_advertising.h"
#include "ble_conn_params.h"

#include "app_timer.h"
#include "app_uart.h"
#include "app_util_platform.h"
#include "app_fifo.h"
#include "app_pwm.h"
#include "app_error.h"

#include "nrf_log.h"
#include "nrf_log_ctrl.h"
#include "nrf_log_default_backends.h"

// Included for peer manager
#include "nrf_fstorage.h"
#include "nrf_fstorage_sd.h"
#include "fds.h"
#include "peer_manager.h"
#include "peer_manager_handler.h"
#include "ble_conn_state.h"

// Included for persisent flash reads/writes
#include "fds.h"
#include "nrf_fstorage.h"

#if defined (UART_PRESENT)
#include "nrf_uart.h"
#endif
#if defined (UARTE_PRESENT)
#include "nrf_uarte.h"
#endif



#define APP_BLE_CONN_CFG_TAG            1                                           /**< A tag identifying the SoftDevice BLE configuration. */

#define DEVICE_NAME                     "Lura_Health_Dan"                   /**< Name of device. Will be included in the advertising data. */
#define NUS_SERVICE_UUID_TYPE           BLE_UUID_TYPE_VENDOR_BEGIN                  /**< UUID type for the Nordic UART Service (vendor specific). */

#define APP_BLE_OBSERVER_PRIO           3                                           /**< Application's BLE observer priority. You shouldn't need to modify this value. */

#define APP_ADV_INTERVAL                510                                         /**< The advertising interval (in units of 0.625 ms. This value corresponds to 40 ms). */

#define APP_ADV_DURATION                18000                                       /**< The advertising duration (180 seconds) in units of 10 milliseconds. */

#define MIN_CONN_INTERVAL               MSEC_TO_UNITS(20, UNIT_1_25_MS)             /**< Minimum acceptable connection interval (20 ms), Connection interval uses 1.25 ms units. */
#define MAX_CONN_INTERVAL               MSEC_TO_UNITS(200, UNIT_1_25_MS)             /**< Maximum acceptable connection interval (200 ms), Connection interval uses 1.25 ms units. */
#define SLAVE_LATENCY                   0                                           /**< Slave latency. */
#define CONN_SUP_TIMEOUT                MSEC_TO_UNITS(4000, UNIT_10_MS)             /**< Connection supervisory timeout (4 seconds), Supervision Timeout uses 10 ms units. */
#define FIRST_CONN_PARAMS_UPDATE_DELAY  APP_TIMER_TICKS(5000)                       /**< Time from initiating event (connect or start of notification) to first time sd_ble_gap_conn_param_update is called (5 seconds). */
#define NEXT_CONN_PARAMS_UPDATE_DELAY   APP_TIMER_TICKS(30000)                      /**< Time between each call to sd_ble_gap_conn_param_update after the first call (30 seconds). */
#define MAX_CONN_PARAMS_UPDATE_COUNT    3                                           /**< Number of attempts before giving up the connection parameter negotiation. */

#define DEAD_BEEF                       0xDEADBEEF                                  /**< Value used as error code on stack dump, can be used to identify stack location on stack unwind. */

#define UART_TX_BUF_SIZE                256                                         /**< UART TX buffer size. */
#define UART_RX_BUF_SIZE                256                                         /**< UART RX buffer size. */

#define SEC_PARAM_BOND                  0                                           /**< Perform bonding. */
#define SEC_PARAM_MITM                  0                                           /**< Man In The Middle protection not required. */
#define SEC_PARAM_LESC                  0                                           /**< LE Secure Connections not enabled. */
#define SEC_PARAM_KEYPRESS              0                                           /**< Keypress notifications not enabled. */
#define SEC_PARAM_IO_CAPABILITIES       BLE_GAP_IO_CAPS_NONE                        /**< No I/O capabilities. */
#define SEC_PARAM_OOB                   0                                           /**< Out Of Band data not available. */
#define SEC_PARAM_MIN_KEY_SIZE          7                                           /**< Minimum encryption key size. */
#define SEC_PARAM_MAX_KEY_SIZE          16                                          /**< Maximum encryption key size. */

#define SAMPLES_IN_BUFFER               50                                          /**< SAADC buffer > */

#define DATA_INTERVAL                   500


#define NRF_SAADC_CUSTOM_CHANNEL_CONFIG_SE(PIN_P) \
{                                                   \
    .resistor_p = NRF_SAADC_RESISTOR_DISABLED,      \
    .resistor_n = NRF_SAADC_RESISTOR_DISABLED,      \
    .gain       = NRF_SAADC_GAIN1_5,                \
    .reference  = NRF_SAADC_REFERENCE_INTERNAL,     \
    .acq_time   = NRF_SAADC_ACQTIME_10US,           \
    .mode       = NRF_SAADC_MODE_SINGLE_ENDED,      \
    .burst      = NRF_SAADC_BURST_DISABLED,         \
    .pin_p      = (nrf_saadc_input_t)(PIN_P),       \
    .pin_n      = NRF_SAADC_INPUT_DISABLED          \
}

/* UNDEFS FOR DEBUGGING */
#undef RX_PIN_NUMBER
#undef RTS_PIN_NUMBER
#undef LED_4          
#undef LED_STOP       

BLE_NUS_DEF(m_nus, NRF_SDH_BLE_TOTAL_LINK_COUNT);                                   /**< BLE NUS service instance. */
NRF_BLE_GATT_DEF(m_gatt);                                                           /**< GATT module instance. */
NRF_BLE_QWR_DEF(m_qwr);                                                             /**< Context for the Queued Write module.*/
BLE_ADVERTISING_DEF(m_advertising);                                                 /**< Advertising module instance. */
APP_TIMER_DEF(m_timer_id);

static uint16_t   m_conn_handle          = BLE_CONN_HANDLE_INVALID;                 /**< Handle of the current connection. */
static uint16_t   m_ble_nus_max_data_len = BLE_GATT_ATT_MTU_DEFAULT - 3;            /**< Maximum length of data (in bytes) that can be transmitted to the peer by the Nordic UART service module. */
static ble_uuid_t m_adv_uuids[]          =                                          /**< Universally unique service identifier. */
{
    {BLE_UUID_NUS_SERVICE, NUS_SERVICE_UUID_TYPE}
};


/* Lura Health nRF52810 port assignments */
#define ENABLE_ISFET_PIN 8
#define CHIP_POWER_PIN   12

/* GLOBALS */
uint32_t AVG_PH_VAL        = 0;
uint32_t AVG_BATT_VAL      = 0;
uint32_t AVG_TEMP_VAL      = 0;
bool     PH_IS_READ        = false;
bool     BATTERY_IS_READ   = false;
bool     SAADC_CALIBRATED  = false;
bool     CONNECTION_MADE   = false;
bool     CAL_MODE          = false;
bool     READ_CAL_DATA     = false;
bool     PT1_READ          = false;
bool     PT2_READ          = false;
bool     PT3_READ          = false;
double   PT1_PH_VAL        = 0;
double   PT1_MV_VAL        = 0;
double   PT2_PH_VAL        = 0;
double   PT2_MV_VAL        = 0;
double   PT3_PH_VAL        = 0;
double   PT3_MV_VAL        = 0;
int      NUM_CAL_PTS       = 0;
float     MVAL_CALIBRATION  = 0;
float     BVAL_CALIBRATION  = 0;
float     RVAL_CALIBRATION  = 0;
float     CAL_PERFORMED     = 0;
static volatile uint8_t write_flag=0;

/* Used for reading/writing calibration values to flash */
#define MVAL_FILE_ID      0x1110
#define MVAL_REC_KEY      0x1111
#define BVAL_FILE_ID      0x2220
#define BVAL_REC_KEY      0x2221
#define RVAL_FILE_ID      0x3330
#define RVAL_REC_KEY      0x3331
#define CAL_DONE_FILE_ID  0x4440
#define CAL_DONE_REC_KEY  0x4441



static const nrf_drv_timer_t   m_timer = NRF_DRV_TIMER_INSTANCE(1);
static       nrf_saadc_value_t m_buffer_pool[1][SAMPLES_IN_BUFFER];
static       nrf_ppi_channel_t m_ppi_channel;


// Forward declarations
void enable_pH_voltage_reading  (void);
void disable_pH_voltage_reading (void);
void saadc_init                 (void);
void enable_isfet_circuit       (void);
void disable_isfet_circuit      (void);
void turn_chip_power_on         (void);
void turn_chip_power_off         (void);
void restart_saadc              (void);
void restart_pH_interval_timer  (void);
void write_cal_values_to_flash   (void);
void linreg                     (int num, double x[], double y[]);
void perform_calibration        (uint8_t cal_pts);
double calculate_pH_from_mV     (uint32_t ph_val);
static void advertising_start   (bool erase_bonds);
uint32_t saadc_result_to_mv     (uint32_t saadc_result);


/**@brief Function for assert macro callback.
 *
 * @details This function will be called in case of an assert in the SoftDevice.
 *
 * @warning This handler is an example only and does not fit a final product. 
 *          You need to analyse how your product is supposed to react in case of 
 *          Assert.
 * @warning On assert from the SoftDevice, the system can only recover on reset.
 *
 * @param[in] line_num    Line number of the failing ASSERT call.
 * @param[in] p_file_name File name of the failing ASSERT call.
 */
void assert_nrf_callback(uint16_t line_num, const uint8_t * p_file_name)
{
    app_error_handler(DEAD_BEEF, line_num, p_file_name);
}

/**@brief Function for handling Peer Manager events.
 *
 * @param[in] p_evt  Peer Manager event.
 */
static void pm_evt_handler(pm_evt_t const * p_evt)
{
    NRF_LOG_INFO("ENTERED PM_EVT_HANDLER");
    NRF_LOG_INFO("evt id: %u\n", p_evt->evt_id);
    //NRF_LOG_FLUSH();
    pm_handler_on_pm_evt(p_evt);
    pm_handler_flash_clean(p_evt);

    switch (p_evt->evt_id)
    {
        case PM_EVT_CONN_SEC_SUCCEEDED:
            NRF_LOG_INFO("PM_EVT_CONN_SEC_SUCCEEDED");
            NRF_LOG_FLUSH();
            break;

        case PM_EVT_PEERS_DELETE_SUCCEEDED:
            NRF_LOG_INFO("PM_EVT_PEERS_DELETE_SUCCEEDED");
            NRF_LOG_FLUSH();            
            advertising_start(false);
            break;

        case PM_EVT_BONDED_PEER_CONNECTED:  
            NRF_LOG_INFO("PM_EVT_BONDED_PEER_CONNECTED");
            NRF_LOG_FLUSH();
            break;

        case PM_EVT_CONN_SEC_CONFIG_REQ:
        {
            // Allow pairing request from an already bonded peer.
            NRF_LOG_INFO("PM_EVT_CONN_SEC_CONFIG_REQ");
            NRF_LOG_FLUSH();
            pm_conn_sec_config_t conn_sec_config = {.allow_repairing = true};
            pm_conn_sec_config_reply(p_evt->conn_handle, &conn_sec_config);
        } break;

        default:
            break;
    }
}


/**@brief Function for initializing the timer module.
 */
void timers_init(void)
{
    uint32_t err_code;
    err_code = app_timer_init();
    APP_ERROR_CHECK(err_code);
}


/**@brief Function for the GAP initialization.
 *
 * @details This function will set up all the necessary GAP (Generic Access 
 *          Profile) parameters of the device. It also sets the permissions 
 *          and appearance.
 */
void gap_params_init(void)
{
    uint32_t                err_code;
    ble_gap_conn_params_t   gap_conn_params;
    ble_gap_conn_sec_mode_t sec_mode;

    BLE_GAP_CONN_SEC_MODE_SET_OPEN(&sec_mode);

    err_code = sd_ble_gap_device_name_set(&sec_mode,
                                         (const uint8_t *)DEVICE_NAME,
                                          strlen(DEVICE_NAME));
    APP_ERROR_CHECK(err_code);

    memset(&gap_conn_params, 0, sizeof(gap_conn_params));

    gap_conn_params.min_conn_interval = MIN_CONN_INTERVAL;
    gap_conn_params.max_conn_interval = MAX_CONN_INTERVAL;
    gap_conn_params.slave_latency     = SLAVE_LATENCY;
    gap_conn_params.conn_sup_timeout  = CONN_SUP_TIMEOUT;

    err_code = sd_ble_gap_ppcp_set(&gap_conn_params);
    APP_ERROR_CHECK(err_code);
}


/**@brief Function for handling Queued Write Module errors.
 *
 * @details A pointer to this function will be passed to each service which may 
 *          need to inform the application about an error.
 *
 * @param[in] nrf_error Error code containing information about what went wrong.
 */
static void nrf_qwr_error_handler(uint32_t nrf_error)
{
    APP_ERROR_HANDLER(nrf_error);
}

// Helper function
void substring(char s[], char sub[], int p, int l) {
   int c = 0;
   
   while (c < l) {
      sub[c] = s[p+c-1];
      c++;
   }
   sub[c] = '\0';
}

// Read saadc values for temperature, battery level, and pH to store for calibration
void read_saadc_for_calibration(void) 
{
    int NUM_SAMPLES = 50;
    nrf_saadc_value_t temp_val = 0;
    ret_code_t err_code;
    disable_pH_voltage_reading();
    AVG_PH_VAL = 0;
    READ_CAL_DATA = true;
    PH_IS_READ = false;
    // Make sure isfet circuit is enabled for saadc readings
    enable_isfet_circuit();
    nrf_delay_ms(10);
    // Take saadc readings for pH, temp and battery
    enable_pH_voltage_reading();
    for (int i = 0; i < NUM_SAMPLES; i++) {
      err_code = nrfx_saadc_sample_convert(0, &temp_val);
      APP_ERROR_CHECK(err_code);
      AVG_PH_VAL += saadc_result_to_mv(temp_val);
    }
    AVG_PH_VAL = AVG_PH_VAL / NUM_SAMPLES;
    //NRF_LOG_INFO("averaged avg_ph_val: %u\n");
    //NRF_LOG_FLUSH();
    // Assign averaged readings to the correct calibration point
    if(!PT1_READ){
      PT1_MV_VAL = (double)AVG_PH_VAL;
      PT1_READ   = true;
    }
    else if (PT1_READ && !PT2_READ){
      PT2_MV_VAL = (double)AVG_PH_VAL;
      PT2_READ = true;
    }
    else if (PT1_READ && PT2_READ && !PT3_READ){
       PT3_MV_VAL = (double)AVG_PH_VAL;
       PT3_READ = true;
    }    
    disable_pH_voltage_reading();
}

/* Helper function to clear calibration global state variables
 */
void reset_calibration_state()
{
    CAL_MODE        = false;
    READ_CAL_DATA   = false;
    CAL_PERFORMED   = 1.0;
    PT1_READ        = false;
    PT2_READ        = false;
    PT3_READ        = false;
    PH_IS_READ      = false;
    BATTERY_IS_READ = false;
}

/*
 * Use the values read from read_saadc_for_calibration to reset the M, B and R values
 * to recalibrate accuracy of ISFET voltage output to pH value conversions
 */
void perform_calibration(uint8_t cal_pts)
{
  if (cal_pts == 1) {
    // Compare mV for pH value to mV calculated for same pH with current M & B values,
    // then adjust B value by the difference in mV values (shift intercept of line)
    double incorrect_pH  = calculate_pH_from_mV((uint32_t)PT1_MV_VAL);
    double cal_adjustment = PT1_PH_VAL - incorrect_pH;
    BVAL_CALIBRATION = BVAL_CALIBRATION + cal_adjustment;
    NRF_LOG_INFO("MVAL: " NRF_LOG_FLOAT_MARKER " \n", NRF_LOG_FLOAT(MVAL_CALIBRATION));
    NRF_LOG_INFO("BVAL: " NRF_LOG_FLOAT_MARKER " \n", NRF_LOG_FLOAT(BVAL_CALIBRATION));
    NRF_LOG_INFO("RVAL: " NRF_LOG_FLOAT_MARKER " \n", NRF_LOG_FLOAT(RVAL_CALIBRATION));
    //NRF_LOG_INFO("incorrect: %d, pt1: %d, adjustment: %d, BVAL: %d\n", (int)incorrect_pH, (int)PT1_PH_VAL, (int)cal_adjustment, (int)(BVAL_CALIBRATION));

  }
  else if (cal_pts == 2) {
    // Create arrays of pH value and corresponding mV values (change all line properties)
    double x1 = PT1_MV_VAL;
    double x2 = PT2_MV_VAL;
    double y1 = PT1_PH_VAL;
    double y2 = PT2_PH_VAL;
    double x_vals[] = {x1, x2};
    double y_vals[] = {y1, y2};
    linreg(2, x_vals, y_vals);
  }
  else if (cal_pts == 3) {
    // Create arrays of pH value and corresponding mV values (change all line properties)
    double x1 = PT1_MV_VAL;
    double x2 = PT2_MV_VAL;
    double x3 = PT3_MV_VAL;
    double y1 = PT1_PH_VAL;
    double y2 = PT2_PH_VAL;
    double y3 = PT3_PH_VAL;
    double x_vals[] = {x1, x2, x3};
    double y_vals[] = {y1, y2, y3};
    linreg(3, x_vals, y_vals);
  }
}

/*
 * Checks packet contents to appropriately perform calibration
 */
void check_for_calibration(char **packet)
{
    // Possible Strings to be received by pH device
    char *STARTCAL1 = "STARTCAL1";
    char *STARTCAL2 = "STARTCAL2";
    char *STARTCAL3 = "STARTCAL3";
    char *PWROFF    = "PWROFF";
    char *PT1       = "PT1";
    char *PT2       = "PT2";
    char *PT3       = "PT3";

    // Possible strings to send to mobile application
    char *CALBEGIN = "CALBEGIN";
    char *PT1CONF  = "PT1CONF";
    char *PT2CONF  = "PT2CONF";
    char *PT3CONF  = "PT3CONF";

    // Variables to hold sizes of strings for ble_nus_send function
    uint16_t SIZE_BEGIN = 9;
    uint16_t SIZE_CONF  = 8;

    // Used for parsing out pH value from PT1_X.Y (etc) packets
    char pH_val_substring[4];

    uint32_t err_code;

    if (strstr(*packet, PWROFF) != NULL){
        nrfx_gpiote_out_clear(CHIP_POWER_PIN);
    }

    if (strstr(*packet, STARTCAL1) != NULL){
        CAL_MODE = true;
        NUM_CAL_PTS = 1;
        // Make sure other processes are stopped and reset so calibration can occur
        disable_pH_voltage_reading();
        err_code = ble_nus_data_send(&m_nus, CALBEGIN, &SIZE_BEGIN, m_conn_handle);
        APP_ERROR_CHECK(err_code);
    }
    else if (strstr(*packet, STARTCAL2) != NULL){
        CAL_MODE = true;
        NUM_CAL_PTS = 2;
        // Make sure other processes are stopped and reset so calibration can occur
        disable_pH_voltage_reading();
        err_code = ble_nus_data_send(&m_nus, CALBEGIN, &SIZE_BEGIN, m_conn_handle);
        APP_ERROR_CHECK(err_code);
    }
    else if (strstr(*packet, STARTCAL3) != NULL) {
        CAL_MODE = true;
        NUM_CAL_PTS = 3;
        // Make sure other processes are stopped and reset so calibration can occur
        disable_pH_voltage_reading();
        err_code = ble_nus_data_send(&m_nus, CALBEGIN, &SIZE_BEGIN, m_conn_handle);
        APP_ERROR_CHECK(err_code);
    }

    if (strstr(*packet, PT1) != NULL) {
        // Parse out the pH value from the packet, will always be format "X.Y"
        substring(*packet, pH_val_substring, 5, 3);
        PT1_PH_VAL = atof(pH_val_substring);   

        read_saadc_for_calibration();
        err_code = ble_nus_data_send(&m_nus, PT1CONF, &SIZE_CONF, m_conn_handle);
        APP_ERROR_CHECK(err_code);
        // Restart normal data transmision once calibration is complete
        if (NUM_CAL_PTS == 1) {
          perform_calibration(1);
          write_cal_values_to_flash();
          reset_calibration_state();
          restart_pH_interval_timer();
        }
    }
    else if (strstr(*packet, PT2) != NULL) {
        // Parse out the pH value from the packet, will always be format "X.Y"
        substring(*packet, pH_val_substring, 5, 3);
        PT2_PH_VAL = atof(pH_val_substring);
        
        read_saadc_for_calibration();
        err_code = ble_nus_data_send(&m_nus, PT2CONF, &SIZE_CONF, m_conn_handle);
        APP_ERROR_CHECK(err_code);
        // Restart normal data transmision once calibration is complete
        if (NUM_CAL_PTS == 2) {
          perform_calibration(2);
          write_cal_values_to_flash();
          reset_calibration_state();
          restart_pH_interval_timer();
        }
    }
    else if (strstr(*packet, PT3) != NULL) {
        // Parse out the pH value from the packet, will always be format "X.Y"
        substring(*packet, pH_val_substring, 5, 3);
        PT3_PH_VAL = atof(pH_val_substring); 
       
        read_saadc_for_calibration();
        err_code = ble_nus_data_send(&m_nus, PT3CONF, &SIZE_CONF, m_conn_handle);
        APP_ERROR_CHECK(err_code);
        // Restart normal data transmision once calibration is complete
        if (NUM_CAL_PTS == 3) {
          perform_calibration(3);
          write_cal_values_to_flash();
          reset_calibration_state();
          restart_pH_interval_timer();
        }    
    }
}


/**@brief Function for handling the data from the Nordic UART Service.
 *
 * @details This function will process the data received from the Nordic UART 
 *          BLE Service and send it to the UART module.
 *
 * @param[in] p_evt       Nordic UART Service event.
 */
/**@snippet [Handling the data received over BLE] */
void nus_data_handler(ble_nus_evt_t * p_evt)
{

    if (p_evt->type == BLE_NUS_EVT_RX_DATA)
    {
        // Array to store data received by smartphone will never exceed 9 characters
        char data[10];
        // Pointer to array
        char *data_ptr = data;
        uint32_t err_code;

        NRF_LOG_DEBUG("Received data from BLE NUS.\n");
        NRF_LOG_HEXDUMP_DEBUG(p_evt->params.rx_data.p_data, 
                                            p_evt->params.rx_data.length);

        for (uint32_t i = 0; i < p_evt->params.rx_data.length; i++)
        {
            do
            {
                // Parse data into array
                data[i] = p_evt->params.rx_data.p_data[i];
                if ((err_code != NRF_SUCCESS) && (err_code != NRF_ERROR_BUSY))
                {
                    NRF_LOG_ERROR("Failed receiving NUS message. Error 0x%x. ", 
                                                                    err_code);
                    APP_ERROR_CHECK(err_code);
                }
            } while (err_code == NRF_ERROR_BUSY);
        }
        // Check pack for calibration protocol details
        check_for_calibration(&data_ptr);
    }

}
/**@snippet [Handling the data received over BLE] */


/**@brief Function for initializing services that will be used by the application.
 */
static void services_init(void)
{
    uint32_t           err_code;
    ble_nus_init_t     nus_init;
    nrf_ble_qwr_init_t qwr_init = {0};

    // Initialize Queued Write Module
    qwr_init.error_handler = nrf_qwr_error_handler;

    err_code = nrf_ble_qwr_init(&m_qwr, &qwr_init);
    APP_ERROR_CHECK(err_code);

    // Initialize NUS
    memset(&nus_init, 0, sizeof(nus_init));

    nus_init.data_handler = nus_data_handler;

    err_code = ble_nus_init(&m_nus, &nus_init);
    APP_ERROR_CHECK(err_code);
}


/**@brief Function for handling an event from the Connection Parameters Module.
 *
 * @details This function will be called for all events in the Connection 
 *          Parameters Module which are passed to the application.
 *
 * @note All this function does is to disconnect. This could have been done by 
 *       simply setting the disconnect_on_fail config parameter, but instead 
 *       we use the event handler mechanism to demonstrate its use.
 *
 * @param[in] p_evt  Event received from the Connection Parameters Module.
 */
static void on_conn_params_evt(ble_conn_params_evt_t * p_evt)
{
    uint32_t err_code;

    NRF_LOG_INFO("INSIDE CONN PARAMS EVT");
    NRF_LOG_INFO("evt type: %u\n", p_evt->evt_type);

    if (p_evt->evt_type == BLE_CONN_PARAMS_EVT_FAILED)
    {
        err_code = sd_ble_gap_disconnect(m_conn_handle, 
                                         BLE_HCI_CONN_INTERVAL_UNACCEPTABLE);
        APP_ERROR_CHECK(err_code);
    }
}


/**@brief Function for handling errors from the Connection Parameters module.
 *
 * @param[in] nrf_error  Error code containing information about what went wrong.
 */
static void conn_params_error_handler(uint32_t nrf_error)
{
    APP_ERROR_HANDLER(nrf_error);
}


/**@brief Function for initializing the Connection Parameters module.
 */
static void conn_params_init(void)
{
    uint32_t               err_code;
    ble_conn_params_init_t cp_init;

    memset(&cp_init, 0, sizeof(cp_init));

    cp_init.p_conn_params                  = NULL;
    cp_init.first_conn_params_update_delay  = FIRST_CONN_PARAMS_UPDATE_DELAY;
    cp_init.next_conn_params_update_delay  = NEXT_CONN_PARAMS_UPDATE_DELAY;
    cp_init.max_conn_params_update_count   = MAX_CONN_PARAMS_UPDATE_COUNT;
    cp_init.start_on_notify_cccd_handle    = BLE_GATT_HANDLE_INVALID;
    cp_init.disconnect_on_fail             = false;
    cp_init.evt_handler                    = on_conn_params_evt;
    cp_init.error_handler                  = conn_params_error_handler;

    err_code = ble_conn_params_init(&cp_init);
    APP_ERROR_CHECK(err_code);
}


/**@brief Function for putting the chip into sleep mode.
 *
 * @note This function will not return.
 */
static void sleep_mode_enter(void)
{
    uint32_t err_code; 

    // Go to system-off mode (function will not return; wakeup causes reset).
    //err_code = sd_power_system_off();
    APP_ERROR_CHECK(err_code);
}


/**@brief Function for handling advertising events.
 *
 * @details This function will be called for advertising events which are passed 
 *          to the application.
 *
 * @param[in] ble_adv_evt  Advertising event.
 */
static void on_adv_evt(ble_adv_evt_t ble_adv_evt)
{
    uint32_t err_code;

    switch (ble_adv_evt)
    {
        case BLE_ADV_EVT_FAST:
            break;
        case BLE_ADV_EVT_IDLE:
            sleep_mode_enter();
            break;
        default:
            break;
    }
}


/**@brief Function for handling BLE events.
 *
 * @param[in]   p_ble_evt   Bluetooth stack event.
 * @param[in]   p_context   Unused.
 */
static void ble_evt_handler(ble_evt_t const * p_ble_evt, void * p_context)
{
    uint32_t err_code;

    NRF_LOG_INFO("INSIDE BLE EVT HANDLER\n");
    NRF_LOG_INFO("evt id: %u\n", p_ble_evt->header.evt_id);

    switch (p_ble_evt->header.evt_id)
    {
        case BLE_GATTS_EVT_SYS_ATTR_MISSING:
            NRF_LOG_INFO("INSIDE SYS ATTR MISSING");
            // No system attributes have been stored.
            err_code = sd_ble_gatts_sys_attr_set(m_conn_handle, NULL, 0, 0);
            APP_ERROR_CHECK(err_code);
            break; // BLE_GATTS_EVT_SYS_ATTR_MISSINg
        case BLE_GAP_EVT_CONNECTED:
            m_conn_handle = p_ble_evt->evt.gap_evt.conn_handle;
            err_code = nrf_ble_qwr_conn_handle_assign(&m_qwr, m_conn_handle);
            APP_ERROR_CHECK(err_code);
            CONNECTION_MADE = true;

            NRF_LOG_INFO("CONNECTION MADE (ble_gap_evt) \n");

            break;

        case BLE_GAP_EVT_DISCONNECTED:
            if(p_ble_evt->evt.gap_evt.params.disconnected.reason  == 
                                                    BLE_HCI_CONNECTION_TIMEOUT)
            {
                NRF_LOG_INFO("connection timeout\n");
            }
            m_conn_handle = BLE_CONN_HANDLE_INVALID;
            CONNECTION_MADE = false;
            NRF_LOG_INFO("DISCONNECTED\n");
            NRF_LOG_FLUSH();

            nrfx_timer_uninit(&m_timer);
            nrfx_ppi_channel_free(m_ppi_channel);
            nrfx_saadc_uninit();

            // *** DISABLE ENABLE ***
            disable_isfet_circuit();

            // restart advertising
            advertising_start(false);

            break;

        case BLE_GAP_EVT_PHY_UPDATE_REQUEST:
        {
            NRF_LOG_DEBUG("PHY update request.");
            ble_gap_phys_t const phys =
            {
                .rx_phys = BLE_GAP_PHY_AUTO,
                .tx_phys = BLE_GAP_PHY_AUTO,
            };
            err_code = sd_ble_gap_phy_update(p_ble_evt->evt.gap_evt.conn_handle, 
                                                                         &phys);
            APP_ERROR_CHECK(err_code);
        } break;

        case BLE_GATTC_EVT_TIMEOUT:
            NRF_LOG_INFO("ble gattc evt timeout");
            // Disconnect on GATT Client timeout event.
            err_code = 
                sd_ble_gap_disconnect(p_ble_evt->evt.gattc_evt.conn_handle,
                                      BLE_HCI_REMOTE_USER_TERMINATED_CONNECTION);
            CONNECTION_MADE = false;
            APP_ERROR_CHECK(err_code);
            break;

        case BLE_GATTS_EVT_TIMEOUT:
            // Disconnect on GATT Server timeout event.
            err_code = 
                sd_ble_gap_disconnect(p_ble_evt->evt.gatts_evt.conn_handle,
                                      BLE_HCI_REMOTE_USER_TERMINATED_CONNECTION);
            CONNECTION_MADE = false;
            APP_ERROR_CHECK(err_code);
            break;

         case BLE_GAP_EVT_AUTH_STATUS:
             NRF_LOG_INFO("BLE_GAP_EVT_AUTH_STATUS: status=0x%x bond=0x%x lv4: %d kdist_own:0x%x kdist_peer:0x%x",
                          p_ble_evt->evt.gap_evt.params.auth_status.auth_status,
                          p_ble_evt->evt.gap_evt.params.auth_status.bonded,
                          p_ble_evt->evt.gap_evt.params.auth_status.sm1_levels.lv4,
                          *((uint8_t *)&p_ble_evt->evt.gap_evt.params.auth_status.kdist_own),
                          *((uint8_t *)&p_ble_evt->evt.gap_evt.params.auth_status.kdist_peer));
            break;

        case BLE_GAP_EVT_SEC_PARAMS_REQUEST:
            NRF_LOG_INFO("BLE_GAP_EVT_SEC_PARAMS_REQUEST");
            break;

        default:
            // No implementation needed.
            break;
    }
}


/**@brief Function for the SoftDevice initialization.
 *
 * @details This function initializes the SoftDevice and the BLE event interrupt.
 */
static void ble_stack_init(void)
{
    ret_code_t err_code;

    err_code = nrf_sdh_enable_request();
    APP_ERROR_CHECK(err_code);

    // Configure the BLE stack using the default settings.
    // Fetch the start address of the application RAM.
    uint32_t ram_start = 0;
    err_code = nrf_sdh_ble_default_cfg_set(APP_BLE_CONN_CFG_TAG, &ram_start);
    APP_ERROR_CHECK(err_code);

    // Enable BLE stack.
    err_code = nrf_sdh_ble_enable(&ram_start);
    APP_ERROR_CHECK(err_code);

    // Register a handler for BLE events.
    NRF_SDH_BLE_OBSERVER(m_ble_observer, APP_BLE_OBSERVER_PRIO, 
                                                ble_evt_handler, NULL);
}


/**@brief Function for the Peer Manager initialization.
 */
static void peer_manager_init(void)
{
    ble_gap_sec_params_t sec_param;
    ret_code_t           err_code;

    err_code = pm_init();
    APP_ERROR_CHECK(err_code);

    memset(&sec_param, 0, sizeof(ble_gap_sec_params_t));

    // Security parameters to be used for all security procedures.
    sec_param.bond           = SEC_PARAM_BOND;
    sec_param.mitm           = SEC_PARAM_MITM;
    sec_param.lesc           = SEC_PARAM_LESC;
    sec_param.keypress       = SEC_PARAM_KEYPRESS;
    sec_param.io_caps        = SEC_PARAM_IO_CAPABILITIES;
    sec_param.oob            = SEC_PARAM_OOB;
    sec_param.min_key_size   = SEC_PARAM_MIN_KEY_SIZE;
    sec_param.max_key_size   = SEC_PARAM_MAX_KEY_SIZE;
    sec_param.kdist_own.enc  = 0;
    sec_param.kdist_own.id   = 0;
    sec_param.kdist_peer.enc = 0;
    sec_param.kdist_peer.id  = 0;

    err_code = pm_sec_params_set(&sec_param);
    APP_ERROR_CHECK(err_code);

    err_code = pm_register(pm_evt_handler);
    APP_ERROR_CHECK(err_code);
}


/**@brief Clear bond information from persistent storage.
 */
static void delete_bonds(void)
{
    ret_code_t err_code;

    NRF_LOG_INFO("Erase bonds!");

    err_code = pm_peers_delete();
    APP_ERROR_CHECK(err_code);
}


/**@brief Function for initializing the Advertising functionality.
 */
static void advertising_init(void)
{
    uint32_t               err_code;
    ble_advertising_init_t init;

    memset(&init, 0, sizeof(init));

    init.advdata.name_type          = BLE_ADVDATA_FULL_NAME;
    init.advdata.include_appearance = false;
    init.advdata.flags               = BLE_GAP_ADV_FLAGS_LE_ONLY_LIMITED_DISC_MODE;

    init.srdata.uuids_complete.uuid_cnt = sizeof(m_adv_uuids) / sizeof(m_adv_uuids[0]);
    init.srdata.uuids_complete.p_uuids  = m_adv_uuids;

    init.config.ble_adv_fast_enabled  = true;
    init.config.ble_adv_fast_interval = APP_ADV_INTERVAL;
    init.config.ble_adv_fast_timeout  = APP_ADV_DURATION;
    init.evt_handler = on_adv_evt;

    err_code = ble_advertising_init(&m_advertising, &init);
    APP_ERROR_CHECK(err_code);

    ble_advertising_conn_cfg_tag_set(&m_advertising, APP_BLE_CONN_CFG_TAG);
}


/**@brief Function for initializing the nrf log module.
 */
static void log_init(void)
{
    ret_code_t err_code = NRF_LOG_INIT(NULL);
    APP_ERROR_CHECK(err_code);

    NRF_LOG_DEFAULT_BACKENDS_INIT();
}


/**@brief Function for initializing power management.
 */
static void power_management_init(void)
{
    ret_code_t err_code;
    err_code = nrf_pwr_mgmt_init();
    APP_ERROR_CHECK(err_code);
}


/**@brief Function for handling the idle state (main loop).
 *
 * @details If there is no pending log operation, then sleep until next the 
 *          next event occurs.
 */
static void idle_state_handle(void)
{
    UNUSED_RETURN_VALUE(NRF_LOG_PROCESS());
    nrf_pwr_mgmt_run();
}


/**@brief Function for starting advertising.
 */
static void advertising_start(bool erase_bonds)
{
    if (erase_bonds == true)
    {
        delete_bonds();
        // Advertising is started by PM_EVT_PEERS_DELETED_SUCEEDED event
    }
    else
    {
        ret_code_t err_code = ble_advertising_start(&m_advertising, BLE_ADV_MODE_FAST);

        APP_ERROR_CHECK(err_code);
    }
}


/* This function sets enable pin for ISFET circuitry to HIGH
 */
void enable_isfet_circuit(void)
{
    nrf_drv_gpiote_out_config_t config = NRFX_GPIOTE_CONFIG_OUT_SIMPLE(false);
    if(nrf_drv_gpiote_is_init() == false) {
          nrf_drv_gpiote_init();
    }
    nrf_drv_gpiote_out_init(ENABLE_ISFET_PIN, &config);
    nrf_drv_gpiote_out_set(ENABLE_ISFET_PIN);
}

/* This function holds POWER ON line HIGH to keep chip turned on
 */
void turn_chip_power_on(void)
{
    nrf_drv_gpiote_out_config_t config = NRFX_GPIOTE_CONFIG_OUT_SIMPLE(false);
    if(nrf_drv_gpiote_is_init() == false) {
          nrf_drv_gpiote_init();
    }
    nrf_drv_gpiote_out_init(CHIP_POWER_PIN, &config);
    nrf_drv_gpiote_out_set(CHIP_POWER_PIN);
}

/* This functions turns POWER ON line LOW to turn the chip completely off
 */
void turn_chip_power_off(void)
{
    nrfx_gpiote_out_clear(CHIP_POWER_PIN);
}

/* This function sets enable pin for ISFET circuitry to LOW
 */
void disable_isfet_circuit(void)
{
     // Redundant, but follows design
     // nrfx_gpiote_uninit();
     nrfx_gpiote_out_clear(ENABLE_ISFET_PIN);
}

void timer_handler(nrf_timer_event_t event_type, void * p_context)
{
    // To Add Later
}


void saadc_sampling_event_init(void)
{
    ret_code_t err_code;

    nrf_drv_timer_config_t timer_cfg = NRF_DRV_TIMER_DEFAULT_CONFIG;
    timer_cfg.bit_width = NRF_TIMER_BIT_WIDTH_32;
    err_code = nrf_drv_timer_init(&m_timer, &timer_cfg, timer_handler);
    APP_ERROR_CHECK(err_code);

    /* setup m_timer for compare event every 15us */
    uint32_t ticks = nrf_drv_timer_us_to_ticks(&m_timer, 35);
    nrf_drv_timer_extended_compare(&m_timer,
                                   NRF_TIMER_CC_CHANNEL0,
                                   ticks,
                                   NRF_TIMER_SHORT_COMPARE0_CLEAR_MASK,
                                   false);
    nrf_drv_timer_enable(&m_timer);

    uint32_t timer_compare_event_addr = 
                nrf_drv_timer_compare_event_address_get(&m_timer,
                                                        NRF_TIMER_CC_CHANNEL0);
    uint32_t saadc_sample_task_addr   = nrf_drv_saadc_sample_task_get();

    /* setup ppi channel so that timer compare event triggers task in SAADC */
    err_code = nrf_drv_ppi_channel_alloc(&m_ppi_channel);
    APP_ERROR_CHECK(err_code);

    err_code = nrf_drv_ppi_channel_assign(m_ppi_channel,
                                          timer_compare_event_addr,
                                          saadc_sample_task_addr);
    APP_ERROR_CHECK(err_code);
}


void saadc_sampling_event_enable(void)
{
    ret_code_t err_code = nrf_drv_ppi_channel_enable(m_ppi_channel);
    APP_ERROR_CHECK(err_code);
}


void restart_saadc(void)
{
    nrfx_timer_uninit(&m_timer);
    nrfx_ppi_channel_free(m_ppi_channel);
    nrfx_saadc_uninit();
    while(nrfx_saadc_is_busy()) {
        // make sure SAADC is not busy
    }
    enable_pH_voltage_reading(); 
}

double calculate_pH_from_mV(uint32_t ph_val)
{
    // pH = (ph_val - BVAL_CALIBRATION) / (MVAL_CALIBRATION)
    return ((double)ph_val * MVAL_CALIBRATION) + BVAL_CALIBRATION;
}

// Pack integer values into byte array to send via bluetooth
void create_bluetooth_packet(uint32_t ph_val,
                             uint32_t batt_val,        
                             uint32_t temp_val, 
                             uint8_t* total_packet)
{
    /*
      {0,0,0,0,44,    pH value arr[0-3], comma arr[4]
       0,0,0,0,44,    temperature arr[5-8], comma arr[9]
       0,0,0,0,44,    battery value arr[10-13], commar arr[14]
       0 0 0 0,10};   raw pH value arr[15-18], EOL arr[19]
    */

    uint32_t temp = 0;    // hold intermediate divisions of variables
    uint32_t ASCII_DIG_BASE = 48;

    // If calibration has not been performed, store 0000 in real pH field [0-3],
    // and store the raw SAADC data in the last field [15-18]
    if (!CAL_PERFORMED) {
      temp = ph_val;
      for(int i = 3; i >= 0; i--){
        total_packet[i] = 0 + ASCII_DIG_BASE;
      }
    }
    // If calibration has been performed, store eal pH in [0-3],
    // and store the raw millivolt data in the last field [15-18]
    else if (CAL_PERFORMED) {
      double real_pH  = calculate_pH_from_mV(ph_val);
      double pH_decimal_vals = (real_pH - floor(real_pH)) * 100;
      // Round pH values to 0.25 pH accuracy
      pH_decimal_vals = round(pH_decimal_vals / 25) * 25;
      // If decimals round to 100, increment real pH value and set decimals to 0.00
      if (pH_decimal_vals == 100) {
        real_pH = real_pH + 1.0;
        pH_decimal_vals = 0.00;
      }
      //NRF_LOG_INFO("pH decimals: " NRF_LOG_FLOAT_MARKER " \n", NRF_LOG_FLOAT(pH_decimal_vals));
      // If pH is 9.99 or lower, format with 2 decimal places (4 bytes total)
      if (real_pH < 10.0) {
        total_packet[0] = (uint8_t) ((uint8_t)floor(real_pH) + ASCII_DIG_BASE);
        total_packet[1] = 46;
        total_packet[2] = (uint8_t) (((uint8_t)pH_decimal_vals / 10) + ASCII_DIG_BASE);
        total_packet[3] = (uint8_t) (((uint8_t)pH_decimal_vals % 10) + ASCII_DIG_BASE);
      }
      // If pH is 10.0 or greater, format with 1 decimal place (still 4 bytes total)
      else {
        total_packet[0] = (uint8_t) ((uint8_t)floor(real_pH / 10) + ASCII_DIG_BASE);
        total_packet[1] = (uint8_t) ((uint8_t)floor((uint8_t)real_pH % 10) + ASCII_DIG_BASE);
        total_packet[2] = 46;
        total_packet[3] = (uint8_t) (((uint8_t)pH_decimal_vals / 10) + ASCII_DIG_BASE);
      }
    }
    // Pack temp_val into appropriate location
    // Packing protocol for number abcd: 
    //  [... 0, 0, 0, d] --> [... 0, 0, c, d] --> [... 0, b, c, d] --> [... a, b, c, d]
    temp = temp_val;
    for(int i = 8; i >= 5; i--){
        if (i == 8) total_packet[i] = (uint8_t)(temp % 10 + ASCII_DIG_BASE);
        else {
            temp = temp / 10;
            total_packet[i] = (uint8_t)(temp % 10 + ASCII_DIG_BASE);
        }
    }
    // Pack batt_val into appropriate location
    // Packing protocol for number abcd: 
    //  [... 0, 0, 0, d] --> [... 0, 0, c, d] --> [... 0, b, c, d] --> [... a, b, c, d]
    temp = batt_val;
    for(int i = 13; i >= 10; i--){
        if (i == 13) total_packet[i] = (uint8_t)(temp % 10 + ASCII_DIG_BASE);
        else {
            temp = temp / 10;
            total_packet[i] = (uint8_t)(temp % 10 + ASCII_DIG_BASE);
        }
    }
    // Pack batt_val into appropriate location
    // Packing protocol for number abcd: 
    //  [... 0, 0, 0, d] --> [... 0, 0, c, d] --> [... 0, b, c, d] --> [... a, b, c, d]
    temp = ph_val;
    for(int i = 18; i >= 15; i--){
        if (i == 18) total_packet[i] = (uint8_t)(temp % 10 + ASCII_DIG_BASE);
        else {
            temp = temp / 10;
            total_packet[i] = (uint8_t)(temp % 10 + ASCII_DIG_BASE);
        }
    }
}

uint32_t saadc_result_to_mv(uint32_t saadc_result)
{
    float adc_denom     = 4096.0;
    float adc_ref_mv    = 600.0;
    float adc_prescale  = 5.0;
    float adc_res_in_mv = (((float)saadc_result*adc_ref_mv)/adc_denom) * adc_prescale;

    return (uint32_t)adc_res_in_mv;
}

/**
 * Function is called when SAADC reading event is done. First done event
 * reads pH input, stores in global variable. Second reading stores
 * pH data, combines pH and temp data into a comma-seperated string,
 * then transmits via BLE.
 *
 * BUG: p_buffer[0] is always '0' when reading pH at high frequency.
 *      Workaround is to average values besides 1, divide by 
 *      samples_in_buffer -1 .
 */
void saadc_callback(nrf_drv_saadc_evt_t const * p_event)
{
    if (p_event->type == NRF_DRV_SAADC_EVT_DONE) 
    {
        ret_code_t err_code;
        uint16_t   total_size = 20;
        uint32_t   avg_saadc_reading = 0;
        // Byte array to store total packet
        uint8_t total_packet[] = {48,48,48,48,44,    /* real pH value, comma */
                                  48,48,48,48,44,    /* Temperature, comma */
                                  48,48,48,48,44,    /* Battery value, comma */
                                  48,48,48,48,10};   /* raw pH value, EOL */

        //err_code = nrf_drv_saadc_buffer_convert(p_event->data.done.p_buffer, 
        //                                                    SAMPLES_IN_BUFFER); 
        //APP_ERROR_CHECK(err_code);
        // Sum and average SAADC values
        for (int i = 1; i < SAMPLES_IN_BUFFER; i++)
        {
            if (p_event->data.done.p_buffer[i] < 0) {
                avg_saadc_reading += 0;
            } 
            else {
                avg_saadc_reading += p_event->data.done.p_buffer[i];
            }
            //NRF_LOG_INFO("%d\n", p_event->data.done.p_buffer[i]);
        }

        avg_saadc_reading = avg_saadc_reading/(SAMPLES_IN_BUFFER - 1); 
        // If ph has not been read, read it then restart SAADC to read temp
        if (!PH_IS_READ) {
            AVG_PH_VAL = saadc_result_to_mv(avg_saadc_reading);
            PH_IS_READ = true;
            // Uninit saadc peripheral, restart saadc, enable sampling event
            //NRF_LOG_INFO("read pH val, restarting: %d", AVG_PH_VAL);
            //NRF_LOG_FLUSH();
            restart_saadc();
        } 
        // If pH has been read but not battery, read battery then restart
        else if (!(PH_IS_READ && BATTERY_IS_READ)) {
            AVG_BATT_VAL = saadc_result_to_mv(avg_saadc_reading);
            //NRF_LOG_INFO("read batt val, restarting: %d", AVG_BATT_VAL);
            //NRF_LOG_FLUSH();
            BATTERY_IS_READ = true;
            restart_saadc();
        }
        // Once temp, battery and ph have been read, create and send data in packet
        // or adjust the calibration points as necessary
        else {
            AVG_TEMP_VAL = saadc_result_to_mv(avg_saadc_reading);
            //NRF_LOG_INFO("read temp val: %d\n", AVG_TEMP_VAL);
            //NRF_LOG_FLUSH();

            //NRF_LOG_ERROR( "MVAL: " NRF_LOG_FLOAT_MARKER "\n", NRF_LOG_FLOAT(MVAL_CALIBRATION));
            //NRF_LOG_ERROR( "BVAL: " NRF_LOG_FLOAT_MARKER "\n", NRF_LOG_FLOAT(BVAL_CALIBRATION));
          
            // reset global control boolean
            PH_IS_READ = false;
            BATTERY_IS_READ = false;
  
            // Pack data and send via bluetooth if not in calibration mode
            if (!CAL_MODE) {
              // Create bluetooth data
              create_bluetooth_packet(AVG_PH_VAL, AVG_BATT_VAL, 
                                      AVG_TEMP_VAL, total_packet);

              // Send data
              err_code = ble_nus_data_send(&m_nus, total_packet, 
                                           &total_size, m_conn_handle);
              //APP_ERROR_CHECK(err_code);
              

              // Turn off peripherals
              NRF_LOG_INFO("BLUETOOTH DATA SENT\n");
              //NRF_LOG_FLUSH();
            }

            disable_pH_voltage_reading();
 
            //NRF_LOG_INFO("SAADC DISABLED\n");
            //NRF_LOG_FLUSH();
        }
    }
}


void saadc_blocking_callback(nrf_drv_saadc_evt_t const * p_event)
{
    // Don't need to do anything
}


void init_saadc_for_buffer_conversion(nrf_saadc_channel_config_t channel_config)
{
    ret_code_t err_code;
    err_code = nrf_drv_saadc_init(NULL, saadc_callback);
    APP_ERROR_CHECK(err_code);

    err_code = nrf_drv_saadc_channel_init(0, &channel_config);
    APP_ERROR_CHECK(err_code);

    err_code = nrf_drv_saadc_buffer_convert(m_buffer_pool[0], SAMPLES_IN_BUFFER);
    APP_ERROR_CHECK(err_code);
}

void init_saadc_for_blocking_sample_conversion(nrf_saadc_channel_config_t channel_config)
{
    ret_code_t err_code;
    err_code = nrf_drv_saadc_init(NULL, saadc_blocking_callback);
    APP_ERROR_CHECK(err_code);

    err_code = nrf_drv_saadc_channel_init(0, &channel_config);
    APP_ERROR_CHECK(err_code);
}

/* Reads pH transducer output
 */
void saadc_init(void)
{
    nrf_saadc_input_t ANALOG_INPUT;
    // Change pin depending on global control boolean
    if (!PH_IS_READ) {
        //NRF_LOG_INFO("Setting saadc input to AIN2\n");
        ANALOG_INPUT = NRF_SAADC_INPUT_AIN2;
    }
    else if (!(PH_IS_READ && BATTERY_IS_READ)) {
        //NRF_LOG_INFO("Setting saadc input to AIN3\n");
        ANALOG_INPUT = NRF_SAADC_INPUT_AIN3;
    }
    else {
        //NRF_LOG_INFO("Setting saadc input to AIN1\n");
        ANALOG_INPUT = NRF_SAADC_INPUT_AIN1;        
    }

    nrf_saadc_channel_config_t channel_config =
            NRF_SAADC_CUSTOM_CHANNEL_CONFIG_SE(ANALOG_INPUT);
    
    if (!CAL_MODE)
      init_saadc_for_buffer_conversion(channel_config);
    else 
      init_saadc_for_blocking_sample_conversion(channel_config);
}


/* This function initializes and enables SAADC sampling
 */
void enable_pH_voltage_reading(void)
{
    saadc_init();
    if (!CAL_MODE) {
      saadc_sampling_event_init();
      saadc_sampling_event_enable();
    }
    nrf_pwr_mgmt_run();
}

void restart_pH_interval_timer(void)
{
      ret_code_t err_code;
      err_code = app_timer_start(m_timer_id, APP_TIMER_TICKS(DATA_INTERVAL), NULL);
      APP_ERROR_CHECK(err_code);
      nrf_pwr_mgmt_run();

      //NRF_LOG_INFO("TIMER RESTARTED (disable_ph_voltage_reading)\n");
      //NRF_LOG_FLUSH();
}

/* Function unitializes and disables SAADC sampling, restarts 1 second timer
 */
void disable_pH_voltage_reading(void)
{
    nrfx_timer_uninit(&m_timer);
    nrfx_ppi_channel_free(m_ppi_channel);
    nrfx_saadc_uninit();
    while(nrfx_saadc_is_busy()) {
        // make sure SAADC is not busy
    }

    // *** DISABLE ENABLE ***
    //disable_isfet_circuit();

    if (!CAL_MODE) {
      // Restart timer
      restart_pH_interval_timer();
    }
}

void single_shot_timer_handler()
{
    // disable timer
    ret_code_t err_code;
    err_code = app_timer_stop(m_timer_id);
    APP_ERROR_CHECK(err_code);

    // Delay to ensure appropriate timing 
    enable_isfet_circuit();       
    // PWM output, ISFET capacitor, etc
    nrf_delay_ms(10);              
    // Begin SAADC initialization/start

    /* * * * * * * * * * * * * * *
     *  UNCOMMENT TO SEND DATA
     */

    enable_pH_voltage_reading();

    /*
     *  UNCOMMENT TO SEND DATA
     * * * * * * * * * * * * * * */
}

/**@brief Function for handling events from the GATT library. */
void gatt_evt_handler(nrf_ble_gatt_t * p_gatt, nrf_ble_gatt_evt_t const * p_evt)
{
    if ((m_conn_handle == p_evt->conn_handle) && 
        (p_evt->evt_id == NRF_BLE_GATT_EVT_ATT_MTU_UPDATED))
    {
        m_ble_nus_max_data_len = p_evt->params.att_mtu_effective - OPCODE_LENGTH 
                                                                - HANDLE_LENGTH;
        NRF_LOG_INFO("Data len is set to 0x%X(%d)", m_ble_nus_max_data_len, 
                                                    m_ble_nus_max_data_len);
    }
    NRF_LOG_DEBUG("ATT MTU exchange completed. central 0x%x peripheral 0x%x",
                  p_gatt->att_mtu_desired_central,
                  p_gatt->att_mtu_desired_periph);

    ret_code_t err_code;

    // Create application timer
    err_code = app_timer_create(&m_timer_id,
                                APP_TIMER_MODE_SINGLE_SHOT,
                                single_shot_timer_handler);
    APP_ERROR_CHECK(err_code);
        
    // 1 second timer intervals
    err_code = app_timer_start(m_timer_id, APP_TIMER_TICKS(DATA_INTERVAL), NULL);
    APP_ERROR_CHECK(err_code);

    //NRF_LOG_INFO("TIMER STARTED (gatt_evt_handler) \n");
    NRF_LOG_FLUSH();

      
}

/**@brief Function for initializing the GATT library. */
void gatt_init(void)
{
    ret_code_t err_code;

    err_code = nrf_ble_gatt_init(&m_gatt, gatt_evt_handler);
    APP_ERROR_CHECK(err_code);

    err_code = nrf_ble_gatt_att_mtu_periph_set(&m_gatt, 
                                               NRF_SDH_BLE_GATT_MAX_MTU_SIZE);
    APP_ERROR_CHECK(err_code);
}

// Helper function for linreg
inline static double sqr(double x) {
    return x*x;
}

/*
 * Function for running linear regression on two and three point calibration data
 */
void linreg(int num, double x[], double y[])
{
    double   sumx  = 0.0;                     /* sum of x     */
    double   sumx2 = 0.0;                     /* sum of x**2  */
    double   sumxy = 0.0;                     /* sum of x * y */
    double   sumy  = 0.0;                     /* sum of y     */
    double   sumy2 = 0.0;                     /* sum of y**2  */

    for (int i=0;i<num;i++){ 
        sumx  += x[i];       
        sumx2 += sqr(x[i]);  
        sumxy += x[i] * y[i];
        sumy  += y[i];      
        sumy2 += sqr(y[i]); 
    } 

    double denom = (num * sumx2 - sqr(sumx));

    if (denom == 0) {
        // singular matrix. can't solve the problem.
        NRF_LOG_INFO("singular matrix, cannot solve regression\n");
    }

    MVAL_CALIBRATION = (num * sumxy  -  sumx * sumy) / denom;
    BVAL_CALIBRATION = (sumy * sumx2  -  sumx * sumxy) / denom;
    RVAL_CALIBRATION = (sumxy - sumx * sumy / num) /    
                        sqrt((sumx2 - sqr(sumx)/num) *
                       (sumy2 - sqr(sumy)/num));

    NRF_LOG_INFO("MVAL **CAL**: " NRF_LOG_FLOAT_MARKER "", NRF_LOG_FLOAT(MVAL_CALIBRATION));
    NRF_LOG_INFO("BVAL **CAL**: " NRF_LOG_FLOAT_MARKER "", NRF_LOG_FLOAT(BVAL_CALIBRATION));
    NRF_LOG_INFO("RVAL **CAL**: " NRF_LOG_FLOAT_MARKER " \n", NRF_LOG_FLOAT(RVAL_CALIBRATION));
}


void my_fds_evt_handler(fds_evt_t const * const p_fds_evt)
{
    switch (p_fds_evt->id)
    {
        case FDS_EVT_INIT:
            if (p_fds_evt->result != FDS_SUCCESS)
            {
                NRF_LOG_INFO("ERROR IN EVENT HANDLER\n");
                NRF_LOG_FLUSH();
            }
            break;
        case FDS_EVT_WRITE:
            if (p_fds_evt->result == FDS_SUCCESS)
            {
                write_flag=1;
            }
            break;
        default:
            break;
    }
}

static void fds_write(float value, uint16_t FILE_ID, uint16_t REC_KEY)
{
    fds_record_t       record;
    fds_record_desc_t  record_desc;

    // Set up record.
    record.file_id              = FILE_ID;
    record.key                 = REC_KEY;
    record.data.length_words   = 1;

    if(FILE_ID == MVAL_FILE_ID && REC_KEY == MVAL_REC_KEY){
      record.data.p_data = &MVAL_CALIBRATION;
    }
    else if(FILE_ID == BVAL_FILE_ID && REC_KEY == BVAL_REC_KEY){
      record.data.p_data = &BVAL_CALIBRATION;
    }
    else if(FILE_ID == RVAL_FILE_ID && REC_KEY == RVAL_REC_KEY){
      record.data.p_data = &RVAL_CALIBRATION;
    }
    else if(FILE_ID == CAL_DONE_FILE_ID && REC_KEY == CAL_DONE_REC_KEY) {
      record.data.p_data = &CAL_PERFORMED;
    }
                    
    ret_code_t ret = fds_record_write(&record_desc, &record);
    if (ret != FDS_SUCCESS)
    {
        NRF_LOG_INFO("ERROR WRITING TO FLASH\n");
    }
    NRF_LOG_INFO("SUCCESS WRITING TO FLASH\n");
    NRF_LOG_FLUSH();
}

static void fds_update(float value, uint16_t FILE_ID, uint16_t REC_KEY)
{
    fds_record_t       record;
    fds_record_desc_t  record_desc;
    fds_find_token_t    ftok ={0};

    // Set up record.
    record.file_id              = FILE_ID;
    record.key                 = REC_KEY;
    record.data.length_words   = 1;

    if(FILE_ID == MVAL_FILE_ID && REC_KEY == MVAL_REC_KEY){
      record.data.p_data = &MVAL_CALIBRATION;
      fds_record_find(MVAL_FILE_ID, MVAL_REC_KEY, &record_desc, &ftok);
    }
    else if(FILE_ID == BVAL_FILE_ID && REC_KEY == BVAL_REC_KEY){
      record.data.p_data = &BVAL_CALIBRATION;
      fds_record_find(BVAL_FILE_ID, BVAL_REC_KEY, &record_desc, &ftok);
    }
    else if(FILE_ID == RVAL_FILE_ID && REC_KEY == RVAL_REC_KEY){
      record.data.p_data = &RVAL_CALIBRATION;
      fds_record_find(RVAL_FILE_ID, RVAL_REC_KEY, &record_desc, &ftok);
    }
    else if(FILE_ID == CAL_DONE_FILE_ID && REC_KEY == CAL_DONE_REC_KEY) {
      record.data.p_data = &CAL_PERFORMED;
      fds_record_find(CAL_DONE_FILE_ID, CAL_DONE_REC_KEY, &record_desc, &ftok);
    }
                    
    ret_code_t ret = fds_record_update(&record_desc, &record);
    if (ret != FDS_SUCCESS)
    {
        NRF_LOG_INFO("ERROR WRITING UPDATE TO FLASH\n");
    }
    NRF_LOG_INFO("SUCCESS WRITING UPDATE TO FLASH\n");
    NRF_LOG_FLUSH();
}

float fds_read(uint16_t FILE_ID, uint16_t REC_KEY)
{
    fds_flash_record_t  flash_record;
    fds_record_desc_t  record_desc;
    fds_find_token_t    ftok ={0};//Important, make sure you zero init the ftok token
    float *p_data;
    float data;
    uint32_t err_code;
    
    NRF_LOG_INFO("Start searching... \r\n");
    // Loop until all records with the given key and file ID have been found.
    while (fds_record_find(FILE_ID, REC_KEY, &record_desc, &ftok) == FDS_SUCCESS)
    {
        err_code = fds_record_open(&record_desc, &flash_record);
        if ( err_code != FDS_SUCCESS)
        {
                NRF_LOG_INFO("COULD NOT FIND OR OPEN RECORD\n");	
                return 0.0;
        }

        p_data = (float *) flash_record.p_data;
        data = *p_data;
        for (uint8_t i=0;i<flash_record.p_header->length_words;i++)
        {
                NRF_LOG_INFO("Data read: " NRF_LOG_FLOAT_MARKER "\n", NRF_LOG_FLOAT(data));
        }
        NRF_LOG_INFO("\r\n");
        // Access the record through the flash_record structure.
        // Close the record when done.
        err_code = fds_record_close(&record_desc);
        if (err_code != FDS_SUCCESS)
        {
                NRF_LOG_INFO("ERROR CLOSING RECORD\n");
        }
        NRF_LOG_FLUSH();
    }
    NRF_LOG_INFO("SUCCESS CLOSING RECORD\n");
    return data;
}

static void fds_find_and_delete(uint16_t FILE_ID, uint16_t REC_KEY)
{
    fds_record_desc_t  record_desc;
    fds_find_token_t    ftok;
	
    ftok.page=0;
    ftok.p_addr=NULL;
    // Loop and find records with same ID and rec key and mark them as deleted. 
    while (fds_record_find(FILE_ID, REC_KEY, &record_desc, &ftok) == FDS_SUCCESS)
    {
        fds_record_delete(&record_desc);
        NRF_LOG_INFO("Deleted record ID: %d \r\n",record_desc.record_id);
    }
    // call the garbage collector to empty them, don't need to do this all the time, 
    // this is just for demonstration
    ret_code_t ret = fds_gc();
    if (ret != FDS_SUCCESS)
    {
        NRF_LOG_INFO("ERROR DELETING RECORD\n");
    }
    NRF_LOG_INFO("RECORD DELETED SUCCESFULLY\n");
}

static void fds_init_helper(void)
{	
    ret_code_t ret = fds_register(my_fds_evt_handler);
    if (ret != FDS_SUCCESS)
    {
        NRF_LOG_INFO("ERROR, COULD NOT REGISTER FDS\n");
                    
    }
    ret = fds_init();
    if (ret != FDS_SUCCESS)
    {
        NRF_LOG_INFO("ERROR, COULD NOT INIT FDS\n");
    }
    
    NRF_LOG_INFO("FDS INIT\n");	
}

/* Check words used in fds after initialization, and if more than 4 (default)
 * words are used then read MVAL, BVAL and RVAL values stored in flash. Assign
 * stored values to global variables respectively
 */
static void check_calibration_state(void)
{
    fds_stat_t fds_info;
    fds_stat(&fds_info);
    NRF_LOG_INFO("open records: %u, words used: %u\n", fds_info.open_records, 
                                                       fds_info.words_used);
    // fds_read will return 0 if the CAL_DONE record does not exist, 
    // or if the stored value is 0
    if(fds_read(CAL_DONE_FILE_ID, CAL_DONE_REC_KEY)) {
      CAL_PERFORMED = 1.0;
      NRF_LOG_INFO("Setting CAL_PERFORMED to true\n");
      // Read values stored in flash and set to respective global variables
      MVAL_CALIBRATION = fds_read(MVAL_FILE_ID, MVAL_REC_KEY);
      BVAL_CALIBRATION = fds_read(BVAL_FILE_ID, BVAL_REC_KEY);
      RVAL_CALIBRATION = fds_read(RVAL_FILE_ID, RVAL_REC_KEY);
      NRF_LOG_INFO("MVAL: " NRF_LOG_FLOAT_MARKER " \n", NRF_LOG_FLOAT(MVAL_CALIBRATION));
      NRF_LOG_INFO("BVAL: " NRF_LOG_FLOAT_MARKER " \n", NRF_LOG_FLOAT(BVAL_CALIBRATION));
      NRF_LOG_INFO("RVAL: " NRF_LOG_FLOAT_MARKER " \n", NRF_LOG_FLOAT(RVAL_CALIBRATION));
    }
}

/* If calibration has already been performed then update existing records with new 
 * values. If calibration has not already been performed, then write values to
 * new records
 */
void write_cal_values_to_flash(void) 
{
    // Update the existing flash records
    if (CAL_PERFORMED) {
        fds_update(MVAL_CALIBRATION, MVAL_FILE_ID,     MVAL_REC_KEY);
        fds_update(BVAL_CALIBRATION, BVAL_FILE_ID,     BVAL_REC_KEY);
        fds_update(RVAL_CALIBRATION, RVAL_FILE_ID,     RVAL_REC_KEY);
        fds_update(CAL_PERFORMED,    CAL_DONE_FILE_ID, CAL_DONE_REC_KEY);
    }
    // Write values to new records
    else {
        fds_write(MVAL_CALIBRATION, MVAL_FILE_ID,     MVAL_REC_KEY);
        fds_write(BVAL_CALIBRATION, BVAL_FILE_ID,     BVAL_REC_KEY);
        fds_write(RVAL_CALIBRATION, RVAL_FILE_ID,     RVAL_REC_KEY);
        fds_write(CAL_PERFORMED,    CAL_DONE_FILE_ID, CAL_DONE_REC_KEY);
    }
}
      


/**@brief Application main function.
 */
int main(void)
{
    bool erase_bonds = false;

    // Call function very first to turn on the chip
    turn_chip_power_on();
    enable_isfet_circuit();

    log_init();
    timers_init();
    power_management_init();

    // Initialize fds and check for calibration values
    fds_init_helper();
    check_calibration_state();

    // Continue with adjusted calibration state
    ble_stack_init();
    gap_params_init();
    gatt_init();
    services_init();
    advertising_init();
    conn_params_init();
    peer_manager_init();
    advertising_start(erase_bonds);

    // Enter main loop.
    while (true)
    {
        idle_state_handle();
    } 
}

/*
 * @}
 */

For reference, I actually have two different custom-built mobile applications that can succesfully connect to and read data from this nRF52 DK application as it is written right now. This application also works perfectly fine with nRF Connect, Adafruit BluefruitLE, and other uart central mobile applications (both iOS and Android). I think I am clearly forgetting some important step in the connection/pairing process that is handled automatically/more easily on mobile phone operating systems. Any help or insight would be greatly appreciated!

Parents
  • Update - when I use nRF Connect on my PC with the nRF dongle, when I click the little blue play button under "UART over BLE" service after connecting to my nRF52 DK, I get "write operation failed: ble_gatt_status_atterr_insuf_authentication (0x0105)" error. This definitely means I am perhaps doing something wrong on the peripheral side, although it is really strange my app works fine with the mobile apps.

Reply
  • Update - when I use nRF Connect on my PC with the nRF dongle, when I click the little blue play button under "UART over BLE" service after connecting to my nRF52 DK, I get "write operation failed: ble_gatt_status_atterr_insuf_authentication (0x0105)" error. This definitely means I am perhaps doing something wrong on the peripheral side, although it is really strange my app works fine with the mobile apps.

Children
Related