Skip to main content
Skip table of contents

Tutorial 2: Ready to localize (Python)

Ready to localize

This is the second Pozyx tutorial, in which we’ll go through the process of performing (remote) positioning with the Pozyx system. If you missed the first one, check that one out first, as each tutorial assumes knowledge of the concepts explained in the ones before.

For this example, you need to own at least the contents of the Ready to Localize kit. If all tools are installed, open up ready_to_localize.py in the Pozyx library’s tutorial folder. Probably, the path to this file will be "Downloads/Pozyx-Python-library/tutorials/ready_to_localize.py". You can run the script from either command line or a text editor that allows running the scripts as well. If you're on Windows and have installed Python, you might be able to simply double-click it. If you you get an error, you likely forgot to install pythonosc, you can do this easily using

CODE
pip install python-osc


In this example, we will first set up and measure the anchor locations to perform positioning, after which we’ll be able to get accurate position data of the Pozyx, relative to the anchor setup. We will go in detail through other Pozyx core concepts as well, such as how to check the Pozyx’s device list, and how to read its error status. Mastering these debugging tools now will make using Pozyx in your own projects easier.

Anchor setup and measurement

Anchor setup

The Pozyx positioning system requires that the four anchors are placed inside the area where you wish to do positioning. In the guide 'Where to place the anchors?', it is explained how to place the anchors for the best possible positioning accuracy. The rules of thumb for the anchor placement were:

  • Place the anchors high and in line-of-sight of the user.

  • Spread the anchors around the user, never all on a straight line!

  • Place the anchors vertically with the antenna at the top, power plug at the bottom.

  • For 3D positioning: place the anchors at different heights.

It's also important to keep metallic objects out of immediate range of the antennas.

Before you install the anchors (with the provided Velcros or screws) on the walls or ceiling, it is usually a good idea to make a small sketch of the room, writing down the anchors’ IDs and sketching where they will be placed. You can find the network ID as the hexadecimal number on the label adorning your anchor.

Remember that, for optimal antenna performance, it is recommended to place the anchors vertically with their antenna at the top, and to orient your tags that will be positioning vertically as well. Also make sure that no heavy metal objects are placed near the antenna, as this might degrade performance. We stress this again because it's just that important.

Measurement

While there are different ways to measure your distance, if you are serious about using Pozyx we recommend to have a laser measurer. This will be much more convenient, and much more accurate, than trying to measure out several meters between your anchors with a conventional foldable or extendable We recommend placing them in an approximate rectangle as in the image above. This allows you to simplify both measurement and setup, and to follow along better with the example.

The Pozyx coordinate system

Pozyx determines its position relative to the anchor coordinates you supplied it with. This gives you the freedom to position and rotate your axis in any way you want. This is shown in the images below:

You can see that the coordinate system on the left, which is conventional to the room’s shape and orientation, is made by using anchor 0x256B as an element of the x-axis, giving it a zero y-coordinate. Then the y-axis is perpendicular to this, and anchor 0x3325 therefore has a non-zero x-component to fit into this orthogonal system. Anchor 0x1156 is selected as the origin, again for convenience. This origin doesn’t need to be one of the anchors, however, and you could use the center of the room as the origin point just as well, modifying the anchor’s coordinates accordingly: anchors 0x1156 and 0x3325 would have a negative x-component, while 0x2568 and 0x4244 have a positive coordinate. In turn, 0x1156 and 0x256B have a negative y-component while 0x3325 and 0x4244 will have a positive one.

In the right example, the origin is chosen to be anchor 0x3325’s location, and the x-axis is matched with the path between anchors 0x3325 and 0x1156.

Let’s go over the plan of attack in measuring out your setup, following the approach shown on the left:

  • Use the antenna’s center as Pozyx’s position in space as best as you can.

  • Pick one of your anchors as the origin, like we did 0x1156.

  • Pick the anchor on the same wall to be the one defining your x-axis. We picked 0x2568.

  • Measure out the horizontal distance between both anchors. Not a direct distance as this will include the vertical distance as well (Pythagoras). This will be that anchor’s x-coordinate. In our case, this was 4.5 m.

  • Now measure the distance to the opposite wall. Mark this point on the wall, as this is the x-axis’s zero value on that wall. If both walls are parallel, and there are anchors attached directly to this wall, you can set their y-coordinate to this distance. In our case, we could set 0x4244’s y-coordinate directly to 3.5 m.

  • Now measure the x-coordinates of every anchor on that wall directly using the point you marked earlier, again assuming both walls are parallel. Measurements can be complicated if they are not, so use more reference points then.

  • If there are anchors a bit apart from the wall, like 0x3325, be sure to account for this in in its y-coordinate.

