2018年5月22日 星期二

ble MESH:how to delete a provisioned-node in Serial example (Pyaci)

In this April , Nordic Semiconductor release their Mesh SDK version 2.0.1
in Serial example, this example let:
- a nRF52 connect with a Windows or embedded system with UART
- a nRF52 (program serial example)act as provisioner and config client
- the provision and config records store in JSON database in  a Windows or embedded system
- the Windows or embedded system  control the nRF52 with python script
(named  interactive_pyaci.py)

for detail plz check in Nordic website:
serial example 


However the scripts doesn't have function to delete provisioned records.
That means if the node is reset, this serial example can not provision it again.
Or, if a node leave this Mesh network, the provision is not able to delete it.

In this article , I implement a script to delete the record.
name as delete_provisioned_node.py


#power by Agatha Kuan 05/16/2018
import json as JSON
import os
import sys

def check_provisioned_node(file_name):
    with open(file_name) as f:
        load_in = JSON.load(f)
        print(load_in["nodes"])


        for p in load_in["nodes"]:
            print("device name = "+p["name"])


def delete_node(undeleted_node,file_name):
    with open(file_name) as f:
        load_in = JSON.load(f)
        for p in load_in["nodes"]:
            print("list name = "+p["name"])

        for i in range(len(load_in["nodes"])):
            if load_in["nodes"][i]["name"] == undeleted_node:
                load_in["nodes"].pop(i)
                print("success delete ", undeleted_node)
                break

        open(file_name, "w").write(JSON.dumps(load_in, sort_keys=False, indent=4, separators=(',', ':')))


def main():
#sys.argv[0]  是這個腳本的檔名
    if("-c" == sys.argv[1]):
        print("check recent node")
        print("open file:", sys.argv[2])
        check_provisioned_node(sys.argv[2])

    elif("-d" == sys.argv[1]):
        print("delete device :",sys.argv[2])
        delete_node(sys.argv[2],sys.argv[3])

    elif("?" == sys.argv[1]):
        print("Functions:")
        print("1.for check provisioned node:")
        print("     -c FILE_NAME e.g -c example_database.json")
        print("2.after checking nodes, to select which to delete:")
        print("     -d DEVICE_NAME FILE_NAME e.g -d node_01 example_database.json")




if __name__ == "__main__":
    main()


Demostration:
I provision a node "0577"

in database will add a nodes record:

"nodes": [
    {
      "UUID": "0059ffff0000000009f5cf71a0100577",
      "appKeys": [
        0
      ],
      "cid": "0059",
      "configComplete": false,
      "crpl": 32,
      "deviceKey": "dd5baeb65d8ab9eb709c45397a285ab4",
      "elements": [
        {
          "index": 0,
          "location": "0000",
          "models": [
            {
              "modelId": "0000"
            },
            {
              "modelId": "0002"
            },
            {
              "bind": [
                0
              ],
              "modelId": "00590000"
            }
          ]
        }
      ],
      "features": {
        "friend": 2,
        "lowPower": 2,
        "proxy": 2,
        "relay": 0
      },
      "name": "0577",
      "netKeys": [
        0
      ],
      "pid": "0000",
      "security": "low",
      "unicastAddress": 16,
      "vid": "0000"
    }
]

Than put "delete_provisioned_node.py " in :
../script/interactive_pyaci/database


and run it in CMD :

$ database python delete_provisioned_node.py ?

it will show you operation command



So, if I want to delete "0577" , command

$ database python delete_provisioned_node.py -d 0577 example_database.json



reference:
http://infocenter.nordicsemi.com/index.jsp?topic=%2Fcom.nordic.infocenter.meshsdk.v2.0.1%2Fmd_scripts_interactive_pyaci_doc_demo_configuration.html&cp=4_1_0_1_1_3_2


2018年3月23日 星期五

ble MESH: activate nRF_Driver/Libraries functions on ble MESH SDK(app uart for example)

in recent nordic ble mesh sdk v1.01, external drivers or libraries  are not fully implement, and the correct ways to activate them,like timers, PWM, SAADC...etc, are totally different from BLE SDK(e.g SDK14.02).

What can we do if we need those drivers or libraries? nordic semiconductor's websites  don't have any message about exactly how to  use an example from the nRF5 SDK in combination with the mesh stack.

In this article, I will present
- how to integrate external drivers or libraries in BLE SDK into ble MESH SDK
- activate them with SEGGER embedded Studio

I use UART function for example.

PLATFORM:
device: nrf52832_xxaa
softdevice:S132
project: light_switch_server_nrf52832_xxaa_s132_3.1.0

ble SDK: nRF5_SDK_14.2.0_17b948a

ble MESH SDK: nrf5_SDK_for_Mesh_v1.0.1_src


STEP 0 : copy necessary drivers file to ble MESH sdk

copy "common" folder
from
..\nRF5_SDK_14.2.0_17b948a\components\softdevice\common
to
..\nrf5_SDK_for_Mesh_v1.0.1_src\external\softdevice




