Openwear requires RC oscillator to be used

Fork of nRF51822 by Nordic Semiconductor

Committer:
Rohit Grover
Date:
Tue Sep 02 15:50:05 2014 +0100
Revision:
56:a1071b629aa3
Release 0.1.0
=============

We've achieved significant gains in power consumption: the BLE_Beacon demo now
runs at around 35uA of average current broadcasting once a second at 0dB; when
not using the radio, this demo consumes around 7uA.

Features
~~~~~~~~

- Replace initialization of high-frequency external crystal clock-source with
the use of low-frequency clock. This brings in significant gains in power
consumption.

- Re-implement the micro-second timer on nRF51 using the app_timer module
(which internally uses RTC). This limits the precision of the us_Timer to
30uS; but brings in significant gains in power consumption.

- Reduce the number of available app_timers and the event depths for app-timer
events; this will reduce memory consumption for zero-initialized data by
around 1K.

- Remove the call to conn_params_init() at startup. This is not mandatory; and
was causing an unnecessary re-negotiation of connection parameters a few
seconds into every connection.

- Reduce default transmission power level to 0dbB (was 4dbB before).

- Reduce min connection interval to 50ms and max to 500ms (previous values
were much larger).

- Replace a few instances of use of wait() with nrf_delay_us().

- onConnection() callback now receives connection-parameters applicable to the
new connection.

- onDataSent() callback now receives a count parameter containing the number of
times notifications were sent out since the last callback.

- A 'reason' parameter has been added to Gap::disconnect() to indicate the
reason for disconnection; and also to the onDisconnection callback to
receive a reason from the remote host.

- disable the app_gpiote module by default.

Bugfixes
~~~~~~~~

- onDataWritten() callback now passes an additional parameter
(GattServer::WriteEventCallback_t) encapsulating the update. This avoids
having to re-fetch the updated characteristic's value attribute. It also
fixes a bug where multiple updates to the characteristic's value-attribute
could get clobbered if they occurred in quick succession before the
callbacks could be processed.


Compatibility
~~~~~~~~~~~~~

Compatible with revision 0.1.0 of the BLE_API.

Who changed what in which revision?

