Professional Documents
Culture Documents
beginner's tutorial
MartinBL 18 Mar 2016
Before we begin
Table of content
Topics that will be covered include:
1. Before we begin
2. Basic theory
3. Attribute tables in nRFConnect Bluetooth Low Energy application
4. Description of the example
5. Adding a characteristic
6. Updating the characteristic and sending notifications
If you run into troubles please browse devzone, look for documentation on our Infocenter, and read
the user guides for your kits. You can also check out the "Solution" folder for a proposed solution. I
urge you to post any questions you might have on the forum and not below the tutorial. This makes
it easier for other users and Nordic employees to see and search for your questions and you will
most likely get a faster response(!).
The attribute protocol defines two roles; a server role and a client role. It allows a server to expose
a set of attributes to a client that are accessible using the attribute protocol. An attribute is a
discrete value that has the following three properties associated with it: (1) an attribute type,
defined by a UUID, (2) an attribute handle, (3) a set of permissions that are defined by each higher
layer specification that utilizes the attribute; these permissions cannot be accessed using the
Attribute Protocol.
The attribute type specifies what the attribute represents. Bluetooth SIG defined attribute types are
defined in the Bluetooth SIG assigned numbers page, and used by an associated higher layer
specification. Non-Bluetooth SIG attribute types may also be defined.
Let us relate this to a typical application, namely the Heart Rate Profile. In Table 1 each and every row is
an attribute, and each attribute has a handle, a type, a set of permissions, and a value.
Table 1:
Attribute Handles
The attribute handle uniquely identifies an attribute on a server, allowing a client to reference the
attribute in read or write requests. To simplify things then handle can be considered as the row number
in the attribute table. Although the handle number might not be sequential. The handles are 16-bit
numbers and you will see later on that the SoftDevice use handles extensively to refer to various
attributes. From a programmer’s perspective it is actually a quite effective way to pass values and
information between functions. It makes it easy for your application to keep track of the attributes and
to grab whatever information it needs. The handle numbers vary depending on how many attributes
you have.
Attribute Permissions
Permissions define some rules of how you can interact with a specific attribute. It defines whether an
attribute should be readable and/or writeable and what kind of authorization is required to do the
operations. Note that these permissions only apply to the attribute value, not the handle, type, and the
permission field itself. This allows a client to look through a server’s attribute table and discover what
the server can provide. Although not necessarily read and write the values.
Attribute Values
The value can be anything. It can be a heart rate value measured in beats per minute, the state of a
light switch, or a string like “Hello World”. And sometimes it is information about where to find other
attributes and their properties. For example, in the Service Declaration attribute in Table 1 (handle
0x000E) the value holds a UUID (0x180D) identifying what kind of service it is (notice the "Assigned
Number" field for the Heart Rate service, almost at the top of this page). The Characteristic Declaration
value (handle 0x000F) holds information about the subsequent Characteristic Value Declaration
(Properties, Handle, and Type). Finally, the Heart Rate Measurement Characteristic Value (handle
0x0010) eventually holds the actual number of heart beats per minute.
Characteristic Declaration
Immediately after this follows a Characteristic Declaration (there is a rare exception to this rule, but that
is out of scope for this tutorial). The Characteristic Declaration is similar to the Service Declaration. The
type is always 0x2803, the standard UUID for Characteristic Declarations. And the permissions are
always Read Only without any authentication or authorization required. The value however, contains
some interesting data. It always contains a handle, a UUID, and a set of properties. These three
elements describe the subsequent Characteristic Value Declaration. The handle naturally points to the
Characteristic Value Declaration’s place in the attribute table. The UUID describes what type of
information or value we can expect to find in the Characteristic Value Declaration. For example, a
temperature value, the state of a light switch, or some custom arbitrary value. And finally, the properties
describe how the characteristic value can be interacted with. Table 2 shows the characteristic properties
bit field. Don’t worry about understanding the details of the table now. We will circle back to it later.
Now you might wonder why we have read/write permissions for an attribute and read/write properties
for the characteristic value. Shouldn’t they always be the same? And that is a legitimate question. The
properties for the characteristic value are actually only guidelines for the client, used in the GATT and
application layers. They are just clues, if you will, of what the client can expect from the Characteristic
Value Declaration attribute. The permissions for the attribute (on the ATT layer) will always overrule the
characteristic value properties (on the GATT layer). Now you might ask again “but why do we need both
permissions and properties?”. And the simple, but disappointing, answer is: “Because the Bluetooth
Core Specification says so”. It is confusing, but has implications for how we will set up our characteristic
later so it needs to be said.
In the case of the Heart Rate Measurement Characteristic in Table 1 the Characteristic Value
Declaration is followed by a Descriptor Declaration. The descriptor is an attribute with additional
information about the characteristic. There are several kinds of descriptors, but in this tutorial we will
only deal with the Client Characteristic Configuration Descriptor (CCCD). More about this later.
The following screenshots show the list of services displayed in the nRF Connect BLE app when
connected to my nRF52 DK board running the HRS example.
Figure 1:
Here our example uses two services that are typically used in a heart rate monitor sports watch for
example, a Heart Rate Service and a Battery Service. Our device have also a Device Information service.
To view the handle and UUID of an attribute, move the mouse pointer over the attribute name and a
hover text would be displayed.
Figure 2:
As you can see in the figure above, the type of attribute (UUID) for the Hear Rate Service is 0x180D. To
view the characteristics of a service, click the Expand/Collapse icon.
Figure 3:
Here we see that the Heart Rate service have two characteristics
(https://www.bluetooth.com/specifications/gatt/viewer?
attributeXmlFile=org.bluetooth.service.heart_rate.xml):
A Heart Rate Measurement characteristic, which is mandatory as discussed before, which holds the
heart rate value and is used to send a heart rate measurement.
(https://www.bluetooth.com/specifications/gatt/viewer?
attributeXmlFile=org.bluetooth.characteristic.heart_rate_measurement.xml)
An optional Body Sensor Location characteristic, which is used to describe the intended location of
the heart rate measurement for the device.
(https://www.bluetooth.com/specifications/gatt/viewer?
attributeXmlFile=org.bluetooth.characteristic.body_sensor_location.xml)
The Heart Rate Measurement Characteristic ‘s type of attribute (UUID) is 0x2A37 and the “notify”
property is also shown. If you want to view the descriptors you can again click the Expand/Collapse
icon to expand the characteristics.
Figure 4:
Finally, we can see the values of the CCCD attribute. You can see the predefined descriptor UUID
(0x2902) and that the value is 0x0000. This means that notification is currently switched off. If a service
or a characteristic does not have any child attributes, the list is empty when you click the
Expand/Collapse icon.
The example
It is time to get our hands dirty! I figured that as an example we will make a readable and writeable
characteristic first. Then, after we have enjoyed manually reading and writing some data, we will
include the temperature of the nRF5x's CPU core in the characteristic. In case you didn't know already
the nRF5x actually has its own internal temperature sensor. It is not very accurate, but it is the simplest
way for us to get started with some actual dynamic sensor values. Finally, we will enable notification to
update the temperature values. First thing first: download the example code. To compile it, extract the
example to "your_SDK_folder\examples\ble_peripheral". If you need help with this please have a look
at this thread on devzone: [Compiling github projects]
(https://devzone.nordicsemi.com/question/35068/compiling-github-projects/). Open the project file
and hit “Build”. It should compile without any errors and if you browse through the files you will see that
it pretty much continues where the [service tutorial](https://devzone.nordicsemi.com/tutorials/8/ble-
services-a-beginners-tutorial/) ended.
The important files in this tutorial are our_service.c, our_service.h, and of course main.c. In these files
you will find various comments beginning with:
// FROM_SERVICE_TUTORIAL:
// ALREADY_DONE_FOR_YOU:
FROM_SERVICE_TUTORIAL means that this is code we looked at in the previous tutorial. A few places
you will see ALREADY_DONE_FOR_YOU. This marks very basic code that I have already prepared for
you so that we don't have to go through the less important stuff in detail. Finally, OUR_JOB: Step X.X
indicates what and when things need to be done in the tutorial.
To-do list
This is what needs to be done to get our characteristic up and running:
1. Step 1: Add service. This was completed in the last tutorial when we made our own custom service
with a custom base UUID and a service UUID.
2. Step 2: Declare and configure the characteristic.
3. Step 3: Add a CCCD to let the characteristic send notifications at regular intervals or whenever the
temperature values are changing.
This is what our attribute table should include to achieve all this:
Table: 3
Step 1: Add the service
As mentioned above this step was completed in the last tutorial, but let us take a quick look at what
our work looks like in the nRF Connect app so far. It should look something like this:
The highlighted line is the Service Declaration declaring our service. Unfortunately, the nRF Connect
app does not display the attribute type for the declarations, but as we know from Table 1 attribute type
for the Service Declaration is 0x2800. When we hover over the Service Declaration for our new service
we can see that the attribute handle is 0x000C, and the attribute value is our 128-bit custom UUID.
As you can see the function has three input parameters and one output parameter. The three input
parameters need to be populated with details that will define the characteristic attributes. These
parameters define the properties, read/write permissions, descriptors, characteristic values, etc. In fact,
you have more than 70 properties at your disposal when you are defining your characteristic. It sounds
daunting, but don't give up and throw away your dev kit just yet. Most of the parameters are optional
and almost all of them work just fine if you initialize them to zero using memset(¶meter, 0,
sizeof(parameter));. Pretty neat!
We will start by populating only the essential parameters we need to get started. In fact, all we need to
do is to choose where in memory to store the characteristic attributes and define a characteristic value
type using our custom UUID. In the function our_char_add() I have already declared all the necessary
variables for you and initialized them to zero. The order of which I have declared them might be a little
confusing, but since the necessary parameters are nested into structures, and these structures might be
nested into other structures, this is unfortunately the way it has to be. I have also selected names that
might seem a little cryptic, but these are naming conventions used throughout the SDKs and I chose to
go for consistency.
So let's start populating the parameters. To make things a little easier I have tried to assign each step a
number:
uint32_t err_code;
ble_uuid_t char_uuid;
APP_ERROR_CHECK(err_code);
This will use the same base UUID as the service, but add a different 16-bit UUID for the characteristic.
The UUID is defined as 0xBEEF in our_service.h. Note that this base UUID was added to the vendor
specific table in the previous tutorial when we created the custom service. All subsequent calls with the
same base UUID will return a reference to the same ID in the table. This way we will save some memory
by not needing to store a large array of 128-bit long IDs.
ble_gatts_attr_md_t attr_md;
memset(&attr_md, 0, sizeof(attr_md));
attr_md.vloc = BLE_GATTS_VLOC_STACK;
In the attribute metadata structure ble_gatts_attr_md_t you also have the option to define the
permissions with associated authorization requirements. For example if you need Man In The Middle
protection (MITM) or a passkey to access your attribute. We will circle back to this.
Step 2.C, Configure the Characteristic Value Attribute
Now that we have made our self a new UUID and decided where to store the characteristic we will
store this information in the Characteristic Value Attribute:
ble_gatts_attr_t attr_char_value;
memset(&attr_char_value, 0, sizeof(attr_char_value));
attr_char_value.p_uuid = &char_uuid;
attr_char_value.p_attr_md = &attr_md;
typedef struct
uint16_t conn_handle;
uint16_t service_handle;
// OUR_JOB: Step 2.D, Add handles for our characteristic
ble_gatts_char_handles_t char_handles;
}ble_os_t;
As you can see, the ble_os_t struct already has a field for the service declaration handle. The
connection handle, conn_handle, is there to keep track of the current connection and don't really have
anything to do with the attribute table handles. If you go to the definition of
ble_gatts_char_handles_t you can see that our new variable can hold 16-bit handles for the
characteristic value, user descriptor, its CCCD, and also something called Server Characteristic
Configuration Descriptor (SCCD) which is not within the scope of this tutorial.
err_code = sd_ble_gatts_characteristic_add(p_our_service->service_handle,
&char_md,
&attr_char_value,
&p_our_service->char_handles);
APP_ERROR_CHECK(err_code);
As you can see, we are giving the SoftDevice information about what service the characteristic belongs
to (the service_handle), the Characteristic Metadata, and the Characteristic Value Attributes. The stack
then processes the parameters and initiates the characteristic. Then it stores the handle values of our
characteristic into our p_our_serice structure.
Now compile and download your application to the kit. Make sure that you have remembered to
program the SoftDevice to the kit as well. Open the nRF Connect BLE app, connect to your kit, and do
a service discovery. You should see something like this:
As you can see there is a new characteristic in our service, but it doesn't do anything useful at all. It has
no value and you can neither read from it nor write to it. This is what happens when we initialize the
read/write attribute permissions to zero.
ble_gatts_char_md_t char_md;
memset(&char_md, 0, sizeof(char_md));
char_md.char_props.read = 1;
char_md.char_props.write = 1;
This will populate the characteristic metadata with read and write properties. Do another service
discovery in nRF Connect and note that the "Properties" field in the Characteristic Declaration has
changed to "Read, Write":
Now you can try to read some values to the characteristic by pressing the “Read” button or type in
some hexadecimal number in the value field and hit the “Write” button.
But, wait, what is this?? You are still getting READ and WRITE_NOT_PERMITTED errors?
You should, if you have followed the tutorial to the letter so far. This is because what we just did was
just setting the properties in the characteristic declaration and as discussed earlier they are just
guidelines. So even though these properties are exposed when your nRF Connect app does a service
discovery we have yet to set the required permissions for read and write operations. Before we have
done this the SoftDevice doesn't know what to allow and simply denies any reads and writes of the
characteristic.
Step 2.G, Set read/write permissions to our characteristic
So let's add some read/write permissions. Since this is a beginners tutorial we will keep it simple and
leave the doors wide open. No security, encryption, or passkey needed. A simple way of doing this is to
use the macro BLE_GAP_CONN_SEC_MODE_SET_OPEN(). Go to Step 2.G in our_char_add() and add
the following two lines:
BLE_GAP_CONN_SEC_MODE_SET_OPEN(&attr_md.read_perm);
BLE_GAP_CONN_SEC_MODE_SET_OPEN(&attr_md.write_perm);
Now do yet another service discovery. When you try to read from the characteristic now then this
should pop up in the log window nRF Connect:
Great success! We have just read 0, zero(!), bytes from our new characteristic because we have not yet
assigned a value or a value length to it.
Next, try to write e.g. the value 0x12 to the characteristic. You should get the following error because the
value attribute length is initialized to zero:
attr_char_value.max_len = 4;
attr_char_value.init_len = 4;
attr_char_value.p_value = value;
This will set the initial length, and also the maximum allowed length, of the characteristic value to 4
bytes. And just to be a little thorough we will also set the initial value to 12-34-56-78. Once again, try to
read from your characteristic and then write a couple of bytes. You should see that the value is
updated. Even if you disconnect and reconnect the new value should be retained.
Challenge 1:
What happens? Why are you not allowed to write more than four bytes to your characteristic and how
can you fix it?
Challenge 2:
Try to experiment with the attr_md permissions and the following permission macros:
1. BLE_GAP_CONN_SEC_MODE_SET_NO_ACCESS()
2. BLE_GAP_CONN_SEC_MODE_SET_OPEN()
3. BLE_GAP_CONN_SEC_MODE_SET_ENC_NO_MITM()
Try to do some reads and writes. See what happens and watch out for error messages in the nRF
Connect log window. Hint on number 3: If you click "Bond" in nRF Connect and bond with your kit you
will automatically add encryption (ENC) to your BLE link. However, you are not required to use Man In
The Middle (MITM) protection when bonding.
ble_gatts_attr_md_t cccd_md;
memset(&cccd_md, 0, sizeof(cccd_md));
BLE_GAP_CONN_SEC_MODE_SET_OPEN(&cccd_md.read_perm);
BLE_GAP_CONN_SEC_MODE_SET_OPEN(&cccd_md.write_perm);
cccd_md.vloc = BLE_GATTS_VLOC_STACK;
char_md.p_cccd_md = &cccd_md;
char_md.char_props.notify = 1;
Notice also that "Notify" is added to the Characteristic Declaration's properties field. The UUID value of
the CCCD is 0x2902 as we have seen earlier and the value is 0x0000 meaning that notification and
indication is currently switched off. If you want you can enable notification right away, but since we
have not yet set up any mechanisms updating the values nothing fancy will happen. Anyway, try to click
the "Enable services" button. You should see that the value field of the CCCD is updated to 0x0001,
meaning that notification is indeed enabled. You can also use the "Write" button and write to the CCCD
manually.
p_our_service->conn_handle = BLE_CONN_HANDLE_INVALID;
NRF_SDH_BLE_OBSERVER(m_our_service_observer, APP_BLE_OBSERVER_PRIO,
ble_our_service_on_ble_evt, (void*) &m_our_service);
switch (p_ble_evt->header.evt_id)
case BLE_GAP_EVT_CONNECTED:
p_our_service->conn_handle = p_ble_evt->evt.gap_evt.conn_handle;
break;
case BLE_GAP_EVT_DISCONNECTED:
p_our_service->conn_handle = BLE_CONN_HANDLE_INVALID;
break;
default:
// No implementation needed.
break;
if (p_our_service->conn_handle != BLE_CONN_HANDLE_INVALID)
Here we check whether or not we are in a valid connection, and this is where the housekeeping comes
in handy. If you try to send out notifications when not in a connection the SoftDevice will get grumpy
and give you errors. That is why we need this if-statement and why we implemented the housekeeping
to keep the connection handle updated. Now, inside the if-statement type in the following:
uint16_t len = 4;
ble_gatts_hvx_params_t hvx_params;
memset(&hvx_params, 0, sizeof(hvx_params));
hvx_params.handle = p_our_service->char_handles.value_handle;
hvx_params.type = BLE_GATT_HVX_NOTIFICATION;
hvx_params.offset = 0;
hvx_params.p_len = &len;
hvx_params.p_data = (uint8_t*)temperature_value;
sd_ble_gatts_hvx(p_our_service->conn_handle, &hvx_params);
First you might wonder what hvx stands for? It is not very intuitive, but it stands for Handle Value X,
where X symbolize either notification or indication as the struct and function can be used for both. So
to do a notification we declare a variable, hvx_params, of type ble_gatts_hvx_params_t. This will
hold the necessary parameters to do a notification and provide them to the sd_ble_gatts_hvx()
function. Here is what we will store in the variable:
1. handle: The SoftDevice needs to know what characteristic value we are working on. In applications
with two or more characteristics naturally we will need to reference the handle of the specific
characteristic value we want to use. Our example only has one characteristic and we will use the
handle stored in p_our_service->char_handles.value_handle.
2. type: The SoftDevice needs to know what "hvx type" we want to do; a notification or indication. As
we are doing a notification we use BLE_GATT_HVX_NOTIFICATION. The other option would be
BLE_GATT_HVX_INDICATION.
3. offset: Your characteristic value might be a sequence of many bytes. If you want to transmit only a
couple of these bytes and the bytes are located in the middle of the sequence you can use the
offset to extract them. Since we want to update all of our four bytes we will set the offset to zero.
4. p_len: The SoftDevice needs to know how many bytes to transmit. There is no need to send 20
bytes every time if you only have four bytes of relevant data. As an example, let's say you have a
characteristic with the following sequence of bytes: 0x01, 0x02, 0x03, 0x04, 0x05 and you want to
send just the 3rd and the 4th byte. Then set offset to 2 and len to 2.
5. p_data: Here we add a pointer to the actual data.
Finally we pass this structure into the sd_ble_gatts_hvx(). We also provide the function with the
relevant connection handle. In some applications you might work with several concurrent connections
and this is why the function also needs to know what handle to use. Now we have set up everything
that has to do with the characteristic and notification.
int32_t temperature = 0;
sd_temp_get(&temperature);
our_temperature_characteristic_update(&m_our_service, &temperature);
nrf_gpio_pin_toggle(LED_4);
This will instantiate a timer ID variable and define a timer interval of 1000 ms.
We pass:
1. Our timer ID
2. An enumeration called APP_TIMER_MODE_REPEATED. This tells the timer library to set up a timer
that triggers at regular intervals. The other option is APP_TIMER_MODE_SINGLE_SHOT which sets
up a timer that triggers only once.
3. A pointer to our timer timeout handler which is supposed to be executed on every timer event.
Most often in Nordic's libraries the fact that a module is initiated does not mean that it is started, so we
have to call one last function to reach our goal:
We pass:
Compile, download, connect, and discover services. Now to the moment of truth: Click "Toggle
notifications". If we have done everything right the characteristic value should update every second. You
should see LED 4 and the value line in nRF Connect blink green every second. You should also see
values ticking in in the Log window in nRF Connect like this:
If you now put your finger on the nRF5 chip you should also see that the values are changing. The
reason why the values are Least Significant Byte first is discussed here.
Challenge 1:
Challenge 2:
Try to modify the timer so that the temperature value is only measured when you are in a connection.
I.e. start the timer on a connection event and stop it on a disconnect event. Why spend energy on
measurements if you don't use them, right? Hint: Look for a timer start and stop function in the
app_timer library. Then see if you can do some magic in the ble_event_handler() function in main.c.
Summary
And that is it! You have now made a custom attribute table with a basic characteristic transmitting data
from your server to your client. It is somewhat limited though, in the sense that it is only one way
communication and it has no security or other advanced features. However I hope that I achieved my
goal; that you gained at least some new knowledge and that the code is something you can expand
upon.
Once again I urge you to post any questions you have in the Questions-section on the forum, not here.
You will most likely get faster response that way. Positive or negative critique though is very welcome in
the comment section below. Also remember to check out the "Solution" folder on github if you have any
issues.
Vasiliy Baryshnikov
over 3 years ago
54 comments
1 member is here
Adding Multiple Characteristics to your Service and updating their values, Step by Step:
I used "Bluetooth low energy Characteristics, a beginner's tutorial" to build on top of what they have
(SDK 15)
devzone.nordicsemi.com/.../ble-characteristics-a-beginners-tutorial
Step 1.
Step 2.
Step 3.
In our_services.c create a copy of the our_char_add which will create a new characteristic. You should
assign it a different name. You should assign it a unique UUID. You must assign it a unique characteristic
handle (such as char_handles_1 and char_handles_2). You may choose to set different max_len
depending on your application.
Fullscreen
1 static uint32_t our_char_add_1(ble_os_t * p_our_service)
2 {
3 ...
4
5 // OUR_JOB: Step 2.A, Add a custom characteristic UUID
6 char_uuid.uuid = BLE_UUID_CHARACTERISTIC_1; // specifying our characteristic UUID (16-
7
8 ...
9
10 // OUR_JOB: Step 2.E, Add our new characteristic to the service.
11 err_code = sd_ble_gatts_characteristic_add(p_our_service->service_handle, // adding a
12 &char_md, // adding characteristic metadata to the attribute table
13 &attr_char_value, // adding the attribute characteristic value to the attribute table
14 &p_our_service->char_handles_1); // adding characteristic handles to the attribute tab
15
16 ...
17 }
18
19 static uint32_t our_char_add_2(ble_os_t * p_our_service)
20 {
21
Step 4.
Step 5.
Now let's make a function that updates the characteristics. Basically, you would clone your update
function.
Fullscreen
1 void characteristic_value_update_1(ble_os_t *p_our_service, char machine_serial_number[]
2 {
3 ...
4
5 hvx_params.handle = p_our_service -> char_handles_1.value_handle; // the handle needs to
6
7 ...
8 }
9
10 void characteristic_value_update_2(ble_os_t *p_our_service, char machine_serial_number[]
11 {
12 ...
13
14 hvx_params.handle = p_our_service -> char_handles_2.value_handle; // the handle needs to
15
16 ...
17 }
Enjoy!!
Vasiliy Baryshnikov
over 3 years ago
Receiving (retrieve or write) values from characteristics and making use of them. Step by Step:
I used "Bluetooth low energy Characteristics, a beginner's tutorial" to build on top of what they have
(SDK 15).
Step 1:
In main.c somewhere at the top of the file add the write handler
Fullscreen
1 // BLE_WRITE:
2 /**@brief Function for handling write events to the LED characteristic.
3 *
4 * @param[in] characteristic1_value value that was received from the phone
5 */
6 // called from our_services.c from on_write();
7 // Make a note of the arguments that are passed to this handler, we will use that later
8 static void characteristic1_value_write_handler(uint32_t characteristic1_value)
9 {
10 NRF_LOG_INFO("We have received the characteristic1 value into our App: %d", charact
11 }
12 // Add other handlers here
Step 2:
In our_service.h file, you need to add the init structure at the top of the file.
Fullscreen
1 // BLE_WRITE:
2 /** @brief Our Service init structure. This structure contains all options and data need
3 * initialization of the service.*/
4 // This is used to pass the write handlers for different characteristics from main.c
5 // This is essentially like public constructor. All of the content will be copied to in
6 // Note that "uint32_t characteristic1_value" part had to match from Step 1
7 typedef void (*ble_os_characteristic1_value_write_handler_t) (uint32_t characteristic1_v
8
9 // Add other handlers here...
10 typedef struct
11 {
12 /**< Event handler to be called when the Characteristic1 is written */
13 ble_os_characteristic1_value_write_handler_t characteristic1_value_write_handler;
14 // Add other handlers here...
15
16 } ble_os_init_t;
Step 3:
In our_service.h file modify the structure.
Fullscreen
1 // This structure contains various status information for our service.
2 // The name is based on the naming convention used in Nordics SDKs.
3 // 'ble indicates that it is a Bluetooth Low Energy relevant structure and
4 // os is short for Our Service).
5 typedef struct
6 {
7 uint16_t conn_handle; /**< Handle of the current connection (a
8 uint16_t service_handle; /**< Handle of Our Service (as provided
9
10 ...
11
12 // BLE_WRITE: Write handlers. Upon BLE write, these handler will be called. Their im
13 ble_os_characteristic1_value_write_handler_t characteristic1_value_write_handler; /
14 // Add other handlers here...
15
16 }ble_os_t;
Step 4:
In our_service.c file modify the function.
Fullscreen
1 /**@brief Function for initiating our new service.
2 *
3 * @param[in] p_our_service Our Service structure.
4 * @param[in] init Our Service init structure. (BLE_WRITE)
5 *
6 */
7 void our_service_init(ble_os_t * p_our_service, ble_os_init_t * init)
8 {
9 uint32_t err_code; // Variable to hold return codes from library and softdevice fu
10
11 ...
12
13 // BLE_WRITE: transfer the pointers from the init instance to the module instance
14 p_our_service->characteristic1_value_write_handler = init->characteristic1_value_wri
15
16 ...
17
18 APP_ERROR_CHECK(err_code);
Step 5:
In main.c file modify the function. We have to init our service module and let it know about the write
handler.
Fullscreen
1 /**@brief Function for initializing services that will be used by the application.
2 */
3 static void services_init(void)
4 {
5 ret_code_t err_code;
6 nrf_ble_qwr_init_t qwr_init = {0};
7
8 // BLE_WRITE: Initialize Our Service module.
9 ble_os_init_t init = {0}; // Init Our Service module
10 init.characteristic1_value_write_handler = characteristic1_value_write_handler;
11 // Add other handlers here...
12
13 ...
14
15 // BLE_WRITE: We need to add the init instance pointer to our service instance
16 // Initialize our service
17 our_service_init(&m_our_service, &init);
18
19 ...
20
Step 6:
In main.c file, make sure that the following macro exists.
Fullscreen
1 /**@brief Function for initializing the BLE stack.
2 *
3 * @details Initializes the SoftDevice and the BLE event interrupt.
4 */
5 static void ble_stack_init(void)
6 {
7 ret_code_t err_code;
8
9 ...
10
11 // BLE_WRITE: Make sure this macro exists
12 // OUR_JOB: Step 3.C Call ble_our_service_on_ble_evt() to do housekeeping of ble con
13 // Needed for associating the observer with the event handler of the service
14 NRF_SDH_BLE_OBSERVER(m_our_service_observer, APP_BLE_OBSERVER_PRIO, ble_our_service_
15
16 }
Step 7:
In our_service.c file modify the function.
Fullscreen
1 // ALREADY_DONE_FOR_YOU: Declaration of a function that will take care of some houseke
2 void ble_our_service_on_ble_evt(ble_evt_t const * p_ble_evt, void * p_context)
3 {
4 ble_os_t * p_our_service =(ble_os_t *) p_context;
5 // OUR_JOB: Step 3.D Implement switch case handling BLE events related to our serv
6
7 switch(p_ble_evt -> header.evt_id)
8 {
9
10 ...
11
12 // BLE_WRITE:
13 // Write: Data was received to the module
14 case BLE_GATTS_EVT_WRITE:
15 on_write(p_our_service, p_ble_evt);
16 break;
17
18 //
19 default:
20 // No implementation needed
21
Step 8:
In our_service.c somewhere at the top of the file add this function
Fullscreen
1 // BLE_WRITE:
2 /**@brief Function for handling the Write event.
3 *
4 * @param[in] p_our_service Our Service structure.
5 * @param[in] p_ble_evt Event received from the BLE stack.
6 */
7 static void on_write(ble_os_t * p_our_service, ble_evt_t const * p_ble_evt)
8 {
9 NRF_LOG_INFO("on_write: called");
10
11 ble_gatts_evt_write_t const * p_evt_write = &p_ble_evt->evt.gatts_evt.params.write
12
13
14
15 if ((p_evt_write->handle == p_our_service->characteristic1_value_write_handler.val
16 {
17 NRF_LOG_INFO("characteristic1_value: Write Happened!");
18
19
20 // Make sure that the data is 4 bytes (or whatever the size of your characteri
Emil_Ekelund
over 2 years ago
in reply to Vasiliy Baryshnikov
Hi,
Thanks a lot this tutorial, it really helped me!
However, I found a small error in the code, in Step 8 in the if-statement
Fullscreen
1 if ((p_evt_write->handle == p_our_service->characteristic1_value_write_handler.v
and my code started working. Otherwise I got an error saying "request for member
‘value_handle’ in something not a structure or union"
I am leaving this here for future reference.
epietrowicz
over 2 years ago
in reply to Emil_Ekelund
This doesn't seem to fix the problem for me as it attaches the handle to the
char_handles_2 from the previous tutorial. Were you actually able to get correct data
from a BLE central device?
vickynike
5 months ago
in reply to epietrowicz
These are the changes I made to fix error:
In Step 8:
change
Fullscreen
1 if ((p_evt_write->handle == p_our_service->characteristic1_value_write_h
to
Fullscreen
1 if ((p_evt_write->handle == p_our_service->char_handles.value_handle))
wolfman24
over 7 years ago
Hey Nordic! just wanted to say thanks for all the previews and tutorials, they have been a great big help
on me trying to finish my project. I'm here on the characteristics aspect (obviously) and i understand the
code for the most part just not really sure where it all goes. If you guys could post the code for this
tutorial, even though i know you're still working on it, that would save me a lot of time. Thanks again!
Brook Gebremedhin
over 6 years ago
@MartinBL
yes you are right , in that case it works
but what i wanted is to do write to the char value from the MCP mobile app
so i removed
hvx_params.p_data = (uint8_t*)temperature_value; since i want
to write to the char value from the app ,
and i want to print it via uart using
printf("hvx data: %d\r\n", (uint32_t)*hvx_params.p_data); and this
prints
the same value even if i change the char value from the app side.
this is where am stuck at
Vasiliy Baryshnikov
over 3 years ago
Thanks a lot for these three tutorials! Really helped me to understand what is going on!
View More