copy clock/common/power/uart 4 folders 
from
..\nRF5_SDK_14.2.0_17b948a\components\drivers_nrf
to
..\nrf5_SDK_for_Mesh_v1.0.1_src\external\nRF5_SDK_14.2.0_17b948a\components\drivers_nrf



copy uart/experimental_log/experimental_section_vars/fifo 4 folders
from
..\nRF5_SDK_14.2.0_17b948a\components\libraries
to
..\nrf5_SDK_for_Mesh_v1.0.1_src\external\nRF5_SDK_14.2.0_17b948a\components\libraries




STEP 1: include divers files to SEGGER embedded Studio preprocessor

click project"light_switch_server", and select "Edit options.."/Preprocessor/ User Include Directories


add the following path into that blue blank:
../../../external/nRF5_SDK_14.2.0_17b948a/components/libraries/uart;
../../../examples/light_switch_idesyn_sandbox/server/src;
../../../external/nRF5_SDK_14.2.0_17b948a/components/drivers_nrf/common;
../../../external/nRF5_SDK_14.2.0_17b948a/components/libraries/experimental_log;
../../../external/nRF5_SDK_14.2.0_17b948a/components/libraries/experimental_section_vars;
../../../external/nRF5_SDK_14.2.0_17b948a/components/libraries/experimental_log/src;
../../../external/nRF5_SDK_14.2.0_17b948a/components/drivers_nrf/power;
../../../external/nRF5_SDK_14.2.0_17b948a/components/drivers_nrf/clock;
../../../external/nRF5_SDK_14.2.0_17b948a/components/drivers_nrf/uart;
../../../external/nRF5_SDK_14.2.0_17b948a/components/libraries/fifo;
../../../external/softdevice/common;


STEP 2: include C files to project

I create nRF_Drivers_Import/nRF_Library_Import 2 folders to put C files imported from SDK14

add to nRF_Drivers_Import:
- nrf_drv_common.c
- nrf_drv_clock.c
- nrf_drv_uart.c
- nrf_drv_power.c

add to nRF_Library_Import:
- app_fifo.c
- app_uart_fifo.c
- retarget.c





STEP 3: initialize UART in main.c

add the following code in main.c

about line 60
#include "app_uart.h"
#if defined (UART_PRESENT)
#include "nrf_uart.h"
#endif
#if defined (UARTE_PRESENT)
#include "nrf_uarte.h"
#endif

#define UART_HWFC   APP_UART_FLOW_CONTROL_DISABLED
#define UART_TX_BUF_SIZE 256                         /**< UART TX buffer size. */
#define UART_RX_BUF_SIZE 256                         /**< UART RX buffer size. */
#define DATA_LEN   (6)
static uint8_t data_array[DATA_LEN];
static uint8_t index = 0;


about line 130
void uart_event_handle(app_uart_evt_t * p_event)
{  

    switch (p_event->evt_type)
    {
        case APP_UART_DATA_READY:
            UNUSED_VARIABLE(app_uart_get(&data_array[index]));
            break;

        case APP_UART_COMMUNICATION_ERROR:
            ERROR_CHECK(p_event->data.error_communication);
            break;

        case APP_UART_FIFO_ERROR:
            ERROR_CHECK(p_event->data.error_code);
            break;

        default:
            break;
    }
}

static void uart_init(void)
{
 uint32_t err_code;
 const app_uart_comm_params_t comm_params =
 {
                 RX_PIN_NUMBER,
   TX_PIN_NUMBER,
   RTS_PIN_NUMBER,
   CTS_PIN_NUMBER,
   UART_HWFC,
   false,
   NRF_UART_BAUDRATE_115200
 };

    APP_UART_FIFO_INIT(&comm_params,
   UART_RX_BUF_SIZE,
   UART_TX_BUF_SIZE,
   uart_event_handle,
   APP_IRQ_PRIORITY_LOWEST,
   err_code);

    ERROR_CHECK(err_code);
}


in main function line 180

    __LOG(LOG_SRC_APP, LOG_LEVEL_INFO, "----- BLE Mesh Light Switch Server Demo -----\n");
    __LOG(LOG_SRC_APP, LOG_LEVEL_INFO, "----- program by AgathaKuan\n");

    hal_leds_init();
    uart_init(); 

    printf("system start\r\n");


STEP 4: include "nrf_mesh_sdk.h" to app_uart_fifo.c
open app_uart_fifo.c 

add about line 40
#include "sdk_common.h"
#include "nrf_mesh_sdk.h"

STEP 5: modify APP_ERROR_CHECK( ) function as ERROR_CHECK( )
open app_uart_fifo.c 
about 200 app_uart_get( ) function:

change APP_ERROR_CHECK (uart_err_code) to ERROR_CHECK (uart_err_code)
uint32_t app_uart_get(uint8_t * p_byte)
{
    ASSERT(p_byte);
    bool rx_ovf = m_rx_ovf;

    ret_code_t err_code =  app_fifo_get(&m_rx_fifo, p_byte);

    // If FIFO was full new request to receive one byte was not scheduled. Must be done here.
    if (rx_ovf)
    {
        m_rx_ovf = false;
        uint32_t uart_err_code = nrf_drv_uart_rx(&app_uart_inst, rx_buffer, 1);

        // RX resume should never fail.
        ERROR_CHECK(uart_err_code);
    }

    return err_code;
}