UserRevisionLine numberNew contents of line
Rohit Grover 56:a1071b629aa3 1 /* Copyright (c) 2012 Nordic Semiconductor. All Rights Reserved.
Rohit Grover 56:a1071b629aa3 2 *
Rohit Grover 56:a1071b629aa3 3 * The information contained herein is property of Nordic Semiconductor ASA.
Rohit Grover 56:a1071b629aa3 4 * Terms and conditions of usage are described in detail in NORDIC
Rohit Grover 56:a1071b629aa3 5 * SEMICONDUCTOR STANDARD SOFTWARE LICENSE AGREEMENT.
Rohit Grover 56:a1071b629aa3 6 *
Rohit Grover 56:a1071b629aa3 7 * Licensees are granted free, non-transferable use of the information. NO
Rohit Grover 56:a1071b629aa3 8 * WARRANTY of ANY KIND is provided. This heading must NOT be removed from
Rohit Grover 56:a1071b629aa3 9 * the file.
Rohit Grover 56:a1071b629aa3 10 *
Rohit Grover 56:a1071b629aa3 11 */
Rohit Grover 56:a1071b629aa3 12
Rohit Grover 56:a1071b629aa3 13 #include "app_button.h"
Rohit Grover 56:a1071b629aa3 14 #include <string.h>
Rohit Grover 56:a1071b629aa3 15 #include "nordic_common.h"
Rohit Grover 56:a1071b629aa3 16 #include "app_util.h"
Rohit Grover 56:a1071b629aa3 17 #include "app_gpiote.h"
Rohit Grover 56:a1071b629aa3 18 #include "app_timer.h"
Rohit Grover 56:a1071b629aa3 19 #include "app_error.h"
Rohit Grover 56:a1071b629aa3 20
Rohit Grover 56:a1071b629aa3 21
Rohit Grover 56:a1071b629aa3 22 static app_button_cfg_t * mp_buttons = NULL; /**< Button configuration. */
Rohit Grover 56:a1071b629aa3 23 static uint8_t m_button_count; /**< Number of configured buttons. */
Rohit Grover 56:a1071b629aa3 24 static uint32_t m_detection_delay; /**< Delay before a button is reported as pushed. */
Rohit Grover 56:a1071b629aa3 25 static app_button_evt_schedule_func_t m_evt_schedule_func; /**< Pointer to function for propagating button events to the scheduler. */
Rohit Grover 56:a1071b629aa3 26 static app_gpiote_user_id_t m_gpiote_user_id; /**< GPIOTE user id for buttons module. */
Rohit Grover 56:a1071b629aa3 27 static app_timer_id_t m_detection_delay_timer_id; /**< Polling timer id. */
Rohit Grover 56:a1071b629aa3 28 static pin_transition_t m_pin_transition; /**< pin transaction direction. */
Rohit Grover 56:a1071b629aa3 29
Rohit Grover 56:a1071b629aa3 30
Rohit Grover 56:a1071b629aa3 31 /**@brief Function for executing the application button handler for specified button.
Rohit Grover 56:a1071b629aa3 32 *
Rohit Grover 56:a1071b629aa3 33 * @param[in] p_btn Button that has been pushed.
Rohit Grover 56:a1071b629aa3 34 */
Rohit Grover 56:a1071b629aa3 35 static void button_handler_execute(app_button_cfg_t * p_btn, uint32_t transition)
Rohit Grover 56:a1071b629aa3 36 {
Rohit Grover 56:a1071b629aa3 37 if (m_evt_schedule_func != NULL)
Rohit Grover 56:a1071b629aa3 38 {
Rohit Grover 56:a1071b629aa3 39 uint32_t err_code = m_evt_schedule_func(p_btn->button_handler, p_btn->pin_no,transition);
Rohit Grover 56:a1071b629aa3 40 APP_ERROR_CHECK(err_code);
Rohit Grover 56:a1071b629aa3 41 }
Rohit Grover 56:a1071b629aa3 42 else
Rohit Grover 56:a1071b629aa3 43 {
Rohit Grover 56:a1071b629aa3 44 if(transition == APP_BUTTON_PUSH)
Rohit Grover 56:a1071b629aa3 45 {
Rohit Grover 56:a1071b629aa3 46 p_btn->button_handler(p_btn->pin_no, APP_BUTTON_PUSH);
Rohit Grover 56:a1071b629aa3 47 }
Rohit Grover 56:a1071b629aa3 48 else if(transition == APP_BUTTON_RELEASE)
Rohit Grover 56:a1071b629aa3 49 {
Rohit Grover 56:a1071b629aa3 50 p_btn->button_handler(p_btn->pin_no, APP_BUTTON_RELEASE);
Rohit Grover 56:a1071b629aa3 51 }
Rohit Grover 56:a1071b629aa3 52 }
Rohit Grover 56:a1071b629aa3 53 }
Rohit Grover 56:a1071b629aa3 54
Rohit Grover 56:a1071b629aa3 55
Rohit Grover 56:a1071b629aa3 56 /**@brief Function for handling the timeout that delays reporting buttons as pushed.
Rohit Grover 56:a1071b629aa3 57 *
Rohit Grover 56:a1071b629aa3 58 * @details The detection_delay_timeout_handler(...) is a call-back issued from the app_timer
Rohit Grover 56:a1071b629aa3 59 * module. It is called with the p_context parameter. The p_context parameter is
Rohit Grover 56:a1071b629aa3 60 * provided to the app_timer module when a timer is started, using the call
Rohit Grover 56:a1071b629aa3 61 * @ref app_timer_start. On @ref app_timer_start the p_context will be holding the
Rohit Grover 56:a1071b629aa3 62 * currently pressed buttons.
Rohit Grover 56:a1071b629aa3 63 *
Rohit Grover 56:a1071b629aa3 64 * @param[in] p_context Pointer used for passing information app_start_timer() was called.
Rohit Grover 56:a1071b629aa3 65 * In the app_button module the p_context holds information on pressed
Rohit Grover 56:a1071b629aa3 66 * buttons.
Rohit Grover 56:a1071b629aa3 67 */
Rohit Grover 56:a1071b629aa3 68 static void detection_delay_timeout_handler(void * p_context)
Rohit Grover 56:a1071b629aa3 69 {
Rohit Grover 56:a1071b629aa3 70 uint32_t err_code;
Rohit Grover 56:a1071b629aa3 71 uint32_t current_state_pins;
Rohit Grover 56:a1071b629aa3 72
Rohit Grover 56:a1071b629aa3 73 // Get current state of pins.
Rohit Grover 56:a1071b629aa3 74 err_code = app_gpiote_pins_state_get(m_gpiote_user_id, &current_state_pins);
Rohit Grover 56:a1071b629aa3 75
Rohit Grover 56:a1071b629aa3 76 if (err_code != NRF_SUCCESS)
Rohit Grover 56:a1071b629aa3 77 {
Rohit Grover 56:a1071b629aa3 78 return;
Rohit Grover 56:a1071b629aa3 79 }
Rohit Grover 56:a1071b629aa3 80
Rohit Grover 56:a1071b629aa3 81 uint8_t i;
Rohit Grover 56:a1071b629aa3 82
Rohit Grover 56:a1071b629aa3 83 // Pushed button(s) detected, execute button handler(s).
Rohit Grover 56:a1071b629aa3 84 for (i = 0; i < m_button_count; i++)
Rohit Grover 56:a1071b629aa3 85 {
Rohit Grover 56:a1071b629aa3 86 app_button_cfg_t * p_btn = &mp_buttons[i];
Rohit Grover 56:a1071b629aa3 87
Rohit Grover 56:a1071b629aa3 88 if (((m_pin_transition.high_to_low & (1 << p_btn->pin_no)) != 0) && (p_btn->button_handler != NULL))
Rohit Grover 56:a1071b629aa3 89 {
Rohit Grover 56:a1071b629aa3 90 //If it's active high then going from high to low was a release of the button.
Rohit Grover 56:a1071b629aa3 91 if(p_btn->active_state == APP_BUTTON_ACTIVE_HIGH)
Rohit Grover 56:a1071b629aa3 92 {
Rohit Grover 56:a1071b629aa3 93 button_handler_execute(p_btn, APP_BUTTON_RELEASE);
Rohit Grover 56:a1071b629aa3 94 }
Rohit Grover 56:a1071b629aa3 95 //If it's active low then going from high to low was a push of the button.
Rohit Grover 56:a1071b629aa3 96 else
Rohit Grover 56:a1071b629aa3 97 {
Rohit Grover 56:a1071b629aa3 98 button_handler_execute(p_btn, APP_BUTTON_PUSH);
Rohit Grover 56:a1071b629aa3 99 }
Rohit Grover 56:a1071b629aa3 100 }
Rohit Grover 56:a1071b629aa3 101 else if (((m_pin_transition.low_to_high & (1 << p_btn->pin_no)) != 0) && (p_btn->button_handler != NULL))
Rohit Grover 56:a1071b629aa3 102 {
Rohit Grover 56:a1071b629aa3 103 //If it's active high then going from low to high was a push of the button.
Rohit Grover 56:a1071b629aa3 104 if(p_btn->active_state == APP_BUTTON_ACTIVE_HIGH)
Rohit Grover 56:a1071b629aa3 105 {
Rohit Grover 56:a1071b629aa3 106 button_handler_execute(p_btn,APP_BUTTON_PUSH);
Rohit Grover 56:a1071b629aa3 107 }
Rohit Grover 56:a1071b629aa3 108 //If it's active low then going from low to high was a release of the button.
Rohit Grover 56:a1071b629aa3 109 else
Rohit Grover 56:a1071b629aa3 110 {
Rohit Grover 56:a1071b629aa3 111 button_handler_execute(p_btn,APP_BUTTON_RELEASE);
Rohit Grover 56:a1071b629aa3 112 }
Rohit Grover 56:a1071b629aa3 113 }
Rohit Grover 56:a1071b629aa3 114 }
Rohit Grover 56:a1071b629aa3 115 }
Rohit Grover 56:a1071b629aa3 116
Rohit Grover 56:a1071b629aa3 117
Rohit Grover 56:a1071b629aa3 118 /**@brief Function for handling the GPIOTE event.
Rohit Grover 56:a1071b629aa3 119 *
Rohit Grover 56:a1071b629aa3 120 * @details Saves the current status of the button pins, and starts a timer. If the timer is already
Rohit Grover 56:a1071b629aa3 121 * running, it will be restarted.
Rohit Grover 56:a1071b629aa3 122 *
Rohit Grover 56:a1071b629aa3 123 * @param[in] event_pins_low_to_high Mask telling which pin(s) had a low to high transition.
Rohit Grover 56:a1071b629aa3 124 * @param[in] event_pins_high_to_low Mask telling which pin(s) had a high to low transition.
Rohit Grover 56:a1071b629aa3 125 */
Rohit Grover 56:a1071b629aa3 126 static void gpiote_event_handler(uint32_t event_pins_low_to_high, uint32_t event_pins_high_to_low)
Rohit Grover 56:a1071b629aa3 127 {
Rohit Grover 56:a1071b629aa3 128 uint32_t err_code;
Rohit Grover 56:a1071b629aa3 129
Rohit Grover 56:a1071b629aa3 130 // Start detection timer. If timer is already running, the detection period is restarted.
Rohit Grover 56:a1071b629aa3 131 // NOTE: Using the p_context parameter of app_timer_start() to transfer the pin states to the
Rohit Grover 56:a1071b629aa3 132 // timeout handler (by casting event_pins_mask into the equally sized void * p_context
Rohit Grover 56:a1071b629aa3 133 // parameter).
Rohit Grover 56:a1071b629aa3 134 STATIC_ASSERT(sizeof(void *) == sizeof(uint32_t));
Rohit Grover 56:a1071b629aa3 135
Rohit Grover 56:a1071b629aa3 136 err_code = app_timer_stop(m_detection_delay_timer_id);
Rohit Grover 56:a1071b629aa3 137 if (err_code != NRF_SUCCESS)
Rohit Grover 56:a1071b629aa3 138 {
Rohit Grover 56:a1071b629aa3 139 // The impact in app_button of the app_timer queue running full is losing a button press.
Rohit Grover 56:a1071b629aa3 140 // The current implementation ensures that the system will continue working as normal.
Rohit Grover 56:a1071b629aa3 141 return;
Rohit Grover 56:a1071b629aa3 142 }
Rohit Grover 56:a1071b629aa3 143
Rohit Grover 56:a1071b629aa3 144 m_pin_transition.low_to_high = event_pins_low_to_high;
Rohit Grover 56:a1071b629aa3 145 m_pin_transition.high_to_low = event_pins_high_to_low;
Rohit Grover 56:a1071b629aa3 146
Rohit Grover 56:a1071b629aa3 147 err_code = app_timer_start(m_detection_delay_timer_id,
Rohit Grover 56:a1071b629aa3 148 m_detection_delay,
Rohit Grover 56:a1071b629aa3 149 (void *)(event_pins_low_to_high | event_pins_high_to_low));
Rohit Grover 56:a1071b629aa3 150 if (err_code != NRF_SUCCESS)
Rohit Grover 56:a1071b629aa3 151 {
Rohit Grover 56:a1071b629aa3 152 // The impact in app_button of the app_timer queue running full is losing a button press.
Rohit Grover 56:a1071b629aa3 153 // The current implementation ensures that the system will continue working as normal.
Rohit Grover 56:a1071b629aa3 154 }
Rohit Grover 56:a1071b629aa3 155 }
Rohit Grover 56:a1071b629aa3 156
Rohit Grover 56:a1071b629aa3 157
Rohit Grover 56:a1071b629aa3 158 uint32_t app_button_init(app_button_cfg_t * p_buttons,
Rohit Grover 56:a1071b629aa3 159 uint8_t button_count,
Rohit Grover 56:a1071b629aa3 160 uint32_t detection_delay,
Rohit Grover 56:a1071b629aa3 161 app_button_evt_schedule_func_t evt_schedule_func)
Rohit Grover 56:a1071b629aa3 162 {
Rohit Grover 56:a1071b629aa3 163 uint32_t err_code;
Rohit Grover 56:a1071b629aa3 164
Rohit Grover 56:a1071b629aa3 165 if (detection_delay < APP_TIMER_MIN_TIMEOUT_TICKS)
Rohit Grover 56:a1071b629aa3 166 {
Rohit Grover 56:a1071b629aa3 167 return NRF_ERROR_INVALID_PARAM;
Rohit Grover 56:a1071b629aa3 168 }
Rohit Grover 56:a1071b629aa3 169
Rohit Grover 56:a1071b629aa3 170 // Save configuration.
Rohit Grover 56:a1071b629aa3 171 mp_buttons = p_buttons;
Rohit Grover 56:a1071b629aa3 172 m_button_count = button_count;
Rohit Grover 56:a1071b629aa3 173 m_detection_delay = detection_delay;
Rohit Grover 56:a1071b629aa3 174 m_evt_schedule_func = evt_schedule_func;
Rohit Grover 56:a1071b629aa3 175
Rohit Grover 56:a1071b629aa3 176 // Configure pins.
Rohit Grover 56:a1071b629aa3 177 uint32_t pins_transition_mask = 0;
Rohit Grover 56:a1071b629aa3 178
Rohit Grover 56:a1071b629aa3 179 while (button_count--)
Rohit Grover 56:a1071b629aa3 180 {
Rohit Grover 56:a1071b629aa3 181 app_button_cfg_t * p_btn = &p_buttons[button_count];
Rohit Grover 56:a1071b629aa3 182
Rohit Grover 56:a1071b629aa3 183 // Configure pin.
Rohit Grover 56:a1071b629aa3 184 nrf_gpio_cfg_input(p_btn->pin_no, p_btn->pull_cfg);
Rohit Grover 56:a1071b629aa3 185
Rohit Grover 56:a1071b629aa3 186 // Build GPIOTE user registration masks.
Rohit Grover 56:a1071b629aa3 187 pins_transition_mask |= (1 << p_btn->pin_no);
Rohit Grover 56:a1071b629aa3 188 }
Rohit Grover 56:a1071b629aa3 189
Rohit Grover 56:a1071b629aa3 190 // Register button module as a GPIOTE user.
Rohit Grover 56:a1071b629aa3 191 err_code = app_gpiote_user_register(&m_gpiote_user_id,
Rohit Grover 56:a1071b629aa3 192 pins_transition_mask,
Rohit Grover 56:a1071b629aa3 193 pins_transition_mask,
Rohit Grover 56:a1071b629aa3 194 gpiote_event_handler);
Rohit Grover 56:a1071b629aa3 195 if (err_code != NRF_SUCCESS)
Rohit Grover 56:a1071b629aa3 196 {
Rohit Grover 56:a1071b629aa3 197 return err_code;
Rohit Grover 56:a1071b629aa3 198 }
Rohit Grover 56:a1071b629aa3 199
Rohit Grover 56:a1071b629aa3 200 // Create polling timer.
Rohit Grover 56:a1071b629aa3 201 return app_timer_create(&m_detection_delay_timer_id,
Rohit Grover 56:a1071b629aa3 202 APP_TIMER_MODE_SINGLE_SHOT,
Rohit Grover 56:a1071b629aa3 203 detection_delay_timeout_handler);
Rohit Grover 56:a1071b629aa3 204 }
Rohit Grover 56:a1071b629aa3 205
Rohit Grover 56:a1071b629aa3 206
Rohit Grover 56:a1071b629aa3 207 uint32_t app_button_enable(void)
Rohit Grover 56:a1071b629aa3 208 {
Rohit Grover 56:a1071b629aa3 209 if (mp_buttons == NULL)
Rohit Grover 56:a1071b629aa3 210 {
Rohit Grover 56:a1071b629aa3 211 return NRF_ERROR_INVALID_STATE;
Rohit Grover 56:a1071b629aa3 212 }
Rohit Grover 56:a1071b629aa3 213
Rohit Grover 56:a1071b629aa3 214 return app_gpiote_user_enable(m_gpiote_user_id);
Rohit Grover 56:a1071b629aa3 215 }
Rohit Grover 56:a1071b629aa3 216
Rohit Grover 56:a1071b629aa3 217
Rohit Grover 56:a1071b629aa3 218 uint32_t app_button_disable(void)
Rohit Grover 56:a1071b629aa3 219 {
Rohit Grover 56:a1071b629aa3 220 uint32_t err_code;
Rohit Grover 56:a1071b629aa3 221
Rohit Grover 56:a1071b629aa3 222 if (mp_buttons == NULL)
Rohit Grover 56:a1071b629aa3 223 {
Rohit Grover 56:a1071b629aa3 224 return NRF_ERROR_INVALID_STATE;
Rohit Grover 56:a1071b629aa3 225 }
Rohit Grover 56:a1071b629aa3 226
Rohit Grover 56:a1071b629aa3 227 err_code = app_gpiote_user_disable(m_gpiote_user_id);
Rohit Grover 56:a1071b629aa3 228
Rohit Grover 56:a1071b629aa3 229 if (err_code != NRF_SUCCESS)
Rohit Grover 56:a1071b629aa3 230 {
Rohit Grover 56:a1071b629aa3 231 return err_code;
Rohit Grover 56:a1071b629aa3 232 }
Rohit Grover 56:a1071b629aa3 233
Rohit Grover 56:a1071b629aa3 234 // Make sure polling timer is not running.
Rohit Grover 56:a1071b629aa3 235 return app_timer_stop(m_detection_delay_timer_id);
Rohit Grover 56:a1071b629aa3 236 }
Rohit Grover 56:a1071b629aa3 237
Rohit Grover 56:a1071b629aa3 238
Rohit Grover 56:a1071b629aa3 239 uint32_t app_button_is_pushed(uint8_t pin_no, bool * p_is_pushed)
Rohit Grover 56:a1071b629aa3 240 {
Rohit Grover 56:a1071b629aa3 241 uint32_t err_code;
Rohit Grover 56:a1071b629aa3 242 uint32_t active_pins;
Rohit Grover 56:a1071b629aa3 243
Rohit Grover 56:a1071b629aa3 244 app_button_cfg_t * p_btn = &mp_buttons[pin_no];
Rohit Grover 56:a1071b629aa3 245
Rohit Grover 56:a1071b629aa3 246 if (mp_buttons == NULL)
Rohit Grover 56:a1071b629aa3 247 {
Rohit Grover 56:a1071b629aa3 248 return NRF_ERROR_INVALID_STATE;
Rohit Grover 56:a1071b629aa3 249 }
Rohit Grover 56:a1071b629aa3 250
Rohit Grover 56:a1071b629aa3 251 err_code = app_gpiote_pins_state_get(m_gpiote_user_id, &active_pins);
Rohit Grover 56:a1071b629aa3 252
Rohit Grover 56:a1071b629aa3 253 if (err_code != NRF_SUCCESS)
Rohit Grover 56:a1071b629aa3 254 {
Rohit Grover 56:a1071b629aa3 255 return err_code;
Rohit Grover 56:a1071b629aa3 256 }
Rohit Grover 56:a1071b629aa3 257
Rohit Grover 56:a1071b629aa3 258 if(p_btn->active_state == APP_BUTTON_ACTIVE_LOW)
Rohit Grover 56:a1071b629aa3 259 {
Rohit Grover 56:a1071b629aa3 260 // If the pin is active low, then the pin being high means it is not pushed.
Rohit Grover 56:a1071b629aa3 261 if(((active_pins >> pin_no) & 0x01))
Rohit Grover 56:a1071b629aa3 262 {
Rohit Grover 56:a1071b629aa3 263 *p_is_pushed = false;
Rohit Grover 56:a1071b629aa3 264 }
Rohit Grover 56:a1071b629aa3 265 else
Rohit Grover 56:a1071b629aa3 266 {
Rohit Grover 56:a1071b629aa3 267 *p_is_pushed = true;
Rohit Grover 56:a1071b629aa3 268 }
Rohit Grover 56:a1071b629aa3 269 }
Rohit Grover 56:a1071b629aa3 270 else if(p_btn->active_state == APP_BUTTON_ACTIVE_HIGH)
Rohit Grover 56:a1071b629aa3 271 {
Rohit Grover 56:a1071b629aa3 272 // If the pin is active high, then the pin being high means it is pushed.
Rohit Grover 56:a1071b629aa3 273 if(((active_pins >> pin_no) & 0x01))
Rohit Grover 56:a1071b629aa3 274 {
Rohit Grover 56:a1071b629aa3 275 *p_is_pushed = true;
Rohit Grover 56:a1071b629aa3 276 }
Rohit Grover 56:a1071b629aa3 277 else
Rohit Grover 56:a1071b629aa3 278 {
Rohit Grover 56:a1071b629aa3 279 *p_is_pushed = false;
Rohit Grover 56:a1071b629aa3 280 }
Rohit Grover 56:a1071b629aa3 281 }
Rohit Grover 56:a1071b629aa3 282
Rohit Grover 56:a1071b629aa3 283 return NRF_SUCCESS;
Rohit Grover 56:a1071b629aa3 284 }