Introduction
Note
Please note that this RTLS Intro lab only applies to SDK 3.10.00. We are in the process of revamping our RTLS SimpleLink Academy Lab content and plan to release at the end of August 2019.
For updated documentation RTLS for SDK 3.20.00 please refer to the TI BLE-Stack User's Guide.
This module is an introduction to the Real Time Localization System (RTLS) Toolbox as well as its suite of PC tools. The RTLS toolbox is a collection of tools for localization, direction finding, and secure range bounding.
The RTLS architecture consists of software running on the host (PC) and the embedded device (CC26xx). Embedded RTLS nodes are controlled by the host via commands over UART. Host software is responsible for sending commands to the embedded nodes and collecting the results. The suite of host software is referred to as the RTLS Node Manager.
It is assumed that the reader has a basic knowledge of embedded tool chains and general C and Python programming concepts.
This lab is based on the rtls_master
rtls_slave
and rtls_passive
projects
that are part of the SimpleLink™ CC2640R2 SDK
(http://www.ti.com/tool/SIMPLELINK-CC2640R2-SDK).
This lab will focus on running the out of box demos, setting up the host Python environment, and getting started with data collection using Angle of Arrival (AoA) or Time of Flight (ToF).
Prerequisites
Software for desktop development
- SimpleLink™ CC2640R2 SDK (http://www.ti.com/tool/SIMPLELINK-CC2640R2-SDK)
- Those listed under the Dependencies section of the CC2640R2 SDK Release Notes
- Python 3.7 or higher
- Git bash
- RTLS known issues page on E2E. Apply all relevant fixes from this page.
- Chrome web browser
Hardware
This module requires the following kits:
- 3x CC2640R2-LAUNCHXL
- 1x BOOSTXL-AoA
Recommended reading
These chapters in the TI BLE-Stack User's Guide
- Getting Started
- The CC2640R2F SDK Platform
- Application
- BLE-Stack
- RTLS Toolbox
- Network Processor Interface (NPI)
Getting started with AoA booster pack
Project readme files:
- rtls_master
- All relevant information to
rtls_slave
andrtls_passive
is contained in thertls_master
readme rtls_agent
Readme located in <SimpleLink CC2640R2 SDK> → tools → blestack → rtls_agent folder within the CC2640R2 SDK. This will be covered in detail in Task 2.
Getting started – Desktop
Install the Software
- Run the SimpleLink CC2640R2 SDK installer.
- Install Python 3.7 or later from the Python Download page.
- Setup the Python environment as described in the README.html in the <SimpleLink CC2640R2 SDK> → tools → blestack → rtls_agent folder.
- If a bash environment doesn't exist on your system, install Git bash
This gives you:
- The SDK with TI-RTOS included at
<SIMPLELINK_CC2640R2_SDK_INSTALL_DIR>
which defaults toC:\ti\simplelink_cc2640r2_sdk_x_xx_xx_xx
. - Python 3.7 environment with all dependencies required by the RTLS Node Manager
Modify/Load the software
- Load Board #1 + BOOSTXL-AoA with
rtls_passive
project:
<SimpleLink CC2640R2 SDK> → examples → rtos → CC2640R2_LAUNCHXL → blestack → rtls_passive - Load Board #2 with
rtls_slave
project:
<SimpleLink CC2640R2 SDK> → examples → rtos → CC2640R2_LAUNCHXL → blestack → rtls_slave - Load Board #3 with
rtls_master
project:
<SimpleLink CC2640R2 SDK> → examples → rtos → CC2640R2_LAUNCHXL → blestack → rtls_master
Building the projects
Be sure to build both the stack and application before loading projects.
Note that the rtls_passive
uses the micro BLE-Stack setup as a connection
monitor, therefore it does not have a separate stack project.
The following tasks will show you how to get started with data collection using the RTLS toolbox. This lab will focus on the RTLS Toolbox as a whole and the Python based PC environment. Subsequent labs will cover AoA or ToF specific topics.
Localization Techniques
A Real Time Localization System can be defined as a system capable of determining the position of a target within a defined physical area in real time. The physical area is normally defined through deployment of reference/locator nodes.
There are two fundamentally different approaches to location finding:
Trilateration, where you know the distance between a reference node and a
target node. Time of Flight
gives you the distance from the receiver to the transmitter.
Triangulation, where you know the direction from a reference node to a target
node. Angle of Arrival
is a technique that can be used to measure the angle
from the receiver to the transmitter.
What is a node?
A node in this case is referred to as a localization capable embedded device. For the demos in the SDK, nodes are LaunchPads
RTLS Toolbox Introduction
In the Localization Techniques, we discussed how multiple AoA nodes can combine angle information to perform triangulation and how multiple ToF nodes can combine distance information to perform trilateration. It is important to remember in the pictures above, it is not possible for one single node to localize an object using the TI sample applications. A single AoA node only produces one angle, and a single ToF node only produces one distance. These by nature, are ambiguous measurements. If there are at least two nodes providing AoA or ToF data, then localization can occur. This requires a fourth device that is capable of combining the samples from the individual nodes and finding the intersection.
The intersection between the angles (AoA) or circles (ToF) is the estimated location of the device. An overview of the topology is shown below. In the diagram below, the black, blue, and red boxes represent CC2640R2 LaunchPads while the grey box is a PC.
Time of Flight performs which of the following localization techniques?
For a comprehensive presentation of the RTLS toolbox and its software components, please see the TI BLE-Stack User's Guide
RTLS Roles and Topology
Each node in an RTLS network utilizes the software components listed above
in a different way to perform a specific task related to localization. There are
three examples: rtls_master
, rtls_slave
, and rtls_passive
. The capabilities
of these examples are explained below.
RTLS Master
The RTLS master runs a full BLE-Stack and acts as a BLE central device. It will scan and connect to the RTLS slave over BLE. Once a connection is established the RTLS Master will do the following:
- Share the connection parameters (access address, master sleep clock accuracy, and CRC init) with the PC.
- Use the BLE link to share ToF and AoA parameters with the peripheral device.
- Implements the ToF master role
- The RTLS master does not send out AoA packets, but configures the slave to do so.
RTLS Slave
The RTLS slave runs a full BLE-Stack and acts as a BLE peripheral device. This is the device that is to be located. The slave device will advertise and enter a connection with the RTLS Master.
- Advertises special string to be detected by
rtls_master
(covered in detail in Task 3) - BLE-Stack peripheral role
- Implements ToF slave role
- Sends data packets with AoA tone embedded using Constant Tone Extension (CTE)
- Wireless/battery operated, not connected to PC
RTLS Passive
The RTLS passive does not actively participate in the BLE connection between the RTLS master and slave. Instead, it uses the Micro BLE-Stack in connection monitoring mode to follow the connection. To do this, the passive device relies on the Master to distribute the connection parameters once a connection is formed. The passive node does the following:
- Implement ToF passive role
- Receives packets with CTE and performs in-phase and quadrature component (IQ) sampling
- Uses Micro BLE-Stack in Connection Monitoring mode to follow connection between master and slave
PC/Central Processing Node
The PC node is responsible for controlling the embedded RTLS nodes by sending commands and processing events. In the SDK, this is realized by a combination of a Python layer that implements the UNPI master role and a rtls_agent_cli server that translates UNPI commands to a socket interface that is used by the GUI Composer application running in the browser. In a final product, these algorithms may be implemented on an embedded device or even perhaps the RTLS master node.
The PC implements the following roles in Python:
- UNPI master
- COM port interface
- Implementation of RTLS UNPI subsystem/command set
- rtls_agent_cli server
The PC implements the following roles in GUI Composer/Javascript:
- rtls_agent_cli client on localhost
- Graphing and logging data from rtls_agent_cli
- Parsing JSON objects to extract RTLS data
- Issue commands to RTLS nodes via rtls_agent_cli to UNPI conversion
- Enumerate devices
- Distribute connection parameters to passive nodes
Quiz!
Is it possible to have multiple RTLS passive devices in a network? [quiz] v Yes, the PC program will distribute connection information to any device that has RTLS capabilities of passive. --> Since it is a passive role and not a participant any number of devices can listen to a connection so long as they have parameters. x No, only one device can track the connection between master and slave due to timing requirements. --> This is incorrect, any device with connection parameters (access address, crc init, etc) can follow the connection. [quiz]
Why would multiple passive devices be desirable? (select all that apply)
Task 1 – Running the RTLS Demo
In this task, we will use the pre-compiled rtls_agent_cli.exe
located at
<SimpleLink CC2640R2 SDK> → tools → blestack → rtls_agent
It is recommended to familiarize yourself with the readme in the same folder
before starting.
Apply any fixes from the RTLS known issues page on E2E
See the image below for a recommended layout of the nodes during evaluation, this is a 2D image where all devices are laying on a flat surface "pointing" as shown in the picture. In this case each node should be placed on a box so that it does not sit directly on the ground. Note that a desk environment generally has sub-optimal RF conditions and should be avoided.
Build the projects and flash the LaunchPads as described in Modify/Load the software
Run the pre-compiled
rtls_agent_cli.exe
, press 'a' to auto detect the LaunchPads connected to your PC. This will trigger thertls_agent_cli.exe
to sendRTLS_CMD_IDENTIFY
to each node.- Once the nodes are identified, press enter to start the agent.
- Note that it is normal to receive NoRsp from ports if they are not programmed with RTLS software. Additionally, the COM ports used by the JTAG debugger will not respond, so NoRsp is expected there as well.
- As long as
RTLS_MASTER
andRTLS_PASSIVE
are detected, then it is okay to move on.
Navigate to TI GUI Composer Gallery and search for "RTLS Monitor" Use version
0.9.3
.- Select the RTLS_Monitor and click on it, this will open it in the browser view of GUI composer. (We recommend using the latest version of Chrome.)
Click the connect button, this will connect to the rtls_agent_cli running on localhost.
On success, you should be able to see graph window displaying sample data.
- It will take ~30 seconds before the graph starts showing data.
- We will cover more about what is happening during that time and what it takes to setup a RTLS network in Task 3.
- If evaluating in ToF mode, you should not move the nodes until the GUI starts receiving data, because ToF is calibrating. Once the GUI starts graphing, it is okay to move the nodes around. The default calibration distance is 1m.
Automatic RTLS
If the "Automatic RTLS" checkbox is enabled then the GUI will connect to the first RTLS slave device it finds and will immediately start AoA or ToF. When this box is unchecked it is possible to scan and select the slave device manually via Bluetooth Address listed in the "Remote devices" combobox.
This is helpful if there are multiple RTLS slaves in the area.
Bonus
Define RTLS_LOCATIONING_AOA
in all three example projects. (For rtls_master
and rtls_slave
, it is found in build_config.opt
. For rtls_passive
, define
it in the preprocessor defines in the project properties.) Repeat the steps above.
What changed?
Quiz!
What localization technique does the out of the box software employ?
Review the log, what devices are providing ToF distance measurements?
What is the format of the individual logs being sent between the rtls_agent_cli server and GUI Composer?
Assessing RTLS performance using the out of the box demos
The RTLS demos provided by TI are intended for evaluation use only and serve as a starting point for localization development. Better performance and accuracy can be achieved by utilizing the foundational demo toolkit provided by TI and developing advanced, customized algorithms to process AoA and ToF data. See the AoA and ToF specific labs for detailed information on demo results that can be used as a performance baseline as well as tips on improving performance.
The demo you have run, is not optimized for performance. It instead serves as a starting point for localization development. It is possible to achieve much better performance and accuracy than is seen here by developing advanced algorithms to process AoA and ToF data. TI is actively looking into algorithm development, and encourages their customers to do so too. This is a strong way of differentiating a localization product. See the AoA and ToF specific labs for more discussion and explanation of the results
Task 2 – Use RTLS Node Manager Directly
This task will bypass the GUI Composer application and interact directly with
the RTLS Python APIs to collect data. This gives power to the developer to define
custom behavior of the RTLS network and control the devices directly.
Specifically, this will cover setting up the required Python dependencies and
running the rtls_example.py
template script described in the SDK.
Assumptions and Notation
Before starting this task the following is assumed
- A command prompt supporting bash or Git bash is open and running.
- Unix style slashes will be used throughout. If it is necessary to run these
steps in the Windows Command Prompt (
cmd.exe
), then/
should be replaced with\
. - Various command prompts will search your System Path variable to find Python. If you have a pre-existing Python version in your path this may be selected over the newly installed version. To prevent mixing the two up, we will use virtual environments.
- We assume that Mac users don't have another instance of Python 3 installed.
If this is not the case, then based on the
PATH
variable an older version of Python may be selected with invoking thepython3
command. Be sure to invoke the correct version of Python. - Here we re-hash some of the instructions from the rtls_agent/readme.html, if you already followed it, some steps may be redundant.
- Install Python per steps in Getting started
- Open a command prompt (Git Bash is recommended)
Create a Python virtual environment
- Navigate to the SDK folder (e.g.
C:\ti\simplelink_cc2640r2_sdk_x_xx_xx_xx\tools\blestack\rtls_agent
) - Execute
py -3.7 -m venv .venv
(windows) orpython3 -m venv .venv
(mac). - This will create a folder called
.venv
in the current directory that includes a copy of the python interpreter and a sandbox for installing packages. - Activate virtual environment using
source .venv/Scripts/activate
(bash) or.venv\Scripts\activate.bat
(Windows cmd) - Observe that when a venv is activated
(.venv)
will appear before each cmd prompt - Notice that once the virtual environment is activated, the
python
command will use the local Python interpreter in the venv. See Virtual Environments for more info.
- Navigate to the SDK folder (e.g.
Install required Python Dependencies into the newly created virtual environment
- Execute
python -m pip --proxy www.proxy.com install -r requirements.dev.txt
- Note that above
--proxy www.proxy.com
is only required if behind a proxy. www.proxy.com
is an example of a proxy. It should be replaced with the web address of your specific proxy if applicable.- This will install the required Python packages that are needed by the RTLS
Python suite (these are listed in
requirements.dev.txt
).
- Execute
Open the
examples/rtls_example.py
, go to line 44, and edit the linemy_nodes = [RTLSNode('COM17', 115200), RTLSNode('COM12', 115200)]
to use the COM ports of the master and passive LaunchPads.Save the file and run it using
python -u examples/rtls_example.py
.- The script will detect if AoA or ToF mode is enabled, and then connect, pair, and start AoA or ToF based on which one is supported.
- From here the code will print out localization data in a loop. It can be killed with the keyboard interrupt (ctrl+c)
- See below for sample output (note that the addresses and COM ports will be different). After the expected output below the nodes will report AoA or ToF data depending on how they are configured.
Pip and requirements files
The RTLS and UNPI layers of the of the rtls_agent are now bundled as separate
python packages. This is why there is a requirements.txt
and a
requirements.dev.txt
. Using requirements.txt
will trigger the contents
of /rtls
and /unpi
to be installed as read-only.
Alternatively requirements.dev.txt
will set these packages up to be
editable. It is recommended to requirements.dev.txt
during development, and
requirements.txt
once the packages are finalized.
54:6C:0E:9F:12:27 TOF_MASTER, RTLS_MASTER
54:6C:0E:83:3A:A4 CM, TOF_PASSIVE, RTLS_PASSIVE
PASSIVE: 54:6C:0E:83:3A:A4 --> {"originator": "Nwp", "type": "SyncRsp", "subsystem": "RTLS", "command": "RTLS_CMD_IDENTIFY", "payload": {"capabilities": {"CM": true, "AOA_TX": false, "AOA_RX": false, "TOF_SLAVE": false, "TOF_PASSIVE": true, "TOF_MASTER": false, "RTLS_SLAVE": false, "RTLS_MASTER": false, "RTLS_PASSIVE": true}, "identifier": "54:6C:0E:83:3A:A4"}}
MASTER: 54:6C:0E:9F:12:27 --> {"originator": "Nwp", "type": "SyncRsp", "subsystem": "RTLS", "command": "RTLS_CMD_IDENTIFY", "payload": {"capabilities": {"CM": false, "AOA_TX": false, "AOA_RX": false, "TOF_SLAVE": false, "TOF_PASSIVE": false, "TOF_MASTER": true, "RTLS_SLAVE": false, "RTLS_MASTER": true, "RTLS_PASSIVE": false}, "identifier": "54:6C:0E:9F:12:27"}}
MASTER: 54:6C:0E:9F:12:27 --> {"originator": "Nwp", "type": "SyncRsp", "subsystem": "RTLS", "command": "RTLS_CMD_SCAN", "payload": {"status": "RTLS_SUCCESS"}}
MASTER: 54:6C:0E:9F:12:27 --> {"originator": "Nwp", "type": "AsyncReq", "subsystem": "RTLS", "command": "RTLS_CMD_SCAN", "payload": {"eventType": 0, "addrType": 0, "addr": "54:6C:0E:83:3F:3D", "rssi": -52, "dataLen": 11, "data": "0A:09:52:54:4C:53:53:6C:61:76:65"}}
MASTER: 54:6C:0E:9F:12:27 --> {"originator": "Nwp", "type": "SyncRsp", "subsystem": "RTLS", "command": "RTLS_CMD_SCAN", "payload": {"status": "RTLS_SUCCESS"}}
MASTER: 54:6C:0E:9F:12:27 --> {"originator": "Nwp", "type": "AsyncReq", "subsystem": "RTLS", "command": "RTLS_CMD_SCAN_STOP", "payload": {"status": "RTLS_SUCCESS"}}
MASTER: 54:6C:0E:9F:12:27 --> {"originator": "Nwp", "type": "SyncRsp", "subsystem": "RTLS", "command": "RTLS_CMD_CONNECT", "payload": {"status": "RTLS_SUCCESS"}}
MASTER: 54:6C:0E:9F:12:27 --> {"originator": "Nwp", "type": "AsyncReq", "subsystem": "RTLS", "command": "RTLS_CMD_CONN_PARAMS", "payload": {"accessAddress": 2480792542, "connInterval": 160, "hopValue": 8, "mSCA": 0, "currChan": 8, "chanMap": [255, 255, 255, 255, 31], "crcInit": 6270394}}
PASSIVE: 54:6C:0E:83:3A:A4 --> {"originator": "Nwp", "type": "SyncRsp", "subsystem": "RTLS", "command": "RTLS_CMD_CONN_PARAMS", "payload": {"status": "RTLS_SUCCESS"}}
PASSIVE: 54:6C:0E:83:3A:A4 --> {"originator": "Nwp", "type": "AsyncReq", "subsystem": "RTLS", "command": "RTLS_CMD_CONNECT", "payload": {"status": "RTLS_SUCCESS"}}
Why is it recommended to create a virtual Python environment (select all that apply)?
Task 3 – Building Custom RTLS Python Scripts
With the environment setup, it is time for us to use Python directly to control the RTLS nodes. The goal of this task is to explain the RTLS PC software and to walk through setting up a RTLS network.
You might want to make a copy of the default rtls_example.py so it is
preserved. Save it with another name like rtls_example_old.py
as a backup.
RTLS Node Manager Python Overview
First, we will briefly discuss the important layers of the Python solution and their role.
/rtls_agent
/rtls/
/rtls/
rtlsmanager.py - Class to manage multiple nodes in an RTLS network.
Subscribes to incoming data from the nodes, routes
outgoing data to each of the nodes. Distributes
connection parameters from master node to any
connected passive nodes when an connection is
established. Handles messages from rtls_agent_cli server
if one is provided.
rtlsnode.py - Class that implements the basic functionality of a node
in an RTLS network. This class will query the embedded
device connected to it and determine its capabilities.
Essentially this assigns a role in an RTLS context to
a COM port.
ss_rtls.py Defines the commands in the RTLS UNPI subsystem.
This file will define builder classes for the various
UNPI commands that the RTLS subsystem supports.
/unpi/
serialnode.py - Thread that manages serial communication from COM ports.
to higher layers.
Queues up messages and sends them to parser.
unpiparser.py - Parser for Unified Network Processor Interface messages.
Implements UNPI frame format packing/unpacking.
RTLS Python Program Template
It is recommended to build RTLS based Python applications on top of the
RTLSManager
class within rtlsmanager.py. Together with the the RTLSNode
class this forms the RTLS API set. The rtls_example.py
from the previous task
shows how perform basic initialization of the RTLSNodes and RTLSManager as well
as setting up the networking and collecting localization data.
Here we highlight some of the important parts of the example:
First, construct an instance of the RTLSNode class for the master and passive
device connected to PC. The first parameter is the user/UART COM port, and the
second is the baudrate which defaults to 115200
.
my_nodes = [RTLSNode('COM14', 115200), RTLSNode('COM47', 115200)]
Instantiate the RTLSManager will the newly created nodes, but do not connect a rtls_agent_cli.
manager = RTLSManager(my_nodes, wssport=None)
Create a subscriber instance, and add it to the manager. Essentially this tells the manager to add messages it receives to the example programs queue to be processed. Tell the manager to automatically distribute connection parameters to all of the passive nodes. Then start the manager.
subscriber = manager.create_subscriber()
# Tell the manager to automatically distribute connection parameters
manager.auto_params = True
# Start RTLS Node threads, Serial threads, and manager thread
manager.start()
Poll the capabilities of each node, assign master and create list of passives.
# Wait until nodes have responded to automatic identify command and get reference
# to single master RTLSNode and list of passive RTLSNode instances
master_node, passive_nodes, failed = manager.wait_identified()
Now that we have instantiated the classes, and got the capabilities of the nodes, we are ready to create the main loop.
while True:
# Get messages from manager
try:
# Pend on activity from one of the nodes with a given timeout.
identifier, msg_pri, msg = subscriber.pend(block=True, timeout=0.05).as_tuple()
# We received data from one of the nodes
# First determine which node sent the information.
from_node = node_msg.identifier
# Print the message as JSON, map MASTER and PASSIVE based on identifier (BD_ADDR)
if sending_node in passive_nodes:
print(f"PASSIVE: {identifier} --> {msg.as_json()}")
else:
print(f"MASTER: {identifier} --> {msg.as_json()}")
# Perform further processing here.
except queue.Empty:
pass
RTLS Network Setup Procedure
The preceding code snippets in this task form a template program built on
top of the RTLSNode
and RTLSManager
classes. At this point you should have
a basic understanding of RTLS classes. Next we will cover the minimum commands
required to setup an RTLS network.
In which Python file would you find the UNPI command definitions for the ToF?
The embedded examples in the SDK will synchronize localization measurements on a
connection. In this case a measurement can be ToF interleaved with the BLE
connection events or AoA measuring the CTE embedded in the connection packets.
In both cases a BLE connection is a prerequisite for performing localization.
The sequence diagram below shows the UNPI commands required to establish a
connection between rtls_master
and rtls_slave
.
As covered in the BLE connections lab, before connecting, a scan must be
performed to see if the desired device is nearby. The scanning device will
inspect the advertising and optionally the scan response data to determine if
it wishes to connect to a given advertiser. Usually the scanner is looking
for a given token or string in the broadcast data of the advertiser. The RTLS
master will look for the string {'R','T','L','S','S','l','a','v','e'}
starting
at the 3rd byte of the slave's advertisement data. If the advertising device
matches the filter, then it will be reported to the PC/Node Manager as
RTLS_CMD_SCAN
responses, if not, it will be discarded.
If the Node Manager wishes to form a connection to one of the devices in the
scan results it can tell the rtls_master
to do so by issuing an
RTLS_CMD_CONNECT
along with the peer device's address and address type.
The address information can be extracted from the RTLS_CMD_SCAN
responses
coming from the master node.
If the connection is successful, the RTLS_CMD_CONNECT
response will be
received with status of RTLS_SUCCESS
. The RTLS examples do not consider a
connection to be established between master and slave until the devices have
paired and formed an L2CAP Connection Oriented Channel (CoC). The L2CAP CoC
is used to send RTLS sync related information between master and slave.
This can include AoA parameters or ToF parameters, or a command to enable AoA
or ToF.
Immediately after the BLE connection is established
(i.e. GAP_LINK_ESTABLISHED_EVENT
received from the stack), the rtls_master
will share the connection parameters with the PC/Node Manager via
RTLS_CMD_CONN_PARAMS
. This information is needed by the connection monitor
inside rtls_passive
in order to follow the connection between RTLS master
and slave.
Distributing Connection Parameters
The RTLSManager
Python class will immediately relay any connection
parameters received (RTLS_CMD_CONN_PARAMS
) to all of the passive nodes
connected. This does not need to be done manually.
Setting up RTLS Network in Python
Now that we understand the basics behind the RTLS network and how to set it up,
let's review how the rtls_example.py
sample app sets up the RTLS network.
Note that the rtls_example.py
will do some additional processing after the
network is setup based on AoA or ToF. This is outside of the scope of this lab
and will be covered in the following AoA and ToF labs respectively.
The commands required to setup a network belong to the RTLS UNPI subsystem and
can be found in the ss_rtls.py
file.
We will use the rtls_example.py
as a starting point. From the sections
above, we know that after the nodes are identified, we want to tell the master
to scan.
You can send a command to an RTLSNode
instance by calling the builder class
associated with the command. For example, if you have an RTLSNode
instance
called master_node
you can tell it to scan like so:
master_node.rtls.scan()
After telling the node to scan, you will receive scan responses, the scan response packets are defined like this in Python:
struct = Struct(
"eventType" / Int8ul,
"addrType" / Enum(Int8ul),
"addr" / NiceBytes(ReverseBytes(Byte[6])),
"rssi" / Int8sl,
"dataLen" / Int8ul,
"data" / NiceBytes(Byte[this.dataLen])
)
In the last section, we covered that the rtls_master
embedded node will
only report scan responses from devices that advertise as RTLS slave.
Therefore, there isn't much need to inspect the data payload unless desired.
Instead, it is adequate to store the addr
and addrType
fields from the
scan response as this is needed for the connect request. Assuming you have a
node message like the one from rtls_example.py
(see top of while True loop)
then you can parse it like this:
if msg.command == 'RTLS_CMD_SCAN' and msg.type == 'AsyncReq':
address = msg.payload.addr
address_type = msg.payload.addrType
The code above will identify a scan result message then store the address and address type in a variable.
Asynchronous vs Synchronous commands in UNPI
You might have noticed that RTLS_CMD_SCAN
is used to tell the node to start
scanning, receive status, and receive scan results.
This is possible within UNPI because each message can be one of the following
types
- Synchronous request
- Synchronous response
- Asynchronous request
In the case of RTLS_CMD_SCAN
the message that initiates the scan on the
rtls_master
is a synchronous request. The message that returns the status
of the scan start call is a synchronous response, and the message that
returns scan results is an asynchronous request. See the NPI chapter
in the TI BLE-Stack User's Guide
for more information.
Now, we have collected a list of scan results and are ready to connect.
First, we must wait until the rtls_master
finishes scanning. When scanning
is complete, a message of type RTLS_CMD_SCAN_STOP
will be sent to the PC
from the rtls_master
node. By default, the code below will connect to the
first device that has {'R','T','L','S','S','l','a','v','e'}
as part of the
advertising data.
If there may be multiple rtls_slave
devices nearby, it is best
to specify your specific via address. If there is only one rtls_slave
nearby
this step can be skipped.
If you don't know the address, you can read it from the UART
display of the slave device. Open a Serial terminal (like putty or teraterm)
on the user/UART port of the rtls_slave
LaunchPad. Use 115200 baud, 8N1.
It should show the following text:
0x546C0E833F3D
Advertising
Based on this, you should set the following global variable (remove the 0x, and separate by :)
slave_addr = '54:6C:0E:83:3F:3D' # Slave addr should be set here
Assuming again that we have a node message like the ones in rtls_example.py
the code for connecting is as below.
# Once the scan has stopped and we have a valid address, then
# connect
if msg.command == 'RTLS_CMD_SCAN_STOP':
if address is not None and address_type is not None and (slave_addr is None or slave_addr == address):
master_node.rtls.connect(address_type, address)
else:
# If we didn't find the device, keep scanning.
master_node.rtls.scan()
Remember, the rtls_master
will automatically send the connection parameters
once a BLE connection is formed with the rtls_slave
. The RTLSManager
python
class will intercept this and distribute it to all rtls_passive
nodes so we
don't have to do this in our program. Upon receiving the connection parameters,
the connection monitor will begin following the connection between master and
slave. Note that it may take some time to establish a connection as this
does include LE Secure Connections pairing as well as opening an L2CAP
Connection Oriented Channel.
Equipped with the knowledge above, modify rtls_example.py
to connect to
your slave device. The expected output is show here:
54:6C:0E:9F:12:27 TOF_MASTER, RTLS_MASTER
54:6C:0E:83:3A:A4 CM, TOF_PASSIVE, RTLS_PASSIVE
PASSIVE: 54:6C:0E:83:3A:A4 --> {"originator": "Nwp", "type": "SyncRsp", "subsystem": "RTLS", "command": "RTLS_CMD_IDENTIFY", "payload": {"capabilities": {"CM": true, "AOA_TX": false, "AOA_RX": false, "TOF_SLAVE": false, "TOF_PASSIVE": true, "TOF_MASTER": false, "RTLS_SLAVE": false, "RTLS_MASTER": false, "RTLS_PASSIVE": true}, "identifier": "54:6C:0E:83:3A:A4"}}
MASTER: 54:6C:0E:9F:12:27 --> {"originator": "Nwp", "type": "SyncRsp", "subsystem": "RTLS", "command": "RTLS_CMD_IDENTIFY", "payload": {"capabilities": {"CM": false, "AOA_TX": false, "AOA_RX": false, "TOF_SLAVE": false, "TOF_PASSIVE": false, "TOF_MASTER": true, "RTLS_SLAVE": false, "RTLS_MASTER": true, "RTLS_PASSIVE": false}, "identifier": "54:6C:0E:9F:12:27"}}
MASTER: 54:6C:0E:9F:12:27 --> {"originator": "Nwp", "type": "SyncRsp", "subsystem": "RTLS", "command": "RTLS_CMD_SCAN", "payload": {"status": "RTLS_SUCCESS"}}
MASTER: 54:6C:0E:9F:12:27 --> {"originator": "Nwp", "type": "SyncRsp", "subsystem": "RTLS", "command": "RTLS_CMD_SCAN", "payload": {"status": "RTLS_SUCCESS"}}
MASTER: 54:6C:0E:9F:12:27 --> {"originator": "Nwp", "type": "AsyncReq", "subsystem": "RTLS", "command": "RTLS_CMD_SCAN", "payload": {"eventType": 0, "addrType": 0, "addr": "54:6C:0E:83:3F:3D", "rssi": -54, "dataLen": 11, "data": "0A:09:52:54:4C:53:53:6C:61:76:65"}}
MASTER: 54:6C:0E:9F:12:27 --> {"originator": "Nwp", "type": "AsyncReq", "subsystem": "RTLS", "command": "RTLS_CMD_SCAN_STOP", "payload": {"status": "RTLS_SUCCESS"}}
MASTER: 54:6C:0E:9F:12:27 --> {"originator": "Nwp", "type": "SyncRsp", "subsystem": "RTLS", "command": "RTLS_CMD_CONNECT", "payload": {"status": "RTLS_SUCCESS"}}
MASTER: 54:6C:0E:9F:12:27 --> {"originator": "Nwp", "type": "AsyncReq", "subsystem": "RTLS", "command": "RTLS_CMD_CONN_PARAMS", "payload": {"accessAddress": 1387477511, "connInterval": 160, "hopValue": 14, "mSCA": 0, "currChan": 14, "chanMap": [255, 255, 255, 255, 31], "crcInit": 212647}}
PASSIVE: 54:6C:0E:83:3A:A4 --> {"originator": "Nwp", "type": "SyncRsp", "subsystem": "RTLS", "command": "RTLS_CMD_CONN_PARAMS", "payload": {"status": "RTLS_SUCCESS"}}
PASSIVE: 54:6C:0E:83:3A:A4 --> {"originator": "Nwp", "type": "AsyncReq", "subsystem": "RTLS", "command": "RTLS_CMD_CONNECT", "payload": {"status": "RTLS_SUCCESS"}}
Connection established, all systems go!!!
MASTER: 54:6C:0E:9F:12:27 --> {"originator": "Nwp", "type": "AsyncReq", "subsystem": "RTLS", "command": "RTLS_CMD_CONNECT", "payload": {"status": "RTLS_SUCCESS"}}
Connection established, all systems go!!!
Solution is below.
import queue
import time
from rtls import RTLSManager, RTLSNode
# Un-comment the below to get raw serial transaction logs
# import logging, sys
# logging.basicConfig(stream=sys.stdout, level=logging.DEBUG,
# format='[%(asctime)s] {%(filename)s:%(lineno)d} %(levelname)s - %(message)s')
if __name__ == '__main__':
# Initialize, but don't start RTLS Nodes to give to the RTLSManager
my_nodes = [RTLSNode('COM5', 115200), RTLSNode('COM8', 115200)]
# Initialize references to the connected devices
master_node = None
passive_nodes = []
address = None
address_type = None
# If slave addr is None, the script will connect to the first RTLS slave
# that it found. If you wish to connect to a specific device
# (in the case of multiple RTLS slaves) then you may specify the address
# explicitly as given in the comment to the right
slave_addr = None #'54:6C:0E:83:3F:3D'
# Initialize manager reference, because on Exception we need to stop the manager to stop all the threads.
manager = None
try:
# Start an RTLSManager instance without WebSocket server enabled
manager = RTLSManager(my_nodes, websocket_port=None)
# Create a subscriber object for RTLSManager messages
subscriber = manager.create_subscriber()
# Tell the manager to automatically distribute connection parameters
manager.auto_params = True
# Start RTLS Node threads, Serial threads, and manager thread
manager.start()
# Wait until nodes have responded to automatic identify command and get reference
# to single master RTLSNode and list of passive RTLSNode instances
master_node, passive_nodes, failed = manager.wait_identified()
if len(failed):
print(f"ERROR: {len(failed)} nodes could not be identified. Are they programmed?")
# Exit if no master node exists
if not master_node:
raise RuntimeError("No RTLS Master node connected")
# Combined list for lookup
all_nodes = passive_nodes + [master_node]
#
# At this point the connected devices are initialized and ready
#
# Display list of connected devices
print(f"{master_node.identifier} {', '.join([cap for cap, available in master_node.capabilities.items() if available])}")
# Iterate over Passives and detect their capabilities
for pn in passive_nodes:
print(f"{pn.identifier} {', '.join([cap for cap, available in pn.capabilities.items() if available])}")
# Send an example command to each of them, from commands listed at the bottom of rtls/ss_rtls.py
for n in all_nodes:
n.rtls.identify()
while True:
# Get messages from manager
try:
identifier, msg_pri, msg = subscriber.pend(block=True, timeout=0.05).as_tuple()
# Get reference to RTLSNode based on identifier in message
sending_node = manager[identifier]
if sending_node in passive_nodes:
print(f"PASSIVE: {identifier} --> {msg.as_json()}")
else:
print(f"MASTER: {identifier} --> {msg.as_json()}")
# After identify is received, we start scanning
if msg.command == 'RTLS_CMD_IDENTIFY':
master_node.rtls.scan()
# Once we start scaning, we will save the address of the
# last scan response
if msg.command == 'RTLS_CMD_SCAN' and msg.type == 'AsyncReq':
address = msg.payload.addr
address_type = msg.payload.addrType
# Once the scan has stopped and we have a valid address, then
# connect
if msg.command == 'RTLS_CMD_SCAN_STOP':
if address is not None and address_type is not None and (slave_addr is None or slave_addr == address):
master_node.rtls.connect(address_type, address)
else:
# If we didn't find the device, keep scanning.
master_node.rtls.scan()
# Once we are connected, then we can do stuff
if msg.command == 'RTLS_CMD_CONNECT' and msg.type == 'AsyncReq' and msg.payload.status == 'RTLS_SUCCESS':
print ("Connection established, all systems go!!!")
except queue.Empty:
pass
finally:
if manager:
manager.stop()
You made it to the end
Excellent work!
This work is licensed under a Creative Commons Attribution-NonCommercial-NoDerivatives 4.0 International License.