STEP 6: add sdk_config.h near by main.c

copy  sdk_config.h
from
..\nRF5_SDK_14.2.0_17b948a\examples\ble_peripheral\ble_app_template
to
..\nrf5_SDK_for_Mesh_v1.0.1_src\examples\light_switch\server\src




STEP 7: modify sdk_config.h to enable UART

add these about line  3004
// <h> nRF_Libraries 
//==========================================================
// <q> APP_FIFO_ENABLED  - app_fifo - Software FIFO implementation
 

#ifndef APP_FIFO_ENABLED
#define APP_FIFO_ENABLED 1
#endif

then add these about line 3080
// <e> APP_UART_ENABLED - app_uart - UART driver
//==========================================================
#ifndef APP_UART_ENABLED
#define APP_UART_ENABLED 1
#define APP_UART_DRIVER_INSTANCE 0
#endif

then add these about line 3506
// <q> RETARGET_ENABLED  - retarget - Retargeting stdio functions
 

#ifndef RETARGET_ENABLED
#define RETARGET_ENABLED 1
#endif

about line  2724
// <e> UART_ENABLED - nrf_drv_uart - UART/UARTE peripheral driver
//==========================================================
#ifndef UART_ENABLED
#define UART_ENABLED 1
#endif
#if  UART_ENABLED
// <o> UART_DEFAULT_CONFIG_HWFC  - Hardware Flow Control
 
// <0=> Disabled 
// <1=> Enabled 

#ifndef UART_DEFAULT_CONFIG_HWFC
#define UART_DEFAULT_CONFIG_HWFC 0
#endif

// <o> UART_DEFAULT_CONFIG_PARITY  - Parity
 
// <0=> Excluded 
// <14=> Included 

#ifndef UART_DEFAULT_CONFIG_PARITY
#define UART_DEFAULT_CONFIG_PARITY 0
#endif

// <o> UART_DEFAULT_CONFIG_BAUDRATE  - Default Baudrate
 
// <323584=> 1200 baud 
// <643072=> 2400 baud 
// <1290240=> 4800 baud 
// <2576384=> 9600 baud 
// <3862528=> 14400 baud 
// <5152768=> 19200 baud 
// <7716864=> 28800 baud 
// <10289152=> 38400 baud 
// <15400960=> 57600 baud 
// <20615168=> 76800 baud 
// <30801920=> 115200 baud 
// <61865984=> 230400 baud 
// <67108864=> 250000 baud 
// <121634816=> 460800 baud 
// <251658240=> 921600 baud 
// <268435456=> 57600 baud 

#ifndef UART_DEFAULT_CONFIG_BAUDRATE
#define UART_DEFAULT_CONFIG_BAUDRATE 30801920
#endif

// <o> UART_DEFAULT_CONFIG_IRQ_PRIORITY  - Interrupt priority
 

// <i> Priorities 0,2 (nRF51) and 0,1,4,5 (nRF52) are reserved for SoftDevice
// <0=> 0 (highest) 
// <1=> 1 
// <2=> 2 
// <3=> 3 
// <4=> 4 
// <5=> 5 
// <6=> 6 
// <7=> 7 

#ifndef UART_DEFAULT_CONFIG_IRQ_PRIORITY
#define UART_DEFAULT_CONFIG_IRQ_PRIORITY 7
#endif

// <q> UART_EASY_DMA_SUPPORT  - Driver supporting EasyDMA
 

#ifndef UART_EASY_DMA_SUPPORT
#define UART_EASY_DMA_SUPPORT 1
#endif

// <q> UART_LEGACY_SUPPORT  - Driver supporting Legacy mode
 

#ifndef UART_LEGACY_SUPPORT
#define UART_LEGACY_SUPPORT 1
#endif

// <e> UART0_ENABLED - Enable UART0 instance
//==========================================================
#ifndef UART0_ENABLED
#define UART0_ENABLED 1
#endif
#if  UART0_ENABLED
// <q> UART0_CONFIG_USE_EASY_DMA  - Default setting for using EasyDMA
 

#ifndef UART0_CONFIG_USE_EASY_DMA
#define UART0_CONFIG_USE_EASY_DMA 1
#endif

#endif //UART0_ENABLED
// </e>

// <e> UART_CONFIG_LOG_ENABLED - Enables logging in the module.
//==========================================================
#ifndef UART_CONFIG_LOG_ENABLED
#define UART_CONFIG_LOG_ENABLED 0
#endif
#if  UART_CONFIG_LOG_ENABLED
// <o> UART_CONFIG_LOG_LEVEL  - Default Severity level
 
// <0=> Off 
// <1=> Error 
// <2=> Warning 
// <3=> Info 
// <4=> Debug 

#ifndef UART_CONFIG_LOG_LEVEL
#define UART_CONFIG_LOG_LEVEL 3
#endif

// <o> UART_CONFIG_INFO_COLOR  - ANSI escape code prefix.
 