In this example, we’ve used the approach described above. We’ll also assume the anchors are on heights between 1.1 and 2 meter, leading to these coordinates for each anchor:
0x1156: (0, 0, 1500)
0x256B: (4500, 0, 1800)
0x3325: (500, 3300, 1100)
0x4244: (4450, 3500, 2000)

These are the coordinates used in the example, but you can’t copy these! You will have to change both anchor IDs and coordinates to match your own setup. All these coordinates are expressed in millimeters.

Plug and play

To get the example working and see what exactly is happening before we delve into the code, we’ll need to change the parameters to match your device IDs and the coordinates you measured.

At the bottom of the Python script, in the ifmain structure, we find the script’s parameters. You can see that the first one is the serial port address of your device. We use serial_port = get_serial_ports()[0].device to automate the port selection, and will do so from now on.

The anchors' data is created as a list of DeviceCoordinates objects. DeviceCoordinates' second parameter, POZYX_ANCHOR, is a flag indicating you’re adding an anchor device. You’ll have to match the IDs and coordinates to the ones of your setup. We’ve done that for the example setup from above, as a reference.

PY
if __name__ == "__main__":
    # shortcut to not have to find out the port yourself
    serial_port = get_serial_ports()[0].device
    remote_id = 0x1000                 # remote device network ID
    remote = False                     # whether to use a remote device
    if not remote:
        remote_id = None
    use_processing = False             # enable to send position data through OSC
    ip = "127.0.0.1"                   # IP for the OSC UDP
    network_port = 8888                # network port for the OSC UDP
    osc_udp_client = None
    if use_processing:
        osc_udp_client = SimpleUDPClient(ip, network_port)
    # necessary data for calibration, change the IDs and coordinates yourself
    anchors = [DeviceCoordinates(0x0001, 1, Coordinates(0, 0, 2000)),
               DeviceCoordinates(0x0002, 1, Coordinates(3000, 0, 2000)),
               DeviceCoordinates(0x0003, 1, Coordinates(0, 3000, 2000)),
               DeviceCoordinates(0x0004, 1, Coordinates(3000, 3000, 2000))]
    algorithm = POZYX_POS_ALG_UWB_ONLY # positioning algorithm to use
    dimension = POZYX_3D               # positioning dimension
    height = 1000                      # height of device, required in 2.5D positioning

You will also see the remote_id, remote, algorithm, dimension, and use_processing parameters. remote_id and remote should be familiar from the first tutorial. use_processing will be used for the visualization. algorithm, dimension, and height will allow for customization regarding the positioning, and we'll get back to these when we're looking at the code in detail. Leave these parameters unchanged for now, which will result in 3D positioning being done with the UWB-only algorithm.

Now that you’ve done all the plugging, it’s time for play. Run the example, and if all goes well, you’re now looking at coordinates filling up the window, after the manual anchor configuration is checked and printed. As you are using your local device, the ID will be set to 0.

POS ID 0x0000, x(mm): 1231 y(mm): 2354 z(mm): 1167
POS ID 0x0000, x(mm): 1236 y(mm): 2241 z(mm): 1150
etc...

That’s that! You’re now getting accurate position coordinates from the Pozyx! You might also be seeing one of the following:

POS ID 0x0000, x(mm): 0 y(mm): 0 z(mm): 0
or
ERROR configuration on ID 0x0000, error code 0xXX
or
ERROR positioning on ID 0x0000, error code 0xXX
or
Coordinates that are nothing even remotely close to what they should be

If so, you probably miswrote one of the anchor IDs or entered either wrong or mixed up coordinates, causing the positioning algorithm to fail. If the error persists despite having put in everything correctly, check out the troubleshooting guide. Now that you've seen the positioning in action, let's look at the code that made this possible.

The code explained

We will now cover the essential code to get positioning working, but there's a lot more code in the file. The extras segment will cover how to access the Pozyx's device list, how to retrieve error codes returned by the Pozyx, and how to retrieve additional sensor data each time you position.

Setup and manual calibration

The Pozyx serial connection is initialized, together with the ReadyToLocalize object, and its setup is called.

PY
def setup(self):
    """Sets up the Pozyx for positioning by calibrating its anchor list."""
    print("------------POZYX POSITIONING V1.0 -------------")
    print("NOTES: ")
    print("- No parameters required.")
    print()
    print("- System will auto start calibration")
    print()
    print("- System will auto start positioning")
    print("------------POZYX POSITIONING V1.0 --------------")
    print()
    print("START Ranging: ")
    self.pozyx.clearDevices(self.remote_id)
    self.setAnchorsManual()
    self.printPublishConfigurationResult()