// <0=> Default 
// <1=> Black 
// <2=> Red 
// <3=> Green 
// <4=> Yellow 
// <5=> Blue 
// <6=> Magenta 
// <7=> Cyan 
// <8=> White 

#ifndef UART_CONFIG_INFO_COLOR
#define UART_CONFIG_INFO_COLOR 0
#endif

// <o> UART_CONFIG_DEBUG_COLOR  - ANSI escape code prefix.
 
// <0=> Default 
// <1=> Black 
// <2=> Red 
// <3=> Green 
// <4=> Yellow 
// <5=> Blue 
// <6=> Magenta 
// <7=> Cyan 
// <8=> White 

#ifndef UART_CONFIG_DEBUG_COLOR
#define UART_CONFIG_DEBUG_COLOR 0
#endif

#endif //UART_CONFIG_LOG_ENABLED
// </e>

#endif //UART_ENABLED
// </e>


STEP 8:build project / download to your device
click build/build configuration/ clean "light_switch_server"/ build "light_switch_server"



click target/connect JLink/ Download "light_switch_server" /Verify "light_switch_server"



then open com port, you can see


and that means we exactly activate UART function.


NOTE:
1. nordic semiconductor's websites  don't have any message about exactly how to  use an example from the nRF5 SDK in combination with the mesh stack. However, in this example, I prove that add/ include all necessary files and modify configuration , we can use hardware externals APIs from nRF5 SDK to ble MESH SDK.


REFERENCE:
1. http://infocenter.nordicsemi.com/index.jsp?topic=%2Fcom.nordic.infocenter.meshsdk.v0.10.0%2Fmd_doc_getting_started_how_to_nordicSDK.html&cp=4_1_0_0_0

2. https://devzone.nordicsemi.com/f/nordic-q-a/19549/segger-studio-tutorial-user-include-directories-has-no-effect---no-such-file

3.http://infocenter.nordicsemi.com/index.jsp?topic=%2Fcom.nordic.infocenter.meshsdk.v1.0.0%2FLIBRARIES.html&cp=4_1_0_4





2018年3月16日 星期五

nRF52840 in SDK14 : Hot plugging change PWM frequency

This example shows how to re-initialize PWM frequency without reset.

in this example,I:
- after the device start BLE advertising, press Button_1 can change  LED_1 PWM frequency


Usually, PWM function initialize once on system start, and that means we can only able/disable it or change its percentage. However, if you want to change PWM frequency, you must initialize PWM function again. In this article, there is one way to re-initialize PWM function which the device does not need to restart.

This example is modified from ble_app_template
located in:
nRF5_SDK_14.2.0_17b948a\nRF5_SDK_14.2.0_17b948a\examples\ble_peripheral\ble_app_template

Board:nRF52840/SDK14/S140

STEP 0. project config
add "nrf_drv_ppi", "nrf_drv_pwm","nrf_drv_timer" in nRF_Drivers


add "app_pwm.c" in nRF_Library





STEP 1. modify "sdk_config"

in "nrf_drivers"
check PWM_ENABLED


PPI_ENABLED
at least enabled 3 timers


in nrf_library
check APP_PWM_ENABLED, APP_TIMER_ENABLED


check NRF_PWR_MGNT_ENABLED


STEP 3. modify main.c

add these include & #define in main.c

#include "app_pwm.h"
#include "nrf_delay.h"

#include "nrf_pwr_mgmt.h"
#include "nrf_drv_power.h"
#include "nrf_drv_ppi.h"
#include "nrf_drv_timer.h"

APP_PWM_INSTANCE(PWM1,2);                   // Create the instance "PWM1" using TIMER2.
static volatile bool ready_flag;            // A flag indicating PWM status.
static bool is_low_frequency = false;

APP_TIMER_DEF(m_pwm_timer_id); 
#define PWM_INTERVAL      APP_TIMER_TICKS(1*1000)  

add the following code in main function after services_init( )
    /*1000 period us = 1kHz*/
    app_pwm_config_t pwm1_cfg = APP_PWM_DEFAULT_CONFIG_1CH(1000L, BSP_LED_1);
    /* Switch the polarity of the second channel. */
    pwm1_cfg.pin_polarity[1] = APP_PWM_POLARITY_ACTIVE_LOW;   
    /* Initialize and enable PWM. */
    err_code = app_pwm_init(&PWM1,&pwm1_cfg,pwm_ready_callback); 
    APP_ERROR_CHECK(err_code);
    app_pwm_enable(&PWM1); 


add the following code before bsp_event_handler(bsp_event_t event)
void pwm_ready_callback(uint32_t pwm_id)    // PWM callback function
{
    ready_flag = true;
}


static void pwm_init_low_frequency(void)
{
 ret_code_t err_code;
 
 app_pwm_disable(&PWM1);
 app_pwm_uninit(&PWM1);
 /*4Hz = 2500000 us period*/ app_pwm_config_t pwm1_cfg = APP_PWM_DEFAULT_CONFIG_1CH(2500000, BSP_LED_1);
 /* Switch the polarity of the second channel. */
 pwm1_cfg.pin_polarity[1] = APP_PWM_POLARITY_ACTIVE_LOW;   
 /* Initialize and enable PWM. */
 err_code = app_pwm_init(&PWM1,&pwm1_cfg,pwm_ready_callback); 
 APP_ERROR_CHECK(err_code);
 app_pwm_enable(&PWM1);
}


static void pwm_init_high_frequency(void)
{
 ret_code_t err_code;
 
 app_pwm_disable(&PWM1);
 app_pwm_uninit(&PWM1);
 /*10Hz = 100000 us period*/
 app_pwm_config_t pwm1_cfg = APP_PWM_DEFAULT_CONFIG_1CH(1000, BSP_LED_1);
 /* Switch the polarity of the second channel. */
 pwm1_cfg.pin_polarity[1] = APP_PWM_POLARITY_ACTIVE_LOW;   
 /* Initialize and enable PWM. */
 err_code = app_pwm_init(&PWM1,&pwm1_cfg,pwm_ready_callback); 
 APP_ERROR_CHECK(err_code);
 app_pwm_enable(&PWM1);
}


add the following code in bsp_event_handler(bsp_event_t event) function
static void bsp_event_handler(bsp_event_t event)
{
    ret_code_t err_code;

    switch (event)
    {
        case BSP_EVENT_SLEEP:
            sleep_mode_enter();
            break; // BSP_EVENT_SLEEP

        case BSP_EVENT_DISCONNECT:
            err_code = sd_ble_gap_disconnect(m_conn_handle,
                                             BLE_HCI_REMOTE_USER_TERMINATED_CONNECTION);
            if (err_code != NRF_ERROR_INVALID_STATE)
            {
                APP_ERROR_CHECK(err_code);
            }
            break; // BSP_EVENT_DISCONNECT

        case BSP_EVENT_WHITELIST_OFF:
            if (m_conn_handle == BLE_CONN_HANDLE_INVALID)
            {
                err_code = ble_advertising_restart_without_whitelist(&m_advertising);
                if (err_code != NRF_ERROR_INVALID_STATE)
                {
                    APP_ERROR_CHECK(err_code);
                }
            }
            break; // BSP_EVENT_KEY_0
   
  case BSP_EVENT_KEY_1:
   
   if(false == is_low_frequency)
   {
    pwm_init_low_frequency();
    NRF_LOG_INFO("agatha: low frequency mode\r\n");
    
    is_low_frequency = true;
   }
   else
   {
    pwm_init_high_frequency();
    NRF_LOG_INFO("agatha: high frequency mode\r\n");
    
    is_low_frequency = false;
   }
    
   break;

        default:
            break;
    }
}


add this function before timers_init( )
static uint8_t pwm_percentage=25;
static void pwm_timeout_handler (void * p_context)
{ 
 uint32_t value;   
   
 ready_flag = false;
 /* Set the duty cycle - keep trying until PWM is ready... */
 while (app_pwm_channel_duty_set(&PWM1, 0, pwm_percentage) == NRF_ERROR_BUSY);
 
}

add this function in timers_init( )
static void timers_init(void)
{
    // Initialize timer module.
    ret_code_t err_code = app_timer_init();
    APP_ERROR_CHECK(err_code);
 
 err_code = app_timer_create(&m_pwm_timer_id,
    APP_TIMER_MODE_REPEATED,
    pwm_timeout_handler);      
}


add this function in application_timers_start(void)

static void application_timers_start(void)
{
    /* YOUR_JOB: Start your timers. below is an example of how to start a timer.
       ret_code_t err_code;
       err_code = app_timer_start(m_app_timer_id, TIMER_INTERVAL, NULL);
       APP_ERROR_CHECK(err_code); */
 ret_code_t err_code;
 
 err_code = app_timer_start(m_pwm_timer_id, PWM_INTERVAL, NULL);
 APP_ERROR_CHECK(err_code); 

}

program and reset your device, then open your COM port, when you once press button_1,
you will see:


and on your device , you can see in 25% PWM switch from 1000Hz  to 4Hz without restart.




REFERENCE:
uninitialize and reinitialize the PWM
https://devzone.nordicsemi.com/f/nordic-q-a/10672/changing-pwm-frequency-period






















2018年2月24日 星期六

nRF52840 in SDK14: a simple customized BLE service example

From nordic SDK11 to SDK 14, there many changes to implement a customized BLE service.
this article present a simple customized BLE service, with a characteristic that you can read, write , and notify.

example function description:
press button 3 to turn on LED_2, and press button 4 to turn off LED_2, recent status of LED_2 can be notify. Write "1" on the same characteristic can turn on LED_2, and write "0" can turn it off .

This is adapt from ble_app_template in SDK14
locate in :
 ...\BLE\nRF5_SDK_14.2.0_17b948a\nRF5_SDK_14.2.0_17b948a\examples\ble_peripheral\ble_app_template

I use nRF52840 PDK (pca10056)

STEP 0. add these two files near by main.c


ble_rls.c
/*
author: Agatha Kuan
20180223
ble remote-controled light service
*/

#include "sdk_common.h"
#include "ble_rls.h"
#include <string.h>
#include "ble_srv_common.h"

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

static void on_connect(ble_rls_t * p_rls, ble_evt_t const * p_ble_evt)
{
 p_rls->conn_handle = p_ble_evt->evt.gap_evt.conn_handle;
}

static void on_disconnect(ble_rls_t * p_rls, ble_evt_t const * p_ble_evt)
{
 UNUSED_PARAMETER(p_ble_evt);
 p_rls->conn_handle = BLE_CONN_HANDLE_INVALID;
}

static void on_write(ble_rls_t * p_rls, ble_evt_t const * p_ble_evt)
{  
 ble_gatts_evt_write_t const * p_evt_write = &p_ble_evt->evt.gatts_evt.params.write;
 
 if ((p_evt_write->handle == p_rls->status_char_handles.value_handle)
 && (p_evt_write->len == 1)
 && (p_rls->control_write_handler != NULL))
 {
  p_rls->control_write_handler(p_ble_evt->evt.gap_evt.conn_handle, p_rls, p_evt_write->data[0]);
 }
 
}

void ble_rls_on_ble_evt(ble_evt_t const * p_ble_evt, void * p_context)
{
 if ((p_context == NULL) || (p_ble_evt == NULL)) 
  return;
 
 ble_rls_t * p_rls = (ble_rls_t *)p_context;
 
 switch (p_ble_evt->header.evt_id)
 {
  case BLE_GAP_EVT_CONNECTED:
   on_connect(p_rls, p_ble_evt);
   break;

  case BLE_GAP_EVT_DISCONNECTED:
   on_disconnect(p_rls, p_ble_evt);
   break;

  case BLE_GATTS_EVT_WRITE:
   on_write(p_rls, p_ble_evt);
   break;

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

uint32_t status_char_add(ble_rls_t * p_rls, const ble_rls_init_t * p_rls_init)
{
 uint32_t            err_code;
 ble_gatts_char_md_t char_md;
 ble_gatts_attr_md_t cccd_md;
 ble_gatts_attr_t    attr_char_value;
 ble_uuid_t          ble_uuid;
 ble_gatts_attr_md_t attr_md;
 
 uint8_t    encoded_report_ref[BLE_SRV_ENCODED_REPORT_REF_LEN];
 uint8_t    init_len;
 uint8_t    init_status = CONTROLED_LED_OFF;
 
 if (p_rls->is_notification_supported)
 {
  memset(&cccd_md, 0, sizeof(cccd_md));

  // According to BAS_SPEC_V10, the read operation on cccd should be possible without
  // authentication.
  BLE_GAP_CONN_SEC_MODE_SET_OPEN(&cccd_md.read_perm);
  cccd_md.write_perm = p_rls_init->status_char_attr_md.cccd_write_perm;
  cccd_md.vloc       = BLE_GATTS_VLOC_STACK;
 }
 
 memset(&char_md, 0, sizeof(char_md));

 char_md.char_props.read   = 1;
 char_md.char_props.notify = (p_rls->is_notification_supported) ? 1 : 0;
 char_md.char_props.write  = 1;
 char_md.p_char_user_desc  = NULL;
 char_md.p_char_pf         = NULL;
 char_md.p_user_desc_md    = NULL;
 char_md.p_cccd_md         = (p_rls->is_notification_supported) ? &cccd_md : NULL;
 char_md.p_sccd_md         = NULL;
 
 BLE_UUID_BLE_ASSIGN(ble_uuid, BLE_UUID_STATUS_CHAR);
 
 memset(&attr_md, 0, sizeof(attr_md));
 attr_md.read_perm  = p_rls_init->status_char_attr_md.read_perm;
 attr_md.write_perm = p_rls_init->status_char_attr_md.write_perm;
 attr_md.vloc       = BLE_GATTS_VLOC_STACK;
 attr_md.rd_auth    = 0;
 attr_md.wr_auth    = 0;
 attr_md.vlen       = 0;
 
 memset(&attr_char_value, 0, sizeof(attr_char_value));

 attr_char_value.p_uuid    = &ble_uuid;
 attr_char_value.p_attr_md = &attr_md;
 attr_char_value.init_len  = sizeof(uint8_t);
 attr_char_value.init_offs = 0;
 attr_char_value.max_len   = sizeof(uint8_t);
 attr_char_value.p_value   = &init_status;
 
 err_code = sd_ble_gatts_characteristic_add(p_rls->service_handle, &char_md,
              &attr_char_value,
              &p_rls->status_char_handles);
 if (err_code != NRF_SUCCESS) 
  return err_code;
 
 if (p_rls_init->p_report_ref != NULL)
 {
  // Add Report Reference descriptor
  BLE_UUID_BLE_ASSIGN(ble_uuid, BLE_UUID_REPORT_REF_DESCR);
  
  memset(&attr_md, 0, sizeof(attr_md));
  attr_md.read_perm = p_rls_init->status_report_read_perm;
  BLE_GAP_CONN_SEC_MODE_SET_NO_ACCESS(&attr_md.write_perm);
  
  attr_md.vloc    = BLE_GATTS_VLOC_STACK;
  attr_md.rd_auth = 0;
  attr_md.wr_auth = 0;
  attr_md.vlen    = 0;

  init_len = ble_srv_report_ref_encode(encoded_report_ref, p_rls_init->p_report_ref);
  
  memset(&attr_char_value, 0, sizeof(attr_char_value));

  attr_char_value.p_uuid    = &ble_uuid;
  attr_char_value.p_attr_md = &attr_md;
  attr_char_value.init_len  = init_len;
  attr_char_value.init_offs = 0;
  attr_char_value.max_len   = attr_char_value.init_len;
  attr_char_value.p_value   = encoded_report_ref;
  
  err_code = sd_ble_gatts_descriptor_add(p_rls->status_char_handles.value_handle,
              &attr_char_value,
              &p_rls->report_ref_handle);
  
  if (err_code != NRF_SUCCESS)  
   return err_code;
  
 }
 else 
  p_rls->report_ref_handle = BLE_GATT_HANDLE_INVALID;
 
  
 return NRF_SUCCESS;
}

uint32_t ble_rls_init(ble_rls_t * p_rls, const ble_rls_init_t * p_rls_init)
{
 if (p_rls == NULL || p_rls_init == NULL) 
  return NRF_ERROR_NULL;
 
 uint32_t   err_code;
 ble_uuid_t ble_uuid;
 
 p_rls->control_write_handler  = p_rls_init->control_write_handler;
 p_rls->conn_handle     = BLE_CONN_HANDLE_INVALID;
 p_rls->is_notification_supported = p_rls_init->support_notification;
 
 BLE_UUID_BLE_ASSIGN(ble_uuid, BLE_UUID_RLS_SERVICE);
 
 err_code = sd_ble_gatts_service_add(BLE_GATTS_SRVC_TYPE_PRIMARY, &ble_uuid, &p_rls->service_handle);
 
 if (err_code != NRF_SUCCESS)
  return err_code;
 
 err_code = status_char_add(p_rls, p_rls_init);
 VERIFY_SUCCESS(err_code);

 return NRF_SUCCESS;
}

uint32_t ble_rls_light_status_update(ble_rls_t* p_rls,uint8_t recent_status)
{
 if (p_rls == NULL)    
        return NRF_ERROR_NULL;
 
 uint32_t err_code = NRF_SUCCESS;
 ble_gatts_value_t gatts_value;
 
 if(recent_status != p_rls->light_status)
 {
  memset(&gatts_value, 0, sizeof(gatts_value));

        gatts_value.len     = sizeof(uint8_t);
        gatts_value.offset  = 0;
        gatts_value.p_value = &recent_status;
   
  err_code = sd_ble_gatts_value_set(p_rls->conn_handle,
      p_rls->status_char_handles.value_handle,
      &gatts_value);
 if (err_code == NRF_SUCCESS)
  p_rls->light_status = recent_status;
 else
   return err_code;
  
 if ((p_rls->conn_handle != BLE_CONN_HANDLE_INVALID) && p_rls->is_notification_supported)
 {
     ble_gatts_hvx_params_t hvx_params;

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

            hvx_params.handle = p_rls->status_char_handles.value_handle;
            hvx_params.type   = BLE_GATT_HVX_NOTIFICATION;
            hvx_params.offset = gatts_value.offset;
            hvx_params.p_len  = &gatts_value.len;
            hvx_params.p_data = gatts_value.p_value;

            err_code = sd_ble_gatts_hvx(p_rls->conn_handle, &hvx_params);
 }
 else
   err_code = NRF_ERROR_INVALID_STATE;
 }
 
 return err_code;
}


ble_rls.h
/*
author: Agatha Kuan
20180223
ble remote-controled light service
*/

#ifndef BLE_RLS_H_
#define BLE_RLS_H_

#include <stdint.h>
#include <stdbool.h>
#include "ble.h"
#include "ble_srv_common.h"
#include "nrf_sdh_ble.h"
#include "nrf_ble_gatt.h"

#ifdef __cplusplus
extern "C" {
#endif

#ifndef BLE_RLS_OBSERVER_PRIO
#define BLE_RLS_OBSERVER_PRIO  2
#endif

#define BLE_RLS_DEF(_name)         \
static ble_rls_t _name;           \
NRF_SDH_BLE_OBSERVER(_name ## _obs,     \
      BLE_RLS_OBSERVER_PRIO,  \
      ble_rls_on_ble_evt, &_name);
         

#define BLE_UUID_RLS_SERVICE  0x0330
#define BLE_UUID_STATUS_CHAR  0x0331   /*read,notify,write*/ 
 
#define CONTROLED_LED_ON   1
#define CONTROLED_LED_OFF   0

typedef enum
{
 BLE_RLS_EVT_NOTIFICATION_ENABLED,   
 BLE_RLS_EVT_NOTIFICATION_DISABLED   
} ble_rls_evt_type_t;

typedef struct
{
 ble_rls_evt_type_t evt_type;        /**< Type of event. */
} ble_rls_evt_t;

typedef struct ble_rls_s ble_rls_t;

typedef void (*ble_rls_write_handler_t) (uint16_t conn_handle, ble_rls_t * p_rls, uint8_t new_state);

typedef struct
{
 ble_rls_write_handler_t   control_write_handler;
 bool       support_notification; 
 ble_srv_report_ref_t*   p_report_ref;
 uint8_t       init_status;
 ble_srv_cccd_security_mode_t status_char_attr_md;
 ble_gap_conn_sec_mode_t   status_report_read_perm;
}ble_rls_init_t;

struct ble_rls_s
{
 uint16_t      service_handle;
 ble_gatts_char_handles_t  status_char_handles; 
 uint16_t                       report_ref_handle;
 uint16_t      conn_handle;
 bool       is_notification_supported;
 uint8_t       light_status;
 ble_rls_write_handler_t   control_write_handler;
};

uint32_t ble_rls_init(ble_rls_t* p_rls, const ble_rls_init_t* p_rls_init);

void ble_rls_on_ble_evt(ble_evt_t const * p_ble_evt, void * p_context);

uint32_t ble_rls_on_status_change(uint16_t conn_handle, ble_rls_t * p_rls, uint8_t recent_state);

uint32_t ble_rls_light_status_update(ble_rls_t* p_rls,uint8_t recent_status);

#ifdef __cplusplus
}
#endif

#endif



STEP 1. add in main.c

#include "ble_rls.h"
BLE_RLS_DEF(m_rls);


about line 346
static void remote_control_light_handler(uint16_t conn_handle, ble_rls_t * p_rls, uint8_t new_state)
{
 if (new_state)
 {
  NRF_LOG_INFO("light off\r"); 
  bsp_board_led_off(BSP_BOARD_LED_1);

  ble_rls_light_status_update(&m_rls,CONTROLED_LED_OFF);
 }
 else
 {
  NRF_LOG_INFO("light on\r"); 
  bsp_board_led_on(BSP_BOARD_LED_1);

  ble_rls_light_status_update(&m_rls,CONTROLED_LED_ON);
 }
} 

 

static void services_init(void)
{
 ret_code_t     err_code;
 ble_rls_init_t rls_init;
 
 memset(&rls_init, 0, sizeof(rls_init));
 rls_init.support_notification = true;
 rls_init.control_write_handler = remote_control_light_handler;
 
 BLE_GAP_CONN_SEC_MODE_SET_OPEN(&rls_init.status_char_attr_md.cccd_write_perm);
 BLE_GAP_CONN_SEC_MODE_SET_OPEN(&rls_init.status_char_attr_md.read_perm);

 BLE_GAP_CONN_SEC_MODE_SET_OPEN(&rls_init.status_char_attr_md.write_perm);
 
 BLE_GAP_CONN_SEC_MODE_SET_OPEN(&rls_init.status_report_read_perm);
 
 err_code = ble_rls_init(&m_rls, &rls_init);
 APP_ERROR_CHECK(err_code); 
}


in bsp_event_handler
static void bsp_event_handler(bsp_event_t event)
{
    ret_code_t err_code;

    switch (event)
    {
        case BSP_EVENT_SLEEP:
     NRF_LOG_INFO("bsp_event_handler sleep_mode_enter \r\n");
            sleep_mode_enter();
            break; // BSP_EVENT_SLEEP

        case BSP_EVENT_DISCONNECT:
            err_code = sd_ble_gap_disconnect(m_conn_handle,
                                             BLE_HCI_REMOTE_USER_TERMINATED_CONNECTION);
            if (err_code != NRF_ERROR_INVALID_STATE)
            {
                APP_ERROR_CHECK(err_code);
            }
            break; // BSP_EVENT_DISCONNECT

        case BSP_EVENT_WHITELIST_OFF:
            if (m_conn_handle == BLE_CONN_HANDLE_INVALID)
            {
                err_code = ble_advertising_restart_without_whitelist(&m_advertising);
                if (err_code != NRF_ERROR_INVALID_STATE)
                {
                    APP_ERROR_CHECK(err_code);
                }
            }
            break; // BSP_EVENT_KEY_0
      
       case BSP_EVENT_WAKEUP:
           NRF_LOG_INFO("bsp_event_handler sleep_mode WAKE UP \r\n");
           break;
    
       case BSP_EVENT_KEY_2: 
           NRF_LOG_INFO("light on\r"); 
           bsp_board_led_on(BSP_BOARD_LED_1);
  
           ble_rls_light_status_update(&m_rls,CONTROLED_LED_ON);
           break;
    
      case BSP_EVENT_KEY_3:
           NRF_LOG_INFO("light off\r");
           bsp_board_led_off(BSP_BOARD_LED_1); 
  
           ble_rls_light_status_update(&m_rls,CONTROLED_LED_OFF);
           break;

        default:
            break;
    }
}


in main function

// Start execution.
    NRF_LOG_INFO("Template example started.");
    application_timers_start();

    advertising_start(erase_bonds);
    bsp_board_led_on(BSP_BOARD_LED_2);


STEP.2 program and flash into your device

you can
press button 3 to turn on LED_2
press button 4 to turn off LED_2


on mobile phone, you can see

write 00 can turn off LED_2
write 01 can turn on it.


Note:
1.the example in this article is not the same as ble_app_blinky

2. the format and API I used are more similar to other common ble_services, like battery service or uart services.