The setup function is straightforward. After its initialization of the Pozyx, it performs the anchor configuration. It first clears the Pozyx’s device list using clearDevices(), and then manually adds the anchors in setAnchorsManual() we set up and measured out. Let’s look at how this is done:

PY
def setAnchorsManual(self):
    """Adds the manually measured anchors to the Pozyx's device list one for one."""
    status = self.pozyx.clearDevices(self.remote_id)
    for anchor in self.anchors:
        status &= self.pozyx.addDevice(anchor, self.remote_id)
    if len(anchors) > 4:
        status &= self.pozyx.setSelectionOfAnchors(POZYX_ANCHOR_SEL_AUTO, len(anchors))
    return status

As each anchor is already defined as a DeviceCoordinates object, we can just iterate over the list of anchors and add each anchor to the device’s stored device list, which it will use when positioning. If you’d use more than four anchors, the device’s anchor selection is set to automatically use all available anchors from this set.

Loop

Now that we’ve properly configured the device’s device list with our set of anchors, let’s look at just how easy the actual positioning is in the short loop() function:

PY
def loop(self):
    """Performs positioning and displays/exports the results."""
    position = Coordinates()
    status = self.pozyx.doPositioning(
        position, self.dimension, self.height, self.algorithm, remote_id=self.remote_id)
    if status == POZYX_SUCCESS:
        self.printPublishPosition(position)
    else:
        self.printPublishErrorCode("positioning")

We first create a new object that will contain the Pozyx’s measured coordinates, after which we call the Pozyx’s doPositioning function, which will perform the positioning algorithm and store the coordinates, measured respectively to the anchors, in the position object. That’s essentially the entire positioning loop! If doPositioning returns POZYX_SUCCESS, the position will be printed in a human readable way. We see that we can pass the positioning’s algorithm, dimension, and Pozyx height (used in 2.5D) as parameters in the positioning. There are three dimensions supported by Pozyx: POZYX_2D, POZYX_2_5D, and POZYX_3D.

In 2D the anchors and tags must all be located in the same horizontal plane. This is not the case for semi-3D or 3D positioning. In semi-3D positioning the height of the tag must be supplied in the height parameter (for example, when the tag is mounted on a driving robot with fixed height). The reason why semi-3D exists is because in many cases it is not possible to obtain an accurate estimate for the z-coordinate in 3D-positioning. This is a result of how the anchors are placed and is explained in the guide 'Where to place the anchors?'. As a final parameter you can supply which algorithm to use. Possible values are POZYX_POS_ALG_UWB_ONLY and POZYX_POS_ALG_TRACKING. By default POZYX_POS_ALG_UWB_ONLY is used. For more information about the algorithms we refer to POZYX_POS_ALG. We've defined the algorithm as a parameter, so you can change the algorithm in the paramaters section instead of directly doing so in the function.

Some example usages:

  • status = Pozyx.doPositioning(&position, POZYX_2_5D, 1000 ) semi-3D positioning with the height of the tag fixed at 1000mm (about 3.3feet).

  • status = Pozyx.doPositioning(&position, POZYX_3D) 3D positioning, this requires at least 4 anchors. Note that the anchors must be placed at different heights to obtain a good accuracy of the z-coordinate.

Remote positioning

Positioning the Pozyx attached to your PC means that, except for when you have a long cable, you'll also need to be able to move your PC around if you want to position over a larger area than your desk. This would also mean that if you'd want to track objects, you'd need to attach a processing unit to these objects as well, instead of only the Pozyx. Luckily, the Pozyx attached to your computer can act as a master device, commanding one or more remote 'slaves'.

To position a remote device, you need to do the same as on a local device: put anchors in its device list that it will use for the positioning. A common misconception about the use of Pozyx is that configuring the anchors on your local device will make remote devices capable of positioning straight off the bat, but this isn't the case. You will notice that the addDevice function in setAnchorsManual adds the device to a remote device if remote positioning is enabled, thusly configuring the anchors on the remote device and not on the local one.

Extras

While not necessary for positioning, the added functionality in the code can go a long way for extending the use of Pozyx or when things go wrong, so going over these extras is recommended if you want to take things further without needing to figure things out yourself.

Printing the configuration result

To find out whether the calibration was in fact successful, we will retrieve the Pozyx’s device list. This is the reverse of the configuration step, as we now retrieve the IDs and coordinates in turn from the Pozyx. Pozyx requires this to be done in several steps:

  • Firstly we retrieve the size of the device list through getDeviceListSize.

  • We use this size to create an appropriately sized device list container

  • We retrieve the IDs in the device’s device list using this container with getDeviceIds. You can also use getTagIds and getAnchorIds to have more control over which device IDs you’re getting.

  • Now we can get the device coordinates belonging to each of these IDs using getDeviceCoordinates

This is what is done in the code below, and we print the anchor’s ID with its retrieved coordinates. If these don’t match the anchors you passed to the device, something went wrong and it is recommended to try again. If this keeps failing, try going through the troubleshooting.

PY
def printPublishConfigurationResult(self):
    """Prints and potentially publishes the anchor configuration result in a human-readable way."""
    list_size = SingleRegister()
    status = self.pozyx.getDeviceListSize(list_size, self.remote_id)
    print("List size: {0}".format(list_size[0]))
    if list_size[0] != len(self.anchors):
        self.printPublishErrorCode("configuration")
        return
    device_list = DeviceList(list_size=list_size[0])
    status = self.pozyx.getDeviceIds(device_list, self.remote_id)
    print("Calibration result:")
    print("Anchors found: {0}".format(list_size[0]))
    print("Anchor IDs: ", device_list)
    for i in range(list_size[0]):
        anchor_coordinates = Coordinates()
        status = self.pozyx.getDeviceCoordinates(
            device_list[i], anchor_coordinates, self.remote_id)
        print("ANCHOR,0x%0.4x, %s" % (device_list[i], str(anchor_coordinates)))
        if self.osc_udp_client is not None:
            self.osc_udp_client.send_message(
                "/anchor", [device_list[i], int(anchor_coordinates.x), int(anchor_coordinates.y), int(anchor_coordinates.z)])
            sleep(0.025)

Printing the error code

Pozyx’s operation can misbehave due to various reasons. Other devices not being in range, being on different settings, or another firmware version… As you’re getting started with Pozyx, it’s hard to keep track of where exactly things go wrong, and it’s for this reason that Pozyx keeps track of what went wrong in the error status register, POZYX_ERRORCODE. The error code can be read in two ways, one uses the getErrorCode function to directly read out the value from the error register, while the other, through getSystemError, returns a more verbose error message, representing the relative error textually. For example, as the error code would be 0x05, getSystemError would return "Error 0x05: Error reading from a register from the I2C bus". While the latter is ideal when working in a printed environment, if you work with visualizations it’s less than ideal to handle this entire string and you can perform custom functionality with the error code.

PY
def printPublishErrorCode(self, operation):
    """Prints the Pozyx's error and possibly sends it as a OSC packet"""
    error_code = SingleRegister()
    network_id = self.remote_id
    if network_id is None:
        self.pozyx.getErrorCode(error_code)
        print("ERROR %s, local error code %s" % (operation, str(error_code)))
        if self.osc_udp_client is not None:
            self.osc_udp_client.send_message("/error", [operation, 0, error_code[0]])
        return
    status = self.pozyx.getErrorCode(error_code, self.remote_id)
    if status == POZYX_SUCCESS:
        print("ERROR %s on ID %s, error code %s" %
              (operation, "0x%0.4x" % network_id, str(error_code)))
        if self.osc_udp_client is not None:
            self.osc_udp_client.send_message(
                "/error", [operation, network_id, error_code[0]])
    else:
        self.pozyx.getErrorCode(error_code)
        print("ERROR %s, couldn't retrieve remote error code, local error code %s" %
              (operation, str(error_code)))
        if self.osc_udp_client is not None:
            self.osc_udp_client.send_message("/error", [operation, 0, -1])

In this example, simple but comprehensive error checking is executed. If the positioning is purely local, the local error is read. When remote positioning fails, indicated by the positioning function returning POZYX_FAILURE, the error register is read. If the error couldn’t be read remotely, the error is read locally. We are using getErrorCode instead of getSystemError because this allows us to efficiently send the error data to the visualization, and customize our error output. You can find the resulting error codes’ meaning at the register documentation of POZYX_ERRORCODE.

Adding sensor information

Sensor information, such as orientation or acceleration, can easily be added to the code, as to return this sensor data every positioning loop. Adding orientation and/or acceleration allows you to get a better insight in the object you're tracking, but you'll have to account for the reduced positioning update rate caused by this additional operation. Especially remotely, this will delay your update rate. In this example code, not present in the actual script, we'll retrieve both orientation and acceleration

PY
def printOrientationAcceleration(self):
    orientation = EulerAngles()
    acceleration = Acceleration()
    self.pozyx.getEulerAngles_deg(orientation, self.remote_id)
    self.pozyx.getAcceleration_mg(acceleration, self.remote_id)
    print("Orientation: %s, acceleration: %s" % (str(orientation), str(acceleration))


JavaScript errors detected

Please note, these errors can depend on your browser setup.

If this problem persists, please contact our support.