├── docs ├── system │ └── index.rst ├── tips-and-tricks │ └── index.rst ├── tutorials │ └── index.rst ├── index.rst ├── Makefile ├── troubleshooting │ └── index.rst ├── pypozyx_api │ ├── definitions.rst │ ├── structures.rst │ └── index.rst ├── make.bat ├── overview │ └── index.rst ├── conf.py └── getting_started │ └── index.rst ├── pypozyx ├── tools │ ├── __init__.py │ ├── device_list.py │ ├── discovery.py │ └── version_check.py ├── pozyx_i2c.py ├── definitions │ ├── __init__.py │ ├── bitmasks.py │ ├── registers.py │ └── constants.py ├── structures │ ├── __init__.py │ ├── device_information.py │ ├── byte_structure.py │ ├── generic.py │ ├── sensor_data.py │ └── device.py ├── __init__.py └── pozyx_serial.py ├── unit_tests ├── test_system.py ├── test_device_list.py ├── test_positioning.py ├── test_wireless_communication.py ├── test_structures.py ├── conftest.py ├── test_core.py └── test_sensor.py ├── requirements.txt ├── setup.cfg ├── package-and-upload.sh ├── requirements-tutorials.txt ├── MANIFEST ├── useful ├── change_network_id.py ├── basic_troubleshooting.py ├── change_uwb_settings.py ├── system_analysis.py └── set_same_settings.py ├── .gitignore ├── setup.py ├── README.md ├── tutorials ├── ready_to_range_no_leds.py ├── orientation_3D.py ├── ready_to_range.py ├── multitag_positioning.py └── ready_to_localize.py └── LICENSE.txt /docs/system/index.rst: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /pypozyx/tools/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /unit_tests/test_system.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /docs/tips-and-tricks/index.rst: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /unit_tests/test_device_list.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /unit_tests/test_positioning.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | pyserial>=3.0 2 | -------------------------------------------------------------------------------- /unit_tests/test_wireless_communication.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | [metadata] 2 | description-file = README.rst 3 | -------------------------------------------------------------------------------- /unit_tests/test_structures.py: -------------------------------------------------------------------------------- 1 | # DATA AND BYTESTRUCTURES AND SINGLEREGISTER 2 | -------------------------------------------------------------------------------- /package-and-upload.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | python setup.py bdist_wheel --universal 3 | twine upload dist/* -------------------------------------------------------------------------------- /requirements-tutorials.txt: -------------------------------------------------------------------------------- 1 | pyserial>=3.0 2 | python-osc # needed for communication with Processing 3 | requests # needed for version check 4 | -------------------------------------------------------------------------------- /docs/tutorials/index.rst: -------------------------------------------------------------------------------- 1 | Tutorials 2 | ========= 3 | 4 | Ready to range 5 | -------------- 6 | 7 | 8 | Ready to localize 9 | ----------------- 10 | 11 | 12 | Obtaining the sensor data 13 | ------------------------- 14 | 15 | 16 | Multitag positioning 17 | -------------------- -------------------------------------------------------------------------------- /docs/index.rst: -------------------------------------------------------------------------------- 1 | .. pypozyx documentation master file, created by 2 | sphinx-quickstart on Mon Jun 11 15:35:40 2018. 3 | You can adapt this file completely to your liking, but it should at least 4 | contain the root `toctree` directive. 5 | 6 | Welcome to pypozyx's documentation! 7 | =================================== 8 | 9 | This module provides wrappers for interfacing with a Pozyx device over a serial connection. 10 | 11 | .. toctree:: 12 | :maxdepth: 2 13 | :caption: Contents: 14 | 15 | overview/index 16 | getting_started/index 17 | examples/index 18 | pypozyx_api/index 19 | troubleshooting/index 20 | 21 | 22 | -------------------------------------------------------------------------------- /MANIFEST: -------------------------------------------------------------------------------- 1 | # file GENERATED by distutils, do NOT edit 2 | setup.cfg 3 | setup.py 4 | pypozyx/__init__.py 5 | pypozyx/core.py 6 | pypozyx/i2c.py 7 | pypozyx/lib.py 8 | pypozyx/pozyx_i2c.py 9 | pypozyx/pozyx_serial.py 10 | pypozyx/definitions/__init__.py 11 | pypozyx/definitions/bitmasks.py 12 | pypozyx/definitions/constants.py 13 | pypozyx/definitions/registers.py 14 | pypozyx/structures/__init__.py 15 | pypozyx/structures/byte_structure.py 16 | pypozyx/structures/device.py 17 | pypozyx/structures/generic.py 18 | pypozyx/structures/sensor_data.py 19 | pypozyx/tests/__init__.py 20 | pypozyx/tests/basic_tests.py 21 | pypozyx/tests/byte_structure_tests.py 22 | -------------------------------------------------------------------------------- /docs/Makefile: -------------------------------------------------------------------------------- 1 | # Minimal makefile for Sphinx documentation 2 | # 3 | 4 | # You can set these variables from the command line. 5 | SPHINXOPTS = 6 | SPHINXBUILD = sphinx-build 7 | SPHINXPROJ = pypozyx 8 | SOURCEDIR = . 9 | BUILDDIR = _build 10 | 11 | # Put it first so that "make" without argument is like "make help". 12 | help: 13 | @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) 14 | 15 | .PHONY: help Makefile 16 | 17 | # Catch-all target: route all unknown targets to Sphinx using the new 18 | # "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). 19 | %: Makefile 20 | @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) -------------------------------------------------------------------------------- /docs/troubleshooting/index.rst: -------------------------------------------------------------------------------- 1 | Troubleshooting 2 | =============== 3 | 4 | FAQ 5 | --- 6 | 7 | Lost a device? 8 | -------------- 9 | 10 | Contacting support 11 | ------------------ 12 | 13 | If you want to contact support, please include the following: 14 | 15 | * Run the troubleshooting.py (provide link to github location of script) script and attach its output. 16 | * Mention what you want to achieve. Our support team has experience with many use cases and can set you on the right track. 17 | * If you get an error or exception, please include this in your mail instead of just saying something is broken. 18 | 19 | Ultimately, the more information you can provide our support team from the start, the less they'll have to ask of you and the quicker your problem resolution. 20 | -------------------------------------------------------------------------------- /pypozyx/tools/device_list.py: -------------------------------------------------------------------------------- 1 | from pypozyx import SingleRegister, DeviceList, DeviceCoordinates, Coordinates 2 | 3 | 4 | def all_device_coordinates_in_device_list(pozyx, remote_id=None): 5 | list_size = SingleRegister() 6 | status = pozyx.getDeviceListSize(list_size, remote_id=remote_id) 7 | 8 | if list_size.value == 0: 9 | # TODO investigate if valid? 10 | return 11 | 12 | device_list = DeviceList(list_size=list_size.value) 13 | status &= pozyx.getDeviceIds(device_list, remote_id=remote_id) 14 | 15 | for device_id in device_list: 16 | coordinates = Coordinates() 17 | pozyx.getDeviceCoordinates(device_id, coordinates, remote_id=remote_id) 18 | yield DeviceCoordinates(device_id, 0, pos=coordinates) 19 | 20 | 21 | -------------------------------------------------------------------------------- /pypozyx/pozyx_i2c.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | """ 3 | 4 | """ 5 | 6 | from pypozyx.lib import PozyxLib 7 | from pypozyx.definitions.constants import MODE_POLLING 8 | # from pypozyx.definitions.registers import * 9 | 10 | 11 | class PozyxI2C(PozyxLib): 12 | 13 | def __init__(self, mode=MODE_POLLING, print_output=False): 14 | pass 15 | 16 | def regWrite(self, address, data): 17 | pass 18 | 19 | def regRead(self, address, data): 20 | pass 21 | 22 | def regFunction(self, address, params, data): 23 | pass 24 | 25 | # 26 | def waitForFlag(self, interrupt_flag, timeout_ms, interrupt): 27 | pass 28 | 29 | # 30 | def waitForFlagSafe(self, interrupt_flag, timeout_ms, interrupt): 31 | pass 32 | 33 | def configInterruptPin(self, pin, mode, bActiveHigh, bLatch, remote_id=None): 34 | pass 35 | -------------------------------------------------------------------------------- /docs/pypozyx_api/definitions.rst: -------------------------------------------------------------------------------- 1 | pypozyx.definitions package 2 | =========================== 3 | 4 | Submodules 5 | ---------- 6 | 7 | pypozyx.definitions.bitmasks module 8 | ----------------------------------- 9 | 10 | .. automodule:: pypozyx.definitions.bitmasks 11 | :members: 12 | :undoc-members: 13 | :show-inheritance: 14 | 15 | pypozyx.definitions.constants module 16 | ------------------------------------ 17 | 18 | .. automodule:: pypozyx.definitions.constants 19 | :members: 20 | :undoc-members: 21 | :show-inheritance: 22 | 23 | pypozyx.definitions.registers module 24 | ------------------------------------ 25 | 26 | .. automodule:: pypozyx.definitions.registers 27 | :members: 28 | :undoc-members: 29 | :show-inheritance: 30 | 31 | 32 | Module contents 33 | --------------- 34 | 35 | .. automodule:: pypozyx.definitions 36 | :members: 37 | :undoc-members: 38 | :show-inheritance: 39 | -------------------------------------------------------------------------------- /pypozyx/definitions/__init__.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | """ 3 | pypozyx.definitions - contains all Pozyx constant definitions. 4 | 5 | These definitions are divided in three large groups: 6 | - constants: physical constants, type definitions... 7 | - bitmasks: bitmasks used for ANDing register data against, especially useful for interrupt status data. 8 | - registers: definitions of the register addresses, for more 'advanced' use. 9 | 10 | When importing this, only the constants will get imported. To import the registers and bitmasks, 11 | use the following code: 12 | >>> from pypozyx.definitions.registers import * # or specific registers for best practice 13 | >>> from pypozyx.definitions.bitmasks import * # or specific bitmasks for best practice 14 | """ 15 | from pypozyx.definitions.constants import * 16 | from pypozyx.definitions.bitmasks import PozyxBitmasks 17 | from pypozyx.definitions.registers import PozyxRegisters 18 | -------------------------------------------------------------------------------- /docs/make.bat: -------------------------------------------------------------------------------- 1 | @ECHO OFF 2 | 3 | pushd %~dp0 4 | 5 | REM Command file for Sphinx documentation 6 | 7 | if "%SPHINXBUILD%" == "" ( 8 | set SPHINXBUILD=sphinx-build 9 | ) 10 | set SOURCEDIR=. 11 | set BUILDDIR=_build 12 | set SPHINXPROJ=pypozyx 13 | 14 | if "%1" == "" goto help 15 | 16 | %SPHINXBUILD% >NUL 2>NUL 17 | if errorlevel 9009 ( 18 | echo. 19 | echo.The 'sphinx-build' command was not found. Make sure you have Sphinx 20 | echo.installed, then set the SPHINXBUILD environment variable to point 21 | echo.to the full path of the 'sphinx-build' executable. Alternatively you 22 | echo.may add the Sphinx directory to PATH. 23 | echo. 24 | echo.If you don't have Sphinx installed, grab it from 25 | echo.http://sphinx-doc.org/ 26 | exit /b 1 27 | ) 28 | 29 | %SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% 30 | goto end 31 | 32 | :help 33 | %SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% 34 | 35 | :end 36 | popd 37 | -------------------------------------------------------------------------------- /docs/pypozyx_api/structures.rst: -------------------------------------------------------------------------------- 1 | pypozyx.structures package 2 | ========================== 3 | 4 | Submodules 5 | ---------- 6 | 7 | pypozyx.structures.byte\_structure module 8 | ----------------------------------------- 9 | 10 | .. automodule:: pypozyx.structures.byte_structure 11 | :members: 12 | :undoc-members: 13 | :show-inheritance: 14 | 15 | pypozyx.structures.device module 16 | -------------------------------- 17 | 18 | .. automodule:: pypozyx.structures.device 19 | :members: 20 | :undoc-members: 21 | :show-inheritance: 22 | 23 | pypozyx.structures.generic module 24 | --------------------------------- 25 | 26 | .. automodule:: pypozyx.structures.generic 27 | :members: 28 | :undoc-members: 29 | :show-inheritance: 30 | 31 | pypozyx.structures.sensor\_data module 32 | -------------------------------------- 33 | 34 | .. automodule:: pypozyx.structures.sensor_data 35 | :members: 36 | :undoc-members: 37 | :show-inheritance: 38 | 39 | 40 | Module contents 41 | --------------- 42 | 43 | .. automodule:: pypozyx.structures 44 | :members: 45 | :undoc-members: 46 | :show-inheritance: 47 | -------------------------------------------------------------------------------- /useful/change_network_id.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | """change_network_id.py - Changes a local/remote device's network ID.""" 3 | 4 | from pypozyx import * 5 | from pypozyx.definitions.registers import * 6 | 7 | 8 | def set_new_id(pozyx, new_id, remote_id): 9 | print("Setting the Pozyx ID to 0x%0.4x" % new_id) 10 | pozyx.setNetworkId(new_id, remote_id) 11 | if pozyx.saveConfiguration(POZYX_FLASH_REGS, [POZYX_NETWORK_ID], remote_id) == POZYX_SUCCESS: 12 | print("Saving new ID successful! Resetting system...") 13 | if pozyx.resetSystem(remote_id) == POZYX_SUCCESS: 14 | print("Done") 15 | 16 | 17 | if __name__ == "__main__": 18 | 19 | serial_port = get_first_pozyx_serial_serial_port() 20 | if serial_port is None: 21 | print("No Pozyx connected. Check your USB cable or your driver!") 22 | quit() 23 | 24 | new_id = 0xA004 # the new network id of the pozyx device, change as desired 25 | remote = False # whether to use the remote device 26 | remote_id = 0x6F5E # the remote ID 27 | 28 | if not remote: 29 | remote_id = None 30 | 31 | pozyx = PozyxSerial(serial_port) 32 | set_new_id(pozyx, new_id, remote_id) 33 | -------------------------------------------------------------------------------- /docs/pypozyx_api/index.rst: -------------------------------------------------------------------------------- 1 | pypozyx API 2 | =============== 3 | 4 | PozyxSerial 5 | ----------- 6 | 7 | This also includes the serial helpers you can use to connect to the Pozyx device. 8 | 9 | .. automodule:: pypozyx.pozyx_serial 10 | :members: 11 | :undoc-members: 12 | :show-inheritance: 13 | 14 | Functions 15 | --------- 16 | 17 | Do a split like in the current library documentation here? 18 | 19 | .. automodule:: pypozyx.lib 20 | :members: 21 | :undoc-members: 22 | :show-inheritance: 23 | 24 | 25 | Data structures 26 | --------------- 27 | 28 | .. toctree:: 29 | 30 | pypozyx_api/structures 31 | 32 | Generic 33 | ~~~~~~~ 34 | 35 | Device data 36 | ~~~~~~~~~~~ 37 | 38 | Sensor data 39 | ~~~~~~~~~~~ 40 | 41 | 42 | Constants, bitmasks, registers 43 | ------------------------------ 44 | 45 | .. toctree:: 46 | 47 | pypozyx_api/definitions 48 | 49 | 50 | .. .. automodule:: pypozyx.core 51 | :members: 52 | :undoc-members: 53 | :show-inheritance: 54 | 55 | .. .. automodule:: pypozyx.lib 56 | :members: 57 | :undoc-members: 58 | :show-inheritance: 59 | 60 | .. .. automodule:: pypozyx.pozyx_i2c 61 | :members: 62 | :undoc-members: 63 | :show-inheritance: 64 | -------------------------------------------------------------------------------- /pypozyx/structures/__init__.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | """ 3 | pypozyx.structures - contains ByteStructure and derived classes that contain the Pozyx data. 4 | 5 | The PyPozyx library is designed to work with these objects, and they abstract the low-level 6 | approach that would otherwise be necessary in using the serial port, so these are very 7 | recommended to use. 8 | 9 | This package is subdivided in three groups of structures, derived from ByteStructure: 10 | - device: containers for device functionality. Network ID, UWB settings, device coordinates/range 11 | - sensor_data: containers for sensor data retrieved from the Pozyx. Magnetic, acceleration, etc. 12 | These also take care of the physical convertion to the respective standard units for that sensor. 13 | - generic: as the name suggests, generic containers. The SingleRegister object is a 14 | multipurpose object that can be used to read single registers of any size. Data can be 15 | used to create your own arbitrary packed structures. 16 | 17 | When importing this package, you will get all of the device classes, the sensor data classes, 18 | and both SingleRegister and Data. 19 | """ 20 | from pypozyx.structures.device import * 21 | from pypozyx.structures.sensor_data import * 22 | from pypozyx.structures.generic import SingleRegister, Data 23 | -------------------------------------------------------------------------------- /unit_tests/conftest.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | from pypozyx import * 3 | 4 | def pytest_addoption(parser): 5 | parser.addoption("--dec", action="store", default=False, 6 | help="whether the device ID is a decimal instead of hex") 7 | parser.addoption("--remote", action="store", default=None, 8 | help="the remote ID for testing when applicable") 9 | parser.addoption("--interface", action="store", default=None, 10 | help="the interface to test, defaults to serial") 11 | # TODO: add port for serial 12 | 13 | 14 | @pytest.fixture(scope='session') 15 | def remote(request): 16 | remote = request.config.getoption("--remote") 17 | base = 16 18 | if request.config.getoption("--dec") != False and request.config.getoption("--dec") != '0' and request.config.getoption("--dec") != 'false': 19 | base = 10 20 | if remote is not None: 21 | remote = int(remote, base) 22 | return remote 23 | 24 | 25 | @pytest.fixture(scope='session') 26 | def pozyx(request): 27 | interface = request.config.getoption("--interface") 28 | return PozyxSerial(get_serial_ports()[0].device) 29 | if interface == 'serial' or interface is None: 30 | return PozyxSerial(get_serial_ports()[0].device) 31 | else: 32 | print("not a valid setting for interface option, better not use it right now... giving a pozyx serial anyway") 33 | return PozyxSerial(get_serial_ports()[0].device) 34 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | env/ 12 | build/ 13 | develop-eggs/ 14 | dist/ 15 | downloads/ 16 | eggs/ 17 | .eggs/ 18 | lib/ 19 | lib64/ 20 | parts/ 21 | sdist/ 22 | var/ 23 | tests/ 24 | *.egg-info/ 25 | .installed.cfg 26 | *.egg 27 | 28 | # PyInstaller 29 | # Usually these files are written by a python script from a template 30 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 31 | *.manifest 32 | *.spec 33 | 34 | # Installer logs 35 | pip-log.txt 36 | pip-delete-this-directory.txt 37 | 38 | # Unit test / coverage reports 39 | htmlcov/ 40 | .tox/ 41 | .coverage 42 | .coverage.* 43 | .cache 44 | nosetests.xml 45 | coverage.xml 46 | *,cover 47 | .hypothesis/ 48 | 49 | # Translations 50 | *.mo 51 | *.pot 52 | 53 | # Django stuff: 54 | *.log 55 | local_settings.py 56 | 57 | # Flask stuff: 58 | instance/ 59 | .webassets-cache 60 | 61 | # Scrapy stuff: 62 | .scrapy 63 | 64 | # Sphinx documentation 65 | docs/_build/ 66 | 67 | # PyBuilder 68 | target/ 69 | 70 | # IPython Notebook 71 | .ipynb_checkpoints 72 | 73 | # pyenv 74 | .python-version 75 | 76 | # celery beat schedule file 77 | celerybeat-schedule 78 | 79 | # dotenv 80 | .env 81 | 82 | # virtualenv 83 | venv/ 84 | ENV/ 85 | 86 | # Spyder project settings 87 | .spyderproject 88 | 89 | # Rope project settings 90 | .ropeproject 91 | 92 | # Pycharm project settings 93 | .idea 94 | 95 | .pytest_cache 96 | 97 | build -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from os import path 3 | from setuptools import setup 4 | from codecs import open 5 | 6 | from pypozyx import VERSION as PYPOZYX_VERSION 7 | 8 | here = path.abspath(path.dirname(__file__)) 9 | # Get the long description from the README file 10 | with open(path.join(here, 'README.rst'), encoding='utf-8') as readme: 11 | long_description = readme.read() 12 | 13 | setup( 14 | name='pypozyx', 15 | packages=['pypozyx', 'pypozyx.definitions', 'pypozyx.tools', 16 | 'pypozyx.structures'], 17 | version=PYPOZYX_VERSION, 18 | description='Python library for Pozyx devices', 19 | install_requires=[ 20 | 'pyserial>=3.0' 21 | ], 22 | long_description=long_description, 23 | author='Laurent Van Acker', 24 | license='GPLv3', 25 | author_email='laurent@pozyx.io', 26 | url='https://github.com/pozyxLabs/Pozyx-Python-library', 27 | download_url='https://github.com/pozyxLabs/Pozyx-Python-library/archive/v{}.tar.gz'.format(PYPOZYX_VERSION), 28 | keywords=['pozyx', 'serial', 'positioning', 'localisation'], 29 | classifiers=[ 30 | 'Programming Language :: Python', 31 | 'Development Status :: 5 - Production/Stable', 32 | 'Intended Audience :: End Users/Desktop', 33 | 'Operating System :: OS Independent', 34 | 'License :: OSI Approved :: GNU General Public License v3 (GPLv3)', 35 | 'Topic :: Software Development :: Libraries :: Python Modules', 36 | 'Topic :: Scientific/Engineering', 37 | ], 38 | 39 | ) 40 | -------------------------------------------------------------------------------- /pypozyx/tools/discovery.py: -------------------------------------------------------------------------------- 1 | from pypozyx import PozyxConstants, UWBSettings, SingleRegister, DeviceList 2 | 3 | 4 | def all_discovery_uwb_settings(): 5 | for channel in PozyxConstants.ALL_UWB_CHANNELS: 6 | for bitrate in PozyxConstants.ALL_UWB_BITRATES: 7 | for prf in PozyxConstants.ALL_UWB_PRFS: 8 | for plen in PozyxConstants.ALL_UWB_PLENS: 9 | yield UWBSettings(channel, bitrate, prf, plen, 33) 10 | 11 | 12 | def get_device_list(pozyx): 13 | """""" 14 | device_list_size = SingleRegister() 15 | pozyx.getDeviceListSize(device_list_size) 16 | device_list = DeviceList(list_size=device_list_size[0]) 17 | pozyx.getDeviceIds(device_list) 18 | return device_list 19 | 20 | 21 | def discover_all_devices(pozyx): 22 | original_uwb_settings = UWBSettings() 23 | pozyx.getUWBSettings(original_uwb_settings) 24 | 25 | for uwb_settings in all_discovery_uwb_settings(): 26 | pozyx.setUWBSettings(uwb_settings) 27 | 28 | pozyx.clearDevices() 29 | pozyx.doDiscoveryAll(slots=3, slot_duration=0.1) 30 | 31 | device_list = get_device_list(pozyx) 32 | if device_list: 33 | print("Found on {}".format(uwb_settings)) 34 | for device_id in device_list: 35 | uwbOnDevice = UWBSettings() 36 | status = pozyx.getUWBSettings(uwbOnDevice, device_id) 37 | if status == PozyxConstants.STATUS_SUCCESS: 38 | print("\t- {}, {}".format(hex(device_id), uwbOnDevice)) 39 | else: 40 | print("\t- {}".format(hex(device_id))) 41 | 42 | pozyx.setUWBSettings(original_uwb_settings) 43 | 44 | 45 | if __name__ == '__main__': 46 | from pypozyx import PozyxSerial, get_first_pozyx_serial_port 47 | pozyx = PozyxSerial(get_first_pozyx_serial_port()) 48 | 49 | uwb = UWBSettings() 50 | pozyx.getUWBSettings(uwb) 51 | print("Local device on {}".format(uwb)) 52 | 53 | discover_all_devices(pozyx) 54 | -------------------------------------------------------------------------------- /unit_tests/test_core.py: -------------------------------------------------------------------------------- 1 | from pypozyx import * 2 | from pypozyx.definitions.registers import POZYX_WHO_AM_I, POZYX_CONFIG_GPIO1 3 | 4 | 5 | def test_read_register(pozyx, remote): 6 | status = pozyx.getRead(POZYX_WHO_AM_I, SingleRegister(), remote) 7 | assert status == POZYX_SUCCESS, "register read unsuccessful" 8 | 9 | 10 | def test_read_5_registers(pozyx, remote): 11 | status = pozyx.getRead(POZYX_WHO_AM_I, Data(data=[0] * 10), remote) 12 | assert status == POZYX_SUCCESS, "10 register read unsuccessful" 13 | 14 | 15 | def test_read_50_registers(pozyx, remote): 16 | status = pozyx.getRead(POZYX_WHO_AM_I, Data(data=[0] * 50), remote) 17 | assert status == POZYX_SUCCESS, "50 register read unsuccessful" 18 | 19 | 20 | def test_read_who_am_i(pozyx, remote): 21 | who_am_i = SingleRegister() 22 | status = pozyx.getRead(POZYX_WHO_AM_I, who_am_i, remote) 23 | assert status == POZYX_SUCCESS, "couldn't read POZYX_WHO_AM_I register" 24 | assert who_am_i.value == 0x43, "POZYX_WHO_AM_I register has a wrong value" 25 | 26 | 27 | def test_write_register(pozyx, remote): 28 | status = pozyx.setWrite( 29 | POZYX_CONFIG_GPIO1, SingleRegister(0x10), remote) 30 | assert status == POZYX_SUCCESS, "register write unsuccessful" 31 | config_gpio1 = SingleRegister() 32 | status = pozyx.getRead( 33 | POZYX_CONFIG_GPIO1, config_gpio1, remote) == POZYX_SUCCESS 34 | assert status == POZYX_SUCCESS, "register read unsuccessful" 35 | assert config_gpio1.value == 0x10, "written and read values don't match" 36 | 37 | 38 | def test_no_write_who_am_i(pozyx, remote): 39 | status = pozyx.setWrite(POZYX_WHO_AM_I, SingleRegister(0x10), remote) 40 | assert status != POZYX_SUCCESS, "who am i register write unsuccessful" 41 | who_am_i = SingleRegister() 42 | status = pozyx.getRead(POZYX_WHO_AM_I, who_am_i, remote) 43 | assert status == POZYX_SUCCESS, "couldn't read POZYX_WHO_AM_I register" 44 | assert who_am_i.value != 0x10, "Could write to POZYX_WHO_AM_I" 45 | -------------------------------------------------------------------------------- /useful/basic_troubleshooting.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | """ 3 | Pozyx basic troubleshooting (c) Pozyx Labs 2017 4 | 5 | If you're experiencing trouble with Pozyx, this should be your first step to check for problems. 6 | Please read the article on https://www.pozyx.io/Documentation/Tutorials/troubleshoot_basics/Python 7 | """ 8 | 9 | from pypozyx import PozyxSerial, get_first_pozyx_serial_port, PozyxConstants, POZYX_SUCCESS 10 | from pypozyx.structures.device_information import DeviceDetails 11 | from pypozyx.definitions.registers import POZYX_WHO_AM_I 12 | 13 | 14 | def device_check(pozyx, remote_id=None): 15 | system_details = DeviceDetails() 16 | pozyx.getDeviceDetails(system_details, remote_id=remote_id) 17 | 18 | if remote_id is None: 19 | print("Local %s with id 0x%0.4x" % (system_details.device_name, system_details.id)) 20 | else: 21 | print("%s with id 0x%0.4x" % (system_details.device_name.capitalize(), system_details.id)) 22 | 23 | print("\tWho am i: 0x%0.2x" % system_details.who_am_i) 24 | print("\tFirmware version: v%s" % system_details.firmware_version_string) 25 | print("\tHardware version: v%s" % system_details.hardware_version_string) 26 | print("\tSelftest result: %s" % system_details.selftest_string) 27 | print("\tError: 0x%0.2x" % system_details.error_code) 28 | print("\tError message: %s" % system_details.error_message) 29 | 30 | 31 | def network_check_discovery(pozyx, remote_id=None): 32 | pozyx.clearDevices(remote_id) 33 | if pozyx.doDiscovery(discovery_type=PozyxConstants.DISCOVERY_ALL_DEVICES, remote_id=remote_id) == POZYX_SUCCESS: 34 | print("Found devices:") 35 | pozyx.printDeviceList(remote_id, include_coordinates=False) 36 | 37 | 38 | if __name__ == '__main__': 39 | serial_port = get_first_pozyx_serial_port() 40 | if serial_port is None: 41 | print("No Pozyx connected. Check your USB cable or your driver!") 42 | quit() 43 | 44 | pozyx = PozyxSerial(serial_port) 45 | 46 | # change to remote ID for troubleshooting that device 47 | remote_id = None 48 | 49 | device_check(pozyx, remote_id) 50 | network_check_discovery(pozyx, remote_id) 51 | -------------------------------------------------------------------------------- /pypozyx/structures/device_information.py: -------------------------------------------------------------------------------- 1 | from pypozyx.definitions.constants import PozyxConstants, ERROR_MESSAGES 2 | from pypozyx.structures.byte_structure import ByteStructure 3 | 4 | 5 | class DeviceDetails(ByteStructure): 6 | """Container for system information, such as firmware, hardware... 7 | 8 | Read-only, so all values are implemented as properties. 9 | """ 10 | 11 | byte_size = 5 12 | data_format = 'BBBBB' 13 | 14 | def __init__(self, network_id=None): 15 | super(DeviceDetails, self).__init__() 16 | 17 | self.id = network_id 18 | 19 | def load(self, data, convert=True): 20 | self.data = data 21 | 22 | @property 23 | def firmware_version(self): 24 | return self.data[1] 25 | 26 | @property 27 | def hardware_version(self): 28 | return self.data[2] 29 | 30 | @property 31 | def who_am_i(self): 32 | return self.data[0] 33 | 34 | @property 35 | def selftest(self): 36 | return self.data[3] 37 | 38 | @property 39 | def error_code(self): 40 | return self.data[4] 41 | 42 | @property 43 | def error_message(self): 44 | return ERROR_MESSAGES[self.error_code] 45 | 46 | @property 47 | def firmware_version_major(self): 48 | return self.firmware_version >> 4 49 | 50 | @property 51 | def firmware_version_minor(self): 52 | return self.firmware_version & 0xF 53 | 54 | @property 55 | def firmware_version_string(self): 56 | return "{}.{}".format(self.firmware_version_major, self.firmware_version_minor) 57 | 58 | @property 59 | def device_type(self): 60 | return self.data[2] >> 5 61 | 62 | @property 63 | def device_name(self): 64 | if self.device_type == PozyxConstants.TAG_MODE: 65 | return "tag" 66 | return "anchor" 67 | 68 | @property 69 | def hardware_version_string(self): 70 | return "1.{}".format(self.hardware_version & 0x1F) 71 | 72 | @property 73 | def device_string(self): 74 | if self.id is None: 75 | return "None" 76 | return "0x%0.4x" % self.id 77 | 78 | @property 79 | def selftest_string(self): 80 | return bin(self.selftest) 81 | 82 | 83 | class PositioningSettings(object): 84 | pass 85 | 86 | 87 | class DeviceInformation(object): 88 | pass -------------------------------------------------------------------------------- /pypozyx/definitions/bitmasks.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | """pypozyx.definitions.bitmasks - contains all bitmasks used in Pozyx functionality, such as interrupt flags.""" 3 | 4 | 5 | class PozyxBitmasks: 6 | # Pozyx firmware identifiers 7 | FIRMWARE_MAJOR = 0xF0 8 | FIRMWARE_MINOR = 0xF 9 | 10 | # Pozyx device identifier for hardware 11 | ANCHOR = 0x00 12 | TAG = 0x20 13 | 14 | # Bit mask for POZYX_ST_RESULT 15 | SELFTEST_RESULT_ACCELEROMETER = 0x01 16 | SELFTEST_RESULT_MAGNETOMETER = 0x02 17 | SELFTEST_RESULT_GYRO = 0x04 18 | SELFTEST_RESULT_MCU = 0x08 19 | SELFTEST_RESULT_PRESSURE = 0x10 20 | SELFTEST_RESULT_UWB = 0x20 21 | 22 | # Bit mask for POZYX_INT_STATUS 23 | INT_STATUS_ERR = 0x01 24 | INT_STATUS_POS = 0x02 25 | INT_STATUS_IMU = 0x04 26 | INT_STATUS_RX_DATA = 0x08 27 | INT_STATUS_FUNC = 0x10 28 | 29 | # Bit mask for POZYX_INT_MASK 30 | INT_MASK_ERR = 0x01 31 | INT_MASK_POS = 0x02 32 | INT_MASK_IMU = 0x04 33 | INT_MASK_RX_DATA = 0x08 34 | INT_MASK_FUNC = 0x10 35 | INT_MASK_TDMA = 0x40 36 | INT_MASK_PIN = 0x80 37 | INT_MASK_ALL = 0x1F 38 | 39 | # Bit mask for POZYX_LED_CTRL 40 | LED_CTRL_LED1 = 0x01 41 | LED_CTRL_LED2 = 0x02 42 | LED_CTRL_LED3 = 0x04 43 | LED_CTRL_LED4 = 0x08 44 | 45 | # Bit mask for device type 46 | DEVICE_TYPE = 0xE0 47 | 48 | # Bit mask for POZYX_ST_RESULT 49 | POZYX_ST_RESULT_ACC = 0x01 50 | POZYX_ST_RESULT_MAGN = 0x02 51 | POZYX_ST_RESULT_GYR = 0x04 52 | POZYX_ST_RESULT_MCU = 0x08 53 | POZYX_ST_RESULT_PRES = 0x10 54 | POZYX_ST_RESULT_UWB = 0x20 55 | 56 | # Bit mask for POZYX_INT_STATUS 57 | POZYX_INT_STATUS_ERR = 0x01 58 | POZYX_INT_STATUS_POS = 0x02 59 | POZYX_INT_STATUS_IMU = 0x04 60 | POZYX_INT_STATUS_RX_DATA = 0x08 61 | POZYX_INT_STATUS_FUNC = 0x10 62 | 63 | # Bit mask for POZYX_INT_MASK 64 | POZYX_INT_MASK_ERR = 0x01 65 | POZYX_INT_MASK_POS = 0x02 66 | POZYX_INT_MASK_IMU = 0x04 67 | POZYX_INT_MASK_RX_DATA = 0x08 68 | POZYX_INT_MASK_FUNC = 0x10 69 | POZYX_INT_MASK_TDMA = 0x40 70 | POZYX_INT_MASK_PIN = 0x80 71 | POZYX_INT_MASK_ALL = 0x1F 72 | 73 | # Bit mask for POZYX_LED_CTRL 74 | POZYX_LED_CTRL_LED1 = 0x01 75 | POZYX_LED_CTRL_LED2 = 0x02 76 | POZYX_LED_CTRL_LED3 = 0x04 77 | POZYX_LED_CTRL_LED4 = 0x08 78 | 79 | # Remote operations 80 | POZYX_REMOTE_READ = 0x02 81 | POZYX_REMOTE_WRITE = 0x04 82 | POZYX_REMOTE_DATA = 0x06 83 | POZYX_REMOTE_FUNCTION = 0x08 84 | 85 | # Bit mask for device type 86 | POZYX_TYPE = 0xE0 87 | -------------------------------------------------------------------------------- /pypozyx/tools/version_check.py: -------------------------------------------------------------------------------- 1 | import requests 2 | import json 3 | import pypozyx 4 | import warnings 5 | 6 | 7 | class Version(object): 8 | def __init__(self, version_string): 9 | self.version = version_string 10 | if version_string.startswith("v"): 11 | self.version = version_string[1:] 12 | 13 | if self.version.count('.') == 2: 14 | self.major, self.minor, self.bugfix = self.version.split('.') 15 | else: 16 | self.major, self.minor, self.bugfix = self.version.split('.') + ['0'] 17 | 18 | def __eq__(self, other): 19 | return self.major == other.major and self.minor == other.minor and self.bugfix == other.bugfix 20 | 21 | def __gt__(self, other): 22 | if self.major > other.major: 23 | return True 24 | elif self.major < other.major: 25 | return False 26 | else: 27 | if self.minor > other.minor: 28 | return True 29 | elif self.minor < other.minor: 30 | return False 31 | else: 32 | if self.bugfix > other.bugfix: 33 | return True 34 | elif self.bugfix < other.bugfix: 35 | return False 36 | return False 37 | 38 | def __ge__(self, other): 39 | if self.major > other.major: 40 | return True 41 | elif self.major < other.major: 42 | return False 43 | else: 44 | if self.minor > other.minor: 45 | return True 46 | elif self.minor < other.minor: 47 | return False 48 | else: 49 | if self.bugfix > other.bugfix: 50 | return True 51 | elif self.bugfix < other.bugfix: 52 | return False 53 | return True 54 | 55 | def __str__(self): 56 | return "{}.{}.{}".format(self.major, self.minor, self.bugfix) 57 | 58 | def __repr__(self): 59 | return str(self) 60 | 61 | 62 | def is_on_latest_version(): 63 | response = requests.get('https://api.github.com/repos/pozyxLabs/Pozyx-Python-library/releases') 64 | 65 | releases = json.loads(response.content.decode()) 66 | 67 | versions = [Version(release["tag_name"]) for release in releases] 68 | 69 | highest_release = max(versions) 70 | 71 | pypozyx_version = Version(pypozyx.version) 72 | 73 | if pypozyx_version >= highest_release: 74 | return True, pypozyx_version 75 | else: 76 | return False, highest_release 77 | 78 | 79 | def perform_latest_version_check(): 80 | try: 81 | on_latest_version, latest_version = is_on_latest_version() 82 | if on_latest_version: 83 | print("Using the latest PyPozyx version {}\n".format(latest_version)) 84 | else: 85 | warnings.warn("New PyPozyx version available, please upgrade to {}\n".format(latest_version), stacklevel=2) 86 | except Exception as e: 87 | warnings.warn("Could not get latest release version from GitHub, {}".format(e), stacklevel=2) 88 | 89 | 90 | if __name__ == '__main__': 91 | print(is_on_latest_version()) 92 | -------------------------------------------------------------------------------- /docs/overview/index.rst: -------------------------------------------------------------------------------- 1 | pypozyx 2 | ======== 3 | 4 | About 5 | ----- 6 | 7 | pypozyx is a Python library made for providing an interface with a Pozyx device over a serial connection. This serial interface then also allows communication with remote Pozyx devices through the connected device. 8 | 9 | The library was largely inspired by the already existing Arduino library, yielding a lot of advantages that carried over from the Arduino library. However, as a result pypozyx is not that Pythonic. It is our intention to improve this with our next big release. 10 | 11 | Features 12 | -------- 13 | 14 | * Easy to use, allowing both high-level and low-level interfacing with the Pozyx device. 15 | * Uses the excellent pySerial library for cross-platform functionality. 16 | * Works with Python 2 and Python 3 17 | * Pozyx device serial port detection. 18 | * Specialized data structures for all important Pozyx data. 19 | 20 | Requirements 21 | ------------ 22 | 23 | * Python 2.7 or newer, or Python 3.4 and newer 24 | * pySerial > 3.0 25 | 26 | Pozyx serial connection requirements 27 | ------------------------------------ 28 | 29 | Linux 30 | ~~~~~ 31 | 32 | * Make sure you have permissions set to use the serial device. 33 | * To get permission once (given your device is on port ACM0) you can run ``sudo chmod 666 /dev/ttyACM0`` 34 | * To get permission forever you can run ``sudo adduser $USER dialout`` or the relevant group for your distro. 35 | 36 | MacOS 37 | ~~~~~ 38 | 39 | * Normally everything should work out of the box. 40 | 41 | Windows 42 | ~~~~~~~ 43 | 44 | * Please use Windows 7 or newer. 45 | * Install `STM32 Virtual COM Port Driver `_. 46 | 47 | .. note:: 48 | 49 | After running this installer, you have to install the correct driver package for your system. 50 | 51 | The driver installers are located in *C:\\Program Files (x86)\\STMicroelectronics\\Software\\Virtual comport driver*. 52 | 53 | Choose Win7 if you run Windows 7 or older. Choose Win8 for Windows 8 or newer. Run "dpinst_amd64.exe" on a 64-bit system, "dpinst_x86.exe"on a 32-bit system. 54 | 55 | Installation 56 | ------------ 57 | 58 | Currently, pypozyx is easily installable from `Python Package index (PyPi) `_, but can also be installed from source. 59 | 60 | From PyPi 61 | ~~~~~~~~~ 62 | 63 | To install the library from `PyPi `_, you can run either of the following: 64 | 65 | * ``pip install pypozyx`` or ``python -m pip install pypozyx``. 66 | * ``pip3 install pypozyx`` or ``python3 -m pip install pypozyx``. 67 | 68 | .. note:: 69 | 70 | If installation fails due to permission issues, or the package doesn't seem to install, please try to add --user as a flag to pip, ``pip install --user pypozyx``, or use sudo ``sudo pip install pypozyx``. 71 | 72 | From source 73 | ~~~~~~~~~~~ 74 | 75 | To install from source, you'll have to download the source files first, of course. You can do this either by: 76 | 77 | * ``git clone https://github.com/pozyxLabs/Pozyx-Python-library`` 78 | * Download it from `the repository `_ and extracting it. 79 | * Downloading the `zip file `_ directly, and extracting it. 80 | 81 | Then, in your extracted/downloaded folder, run ``python setup.py install`` or ``python3 setup.py install``. 82 | -------------------------------------------------------------------------------- /pypozyx/__init__.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | """Provides: 3 | 4 | - A way to interact with Pozyx over USB through PozyxSerial 5 | - A solid set of easy-to-use and easy-to-expand functions for 6 | working with Pozyx. 7 | 8 | NB: At this point, documentation is planned for functions and the structures 9 | relevant for users. This constitutes the lib, structures.device, and 10 | structures.sensor_data modules. 11 | 12 | How to use the documentation 13 | ---------------------------- 14 | Documentation is available in two forms: docstrings provided with the code, 15 | and the documentation available on `the Pozyx documentation `_. 16 | For now, this links to the Arduino library functions, as there is a lot of 17 | similarity and evidently both libraries influence each other. If you're not 18 | working low-level, you shouldn't notice much difference between the two. 19 | When the Python webpage comes only, we'll clearly distinguish between 20 | functions not available in the Arduino lib. 21 | 22 | The docstring examples assume that, for ease of use and typing, everything 23 | from the pypozyx package has been imported using:: 24 | 25 | >>> from pypozyx import * 26 | 27 | While not considered good practice, it makes reading and writing scripts 28 | much easier when starting out. 29 | 30 | This imports PozyxSerial, all constants definitions, and the device, generic, 31 | and sensordata structures. This does not import the registers definitions, 32 | or the bitmasks, as these are considered advanced features and assume the 33 | user knows what he's doing. To access these, import the modules 34 | pypozyx.definitions.bitmasks and pypozyx.definitions.registers. 35 | 36 | Whenever you see the following code in the docstrings:: 37 | 38 | >>> pozyx.anyFunction() 39 | >>> # or 40 | >>> p.anyFunction() 41 | 42 | 'pozyx' and 'p' are the actual Pozyx device's interface instance, not a module. 43 | This means that the Pozyx interface instance performs anyFunction. 44 | 45 | Available subpackages 46 | --------------------- 47 | structures 48 | Basic and specialised data structures tailored to using Pozyx 49 | definitions 50 | Pozyx constant definitions, such as general constants, register indexes, 51 | and bit masks for certain functionality. 52 | 53 | 54 | Serial port warnings 55 | -------------------- 56 | Make sure that the serial connection is closed (i.e. program terminated) 57 | before disconnecting Pozyx. Terminating a running program can be done using 58 | Ctrl+Z or Ctrl+C. If something goes wrong, chances are high you will have to 59 | edit which serial port is used by Pozyx, as the previous one will be 60 | considered unavailable by your OS. 61 | 62 | Automatically selecting serial port 63 | ----------------------------------- 64 | A trick to remedy this is by using the following code:: 65 | 66 | >>> import serial.tools.list_ports 67 | >>> port = list(serial.tools.list_ports.comports())[0] 68 | 69 | This only works consistently when Pozyx is the only serial device you're 70 | working with, when you use more devices, their order in the list may 71 | switch at some point. 72 | 73 | """ 74 | 75 | __version__ = '1.3.0' 76 | 77 | VERSION = __version__ 78 | version = __version__ 79 | 80 | 81 | from pypozyx.definitions import * 82 | from pypozyx.pozyx_serial import * 83 | from pypozyx.structures.device import * 84 | from pypozyx.structures.generic import * 85 | from pypozyx.structures.sensor_data import * 86 | -------------------------------------------------------------------------------- /useful/change_uwb_settings.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | """change_uwb_settings.py - Changes the UWB settings of all devices listed. 3 | 4 | This assumes all listed devices are on the same UWB settings already, 5 | otherwise you should run the set_same_settings.py script, as that one 6 | finds all devices on all settings. 7 | """ 8 | 9 | from pypozyx import * 10 | from pypozyx.definitions.registers import POZYX_UWB_CHANNEL, POZYX_UWB_RATES, POZYX_UWB_PLEN 11 | 12 | 13 | class ChangeUWBSettings: 14 | 15 | def __init__(self, pozyx, uwb_settings, devices=[], set_local=True, save_to_flash=True): 16 | self.pozyx = pozyx 17 | self.uwb_settings = uwb_settings 18 | self.devices = devices 19 | self.set_local = set_local 20 | self.save_to_flash = save_to_flash 21 | self.get_start_settings() 22 | 23 | def get_start_settings(self): 24 | self.start_settings = UWBSettings() 25 | status = self.pozyx.getUWBSettings(self.start_settings) 26 | if status == POZYX_SUCCESS: 27 | print("current UWB settings: %s" % self.start_settings) 28 | else: 29 | print("current UWB settings could not be retrieved, terminating") 30 | raise Exception 31 | return status 32 | 33 | def run(self): 34 | for tag in self.devices: 35 | self.set_to_settings(tag) 36 | if not self.set_local: 37 | self.pozyx.setUWBSettings(self.start_settings) 38 | else: 39 | if save_to_flash: 40 | self.pozyx.saveUWBSettings() 41 | 42 | def set_to_settings(self, remote_id): 43 | self.pozyx.setUWBSettings(self.start_settings) 44 | self.pozyx.setUWBSettings(self.uwb_settings, remote_id) 45 | self.pozyx.setUWBSettings(self.uwb_settings) 46 | whoami = SingleRegister() 47 | status = self.pozyx.getWhoAmI(whoami, remote_id) 48 | if whoami[0] != 0x43 or status != POZYX_SUCCESS: 49 | print("Changing UWB settings on device 0x%0.4x failed" % remote_id) 50 | return 51 | else: 52 | print("Settings successfully changed on device 0x%0.4x" % remote_id) 53 | if self.save_to_flash: 54 | status = self.pozyx.saveUWBSettings(remote_id) 55 | if status != POZYX_SUCCESS: 56 | print("\tAnd saving settings failed.") 57 | else: 58 | print("\tAnd saving settings succeeded") 59 | 60 | 61 | if __name__ == '__main__': 62 | # default_settings = UWBSettings(channel=5, 63 | # bitrate=0, 64 | # prf=2, 65 | # plen=0x08, 66 | # gain_db=11.5) 67 | 68 | # new uwb_settings 69 | uwb_settings = UWBSettings(channel=5, 70 | bitrate=0, 71 | prf=2, 72 | plen=0x08, 73 | gain_db=11.5) 74 | 75 | # set to True if local tag needs to change settings as well. 76 | set_local = True 77 | 78 | # set to True if needed to save to flash 79 | save_to_flash = True 80 | 81 | # list of IDs to set UWB settings for. example devices = [0x6001, 0x6002, 82 | # 0x6799] 83 | devices = [] 84 | 85 | # serial port 86 | serial_port = get_first_pozyx_serial_port() 87 | if serial_port is None: 88 | print("No Pozyx connected. Check your USB cable or your driver!") 89 | quit() 90 | 91 | # pozyx 92 | pozyx = PozyxSerial(serial_port) 93 | 94 | # initialize the class 95 | c = ChangeUWBSettings(pozyx, uwb_settings, devices, 96 | set_local, save_to_flash) 97 | 98 | # run the functionality 99 | c.run() 100 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Pozyx-Python-library 2 | A Python library to work with the [pozyx indoor positioning system](https://pozyx.io) over USB. 3 | This works with the [Pozyx Creator kit](https://www.pozyx.io/creator) 4 | 5 | This library works with both Python 2 and 3. 6 | 7 | ## Prerequisites 8 | * Download and install Python. On Windows, make your life easier and make sure Python is in your PATH. A recommended install is therefore the [Anaconda Suite](https://www.anaconda.com/download/) by Continuum. If you're going to follow the tutorials, you'll need to install Python 3 for the python-osc support. 9 | * Install the PySerial package. If you have pip installed, you can do this by writing `pip install pyserial` in your command line interface (cmd on Windows). 10 | * **Windows only** install [ST's virtual COM driver](http://www.st.com/content/st_com/en/products/development-tools/software-development-tools/stm32-software-development-tools/stm32-utilities/stsw-stm32102.html). After running this installer, please run the correct driver package for your system, located in `C:\Program Files (x86)\STMicroelectronics\Software\Virtual comport driver`. Choose Win7 if you run Windows 7 or older. Choose Win8 for Windows 8 or newer. Run `dpinst_amd64.exe` on a 64-bit system, `dpinst_x86.exe` on a 32-bit system. 11 | 12 | ## Installing this package 13 | Just run `pip install pypozyx` 14 | 15 | PyPozyx is now installed. To check whether it is: if you followed all the steps correctly, and know which port your Pozyx is on, the following code should work: 16 | 17 | ```python 18 | from pypozyx import PozyxSerial 19 | port = 'COMX' # on UNIX systems this will be '/dev/ttyACMX' 20 | p = PozyxSerial(port) 21 | ``` 22 | 23 | If your port is correct and the serial connection to the Pozyx isn't used by other software, this will run without any errors. 24 | 25 | ### But! How do I know what port my Pozyx is on? 26 | * You can see the COM ports on your system easily using Python with: `python -c "from pypozyx import *;list_serial_ports()"` 27 | 28 | * **NEW** You can quickly find whether there's a recognized Pozyx device using: `python -c "from pypozyx import *;print(get_first_pozyx_serial_port())"` 29 | 30 | ## Documentation and examples 31 | You can find the Python tutorials on our site: https://docs.pozyx.io/creator/python. 32 | 33 | Documentation for the Python library can be found here: https://pypozyx.readthedocs.io. 34 | 35 | * This was originally a port of the Pozyx's Arduino library, so most of the [Arduino Library Documentation](https://ardupozyx.readthedocs.io) is transformable to this. The major difference is that you don't ever again need to pass along the length of the data you're reading/writing. This is taken care of by the library through the Data and SingleRegister classes like so: 36 | 37 | ```python 38 | whoami = SingleRegister() 39 | pozyx.regRead(POZYX_WHO_AM_I, whoami) # which is pozyx.getWhoAmI(whoami) 40 | ``` 41 | * `SingleRegister(value=0, size=1, signed=1)` is basically an instance `Data([0], 'B')`, which functions as a single uint8_t. If you want to make your custom data, for a single register you can adapt the size and signed parameters, and for larger data structures you can use your own data formats. `Data([0]*3, 'BHI')`, for example, creates a structure of 1 uint8_t, uint16_t and uint32_t. Writing and reading data using this example as a parameter will automatically read/write 7 bytes worth of data. To specify your own data formats, check the [struct package documentation for Python 3](https://docs.python.org/3.5/library/struct.html#format-characters) or [Python 2](https://docs.python.org/2/library/struct.html). 42 | 43 | * A more pythonic library would be nice, but isn't in the works. 44 | 45 | 46 | More usage examples can be found in the [useful](https://github.com/pozyxLabs/Pozyx-Python-library/tree/master/useful) and [tutorials](https://github.com/pozyxLabs/Pozyx-Python-library/tree/master/tutorials) folders of the repository. 47 | -------------------------------------------------------------------------------- /pypozyx/structures/byte_structure.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | """ pypozyx.structures.byte_structure - contains the ByteStructure class, thank you struct.""" 3 | 4 | import struct 5 | 6 | 7 | class ByteStructure(object): 8 | """ 9 | The ByteStructure class is the base class that all custom structs inherit 10 | their basic functionality from. It implements the low-level functionality 11 | that makes it easy to make use arbitrary struct formats in the interface 12 | with Pozyx. 13 | """ 14 | byte_size = 4 15 | data_format = 'BBBB' 16 | 17 | def __init__(self): 18 | """Initializes the structures byte data and data arrays""" 19 | self.byte_data = '00' * self.byte_size 20 | self.bytes_to_data() 21 | 22 | def load_bytes(self, byte_data): 23 | """Loads a hex byte array in the structure's data""" 24 | self.byte_data = byte_data 25 | self.bytes_to_data() 26 | 27 | def bytes_to_data(self): 28 | """Transforms hex data into packed UINT8 byte values""" 29 | s = ''.encode() 30 | for i in range(int(len(self.byte_data) / 2)): 31 | index = 2 * i 32 | s += struct.pack('B', 33 | int(self.byte_data[index:index + 2], 16)) 34 | self.load_packed(s) 35 | 36 | def load_packed(self, packed): 37 | """Unpacks the packed UINT8 bytes in their right format as given in data_format""" 38 | index = 0 39 | self.data = [0] * len(self.data_format) 40 | for i in range(len(self.data_format)): 41 | data_len = struct.calcsize(self.data_format[i]) 42 | self.data[i] = struct.unpack(self.data_format[i], packed[ 43 | index:index + data_len])[0] 44 | index += data_len 45 | self.load(self.data) 46 | 47 | def load_hex_string(self): 48 | """Loads the data's hex string for sending""" 49 | byte_data = self.transform_to_bytes() 50 | s = '' 51 | for i in range(len(byte_data)): 52 | s += '%0.2x' % byte_data[i] 53 | self.byte_data = s 54 | 55 | def transform_to_bytes(self): 56 | """Transforms the data to a UINT8 bytestring in hex""" 57 | new_format = '' 58 | for i in range(len(self.data)): 59 | new_format += 'B' * struct.calcsize(self.data_format[i]) 60 | return self.transform_data(new_format) 61 | 62 | def set_packed_size(self): 63 | """Sets the size (bytesize) to the structures data format, but packed""" 64 | new_format = '' 65 | for i in range(len(self.data)): 66 | new_format += 'B' * struct.calcsize(self.data_format[i]) 67 | self.byte_size = 1 * len(new_format) 68 | 69 | def set_unpacked_size(self): 70 | """Sets the size (bytesize) to the structures data format, unpacked""" 71 | self.byte_size = struct.calcsize(self.data_format) 72 | 73 | def transform_data(self, new_format): 74 | """Transforms the data to a new format, handy for decoding to bytes""" 75 | s = ''.encode() 76 | for i in range(len(self.data)): 77 | s += struct.pack(self.data_format[i], self.data[i]) 78 | return list(struct.unpack(new_format, s)) 79 | 80 | def load(self, data, convert=True): 81 | """Loads data in its relevant class components.""" 82 | raise NotImplementedError('load(data) should be customised for every derived structure') 83 | 84 | def __getitem__(self, key): 85 | return self.data[key] 86 | 87 | def __setitem__(self, key, value): 88 | self.data[key] = value 89 | 90 | def __len__(self): 91 | """Returns the size of the structure's data""" 92 | return len(self.data) 93 | 94 | def __eq__(self, other): 95 | """Returns whether the structures contain the same data""" 96 | if other is None: 97 | return False 98 | return self.data == other.data and self.data_format == other.data_format 99 | 100 | def __str__(self): 101 | """Returns a string that should be tailored to each ByteStructure-derived class.""" 102 | s = '' 103 | for i in range(len(self.data)): 104 | if i > 0: 105 | s += ', ' 106 | s += str(self.data[i]) 107 | return s 108 | -------------------------------------------------------------------------------- /tutorials/ready_to_range_no_leds.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | """ 3 | The Pozyx ready to range tutorial (c) Pozyx Labs 4 | Please read the tutorial that accompanies this sketch: https://www.pozyx.io/Documentation/Tutorials/ready_to_range/Python 5 | 6 | This demo requires two Pozyx devices. It demonstrates the ranging capabilities and the functionality to 7 | to remotely control a Pozyx device. Move around with the other Pozyx device. 8 | 9 | This demo measures the range between the two devices. The closer the devices are to each other, the more LEDs will 10 | light up on both devices. 11 | """ 12 | from pypozyx import (PozyxSerial, PozyxConstants, version, 13 | SingleRegister, DeviceRange, POZYX_SUCCESS, get_first_pozyx_serial_port) 14 | 15 | from pypozyx.tools.version_check import perform_latest_version_check 16 | 17 | 18 | class ReadyToRange(object): 19 | """Continuously performs ranging between the Pozyx and a destination and sets their LEDs""" 20 | 21 | def __init__(self, pozyx, destination_id, range_step_mm=1000, protocol=PozyxConstants.RANGE_PROTOCOL_PRECISION, 22 | remote_id=None): 23 | self.pozyx = pozyx 24 | self.destination_id = destination_id 25 | self.range_step_mm = range_step_mm 26 | self.remote_id = remote_id 27 | self.protocol = protocol 28 | 29 | def setup(self): 30 | """Sets up both the ranging and destination Pozyx's LED configuration""" 31 | print("------------POZYX RANGING V{} -------------".format(version)) 32 | print("NOTES: ") 33 | print(" - Change the parameters: ") 34 | print("\tdestination_id(target device)") 35 | print("\trange_step(mm)") 36 | print("") 37 | print("- Approach target device to see range and") 38 | print("led control") 39 | print("") 40 | if self.remote_id is None: 41 | for device in [self.remote_id, self.destination_id]: 42 | self.pozyx.printDeviceInfo(device) 43 | else: 44 | for device in [None, self.remote_id, self.destination_id]: 45 | self.pozyx.printDeviceInfo(device) 46 | print("") 47 | print("- -----------POZYX RANGING V{} ------------".format(version)) 48 | print("") 49 | print("START Ranging: ") 50 | 51 | # set the ranging protocol 52 | self.pozyx.setRangingProtocol(self.protocol, self.remote_id) 53 | 54 | def loop(self): 55 | """Performs ranging and sets the LEDs accordingly""" 56 | device_range = DeviceRange() 57 | status = self.pozyx.doRanging( 58 | self.destination_id, device_range, self.remote_id) 59 | if status == POZYX_SUCCESS: 60 | print(device_range) 61 | else: 62 | error_code = SingleRegister() 63 | status = self.pozyx.getErrorCode(error_code) 64 | if status == POZYX_SUCCESS: 65 | print("ERROR Ranging, local %s" % 66 | self.pozyx.getErrorMessage(error_code)) 67 | else: 68 | print("ERROR Ranging, couldn't retrieve local error") 69 | 70 | 71 | if __name__ == "__main__": 72 | # Check for the latest PyPozyx version. Skip if this takes too long or is not needed by setting to False. 73 | check_pypozyx_version = True 74 | if check_pypozyx_version: 75 | perform_latest_version_check() 76 | 77 | # hardcoded way to assign a serial port of the Pozyx 78 | serial_port = "COM12" # or serial_port = "/dev/ttyACM0" 79 | 80 | # the easier way 81 | serial_port = get_first_pozyx_serial_port() 82 | if serial_port is None: 83 | print("No Pozyx connected. Check your USB cable or your driver!") 84 | quit() 85 | 86 | remote_id = 0x6856 # the network ID of the remote device 87 | remote = False # whether to use the given remote device for ranging 88 | if not remote: 89 | remote_id = None 90 | 91 | destination_id = 0x6e66 # network ID of the ranging destination 92 | # distance that separates the amount of LEDs lighting up. 93 | range_step_mm = 1000 94 | 95 | # the ranging protocol, other one is PozyxConstants.RANGE_PROTOCOL_PRECISION 96 | ranging_protocol = PozyxConstants.RANGE_PROTOCOL_PRECISION 97 | 98 | pozyx = PozyxSerial(serial_port) 99 | r = ReadyToRange(pozyx, destination_id, range_step_mm, 100 | ranging_protocol, remote_id) 101 | r.setup() 102 | while True: 103 | r.loop() 104 | -------------------------------------------------------------------------------- /tutorials/orientation_3D.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | """ 3 | The pozyx ranging demo (c) Pozyx Labs 4 | please check out https://www.pozyx.io/Documentation/Tutorials/getting_started/Python 5 | 6 | This demo requires one (or two) pozyx shields. It demonstrates the 3D orientation and the functionality 7 | to remotely read register data from a pozyx device. Connect one of the Pozyx devices with USB and run this script. 8 | 9 | This demo reads the following sensor data: 10 | - pressure 11 | - acceleration 12 | - magnetic field strength 13 | - angular velocity 14 | - the heading, roll and pitch 15 | - the quaternion rotation describing the 3D orientation of the device. This can be used to transform from the body coordinate system to the world coordinate system. 16 | - the linear acceleration (the acceleration excluding gravity) 17 | - the gravitational vector 18 | 19 | The data can be viewed in the Processing sketch orientation_3D.pde 20 | """ 21 | from time import time 22 | 23 | from pypozyx import SensorData, SingleRegister, POZYX_SUCCESS, get_first_pozyx_serial_port, PozyxSerial, get_serial_ports 24 | from pypozyx.definitions.bitmasks import POZYX_INT_MASK_IMU 25 | from pythonosc.osc_message_builder import OscMessageBuilder 26 | from pythonosc.udp_client import SimpleUDPClient 27 | 28 | from pypozyx.tools.version_check import perform_latest_version_check 29 | 30 | 31 | class Orientation3D(object): 32 | """Reads out all sensor data from either a local or remote Pozyx""" 33 | 34 | def __init__(self, pozyx, osc_udp_client, remote_id=None): 35 | self.pozyx = pozyx 36 | self.remote_id = remote_id 37 | 38 | self.osc_udp_client = osc_udp_client 39 | 40 | def setup(self): 41 | """There is no specific setup functionality""" 42 | self.pozyx.printDeviceInfo(self.remote_id) 43 | 44 | self.current_time = time() 45 | 46 | def loop(self): 47 | """Gets new IMU sensor data""" 48 | sensor_data = SensorData() 49 | calibration_status = SingleRegister() 50 | if self.remote_id is not None or self.pozyx.checkForFlag(POZYX_INT_MASK_IMU, 0.01) == POZYX_SUCCESS: 51 | status = self.pozyx.getAllSensorData(sensor_data, self.remote_id) 52 | status &= self.pozyx.getCalibrationStatus(calibration_status, self.remote_id) 53 | if status == POZYX_SUCCESS: 54 | self.publishSensorData(sensor_data, calibration_status) 55 | 56 | def publishSensorData(self, sensor_data, calibration_status): 57 | """Makes the OSC sensor data package and publishes it""" 58 | self.msg_builder = OscMessageBuilder("/sensordata") 59 | self.msg_builder.add_arg(int(1000 * (time() - self.current_time))) 60 | self.current_time = time() 61 | self.addSensorData(sensor_data) 62 | self.addCalibrationStatus(calibration_status) 63 | self.osc_udp_client.send(self.msg_builder.build()) 64 | 65 | def addSensorData(self, sensor_data): 66 | """Adds the sensor data to the OSC message""" 67 | self.addComponentsOSC(sensor_data.pressure) 68 | self.addComponentsOSC(sensor_data.acceleration) 69 | self.addComponentsOSC(sensor_data.magnetic) 70 | self.addComponentsOSC(sensor_data.angular_vel) 71 | self.addComponentsOSC(sensor_data.euler_angles) 72 | self.addComponentsOSC(sensor_data.quaternion) 73 | self.addComponentsOSC(sensor_data.linear_acceleration) 74 | self.addComponentsOSC(sensor_data.gravity_vector) 75 | 76 | def addComponentsOSC(self, component): 77 | """Adds a sensor data component to the OSC message""" 78 | for data in component.data: 79 | self.msg_builder.add_arg(float(data)) 80 | 81 | def addCalibrationStatus(self, calibration_status): 82 | """Adds the calibration status data to the OSC message""" 83 | self.msg_builder.add_arg(calibration_status[0] & 0x03) 84 | self.msg_builder.add_arg((calibration_status[0] & 0x0C) >> 2) 85 | self.msg_builder.add_arg((calibration_status[0] & 0x30) >> 4) 86 | self.msg_builder.add_arg((calibration_status[0] & 0xC0) >> 6) 87 | 88 | 89 | if __name__ == '__main__': 90 | # Check for the latest PyPozyx version. Skip if this takes too long or is not needed by setting to False. 91 | check_pypozyx_version = True 92 | if check_pypozyx_version: 93 | perform_latest_version_check() 94 | 95 | # shortcut to not have to find out the port yourself 96 | serial_port = get_first_pozyx_serial_port() 97 | if serial_port is None: 98 | print("No Pozyx connected. Check your USB cable or your driver!") 99 | quit() 100 | 101 | # remote device network ID 102 | remote_id = 0x6e66 103 | # whether to use a remote device. If on False, remote_id is set to None which means the local device is used 104 | remote = False 105 | if not remote: 106 | remote_id = None 107 | 108 | # configure if you want to route OSC to outside your localhost. Networking knowledge is required. 109 | ip = "127.0.0.1" 110 | network_port = 8888 111 | 112 | pozyx = PozyxSerial(serial_port) 113 | osc_udp_client = SimpleUDPClient(ip, network_port) 114 | 115 | orientation_3d = Orientation3D(pozyx, osc_udp_client, remote_id=remote_id) 116 | orientation_3d.setup() 117 | print("Set up Orientation 3D") 118 | while True: 119 | orientation_3d.loop() 120 | -------------------------------------------------------------------------------- /tutorials/ready_to_range.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | """ 3 | The Pozyx ready to range tutorial (c) Pozyx Labs 4 | Please read the tutorial that accompanies this sketch: https://www.pozyx.io/Documentation/Tutorials/ready_to_range/Python 5 | 6 | This demo requires two Pozyx devices. It demonstrates the ranging capabilities and the functionality to 7 | to remotely control a Pozyx device. Move around with the other Pozyx device. 8 | 9 | This demo measures the range between the two devices. The closer the devices are to each other, the more LEDs will 10 | light up on both devices. 11 | """ 12 | from pypozyx import (PozyxSerial, PozyxConstants, version, 13 | SingleRegister, DeviceRange, POZYX_SUCCESS, POZYX_FAILURE, get_first_pozyx_serial_port) 14 | 15 | from pypozyx.tools.version_check import perform_latest_version_check 16 | 17 | 18 | class ReadyToRange(object): 19 | """Continuously performs ranging between the Pozyx and a destination and sets their LEDs""" 20 | 21 | def __init__(self, pozyx, destination_id, range_step_mm=1000, protocol=PozyxConstants.RANGE_PROTOCOL_PRECISION, 22 | remote_id=None): 23 | self.pozyx = pozyx 24 | self.destination_id = destination_id 25 | self.range_step_mm = range_step_mm 26 | self.remote_id = remote_id 27 | self.protocol = protocol 28 | 29 | def setup(self): 30 | """Sets up both the ranging and destination Pozyx's LED configuration""" 31 | print("------------POZYX RANGING V{} -------------".format(version)) 32 | print("NOTES: ") 33 | print(" - Change the parameters: ") 34 | print("\tdestination_id(target device)") 35 | print("\trange_step(mm)") 36 | print("") 37 | print("- Approach target device to see range and") 38 | print("led control") 39 | print("") 40 | if self.remote_id is None: 41 | for device_id in [self.remote_id, self.destination_id]: 42 | self.pozyx.printDeviceInfo(device_id) 43 | else: 44 | for device_id in [None, self.remote_id, self.destination_id]: 45 | self.pozyx.printDeviceInfo(device_id) 46 | print("") 47 | print("- -----------POZYX RANGING V{} -------------".format(version)) 48 | print("") 49 | print("START Ranging: ") 50 | 51 | # make sure the local/remote pozyx system has no control over the LEDs. 52 | led_config = 0x0 53 | self.pozyx.setLedConfig(led_config, self.remote_id) 54 | # do the same for the destination. 55 | self.pozyx.setLedConfig(led_config, self.destination_id) 56 | # set the ranging protocol 57 | self.pozyx.setRangingProtocol(self.protocol, self.remote_id) 58 | 59 | def loop(self): 60 | """Performs ranging and sets the LEDs accordingly""" 61 | device_range = DeviceRange() 62 | status = self.pozyx.doRanging( 63 | self.destination_id, device_range, self.remote_id) 64 | if status == POZYX_SUCCESS: 65 | print(device_range) 66 | if self.ledControl(device_range.distance) == POZYX_FAILURE: 67 | print("ERROR: setting (remote) leds") 68 | else: 69 | error_code = SingleRegister() 70 | status = self.pozyx.getErrorCode(error_code) 71 | if status == POZYX_SUCCESS: 72 | print("ERROR Ranging, local %s" % 73 | self.pozyx.getErrorMessage(error_code)) 74 | else: 75 | print("ERROR Ranging, couldn't retrieve local error") 76 | 77 | def ledControl(self, distance): 78 | """Sets LEDs according to the distance between two devices""" 79 | status = POZYX_SUCCESS 80 | ids = [self.remote_id, self.destination_id] 81 | # set the leds of both local/remote and destination pozyx device 82 | for id in ids: 83 | status &= self.pozyx.setLed(4, (distance < range_step_mm), id) 84 | status &= self.pozyx.setLed(3, (distance < 2 * range_step_mm), id) 85 | status &= self.pozyx.setLed(2, (distance < 3 * range_step_mm), id) 86 | status &= self.pozyx.setLed(1, (distance < 4 * range_step_mm), id) 87 | return status 88 | 89 | 90 | if __name__ == "__main__": 91 | # Check for the latest PyPozyx version. Skip if this takes too long or is not needed by setting to False. 92 | check_pypozyx_version = True 93 | if check_pypozyx_version: 94 | perform_latest_version_check() 95 | 96 | # hardcoded way to assign a serial port of the Pozyx 97 | serial_port = 'COM12' 98 | 99 | # the easier way 100 | serial_port = get_first_pozyx_serial_port() 101 | if serial_port is None: 102 | print("No Pozyx connected. Check your USB cable or your driver!") 103 | quit() 104 | 105 | remote_id = 0x605D # the network ID of the remote device 106 | remote = False # whether to use the given remote device for ranging 107 | if not remote: 108 | remote_id = None 109 | 110 | destination_id = 0x6e66 # network ID of the ranging destination 111 | # distance that separates the amount of LEDs lighting up. 112 | range_step_mm = 1000 113 | 114 | # the ranging protocol, other one is PozyxConstants.RANGE_PROTOCOL_PRECISION 115 | ranging_protocol = PozyxConstants.RANGE_PROTOCOL_PRECISION 116 | 117 | pozyx = PozyxSerial(serial_port) 118 | r = ReadyToRange(pozyx, destination_id, range_step_mm, 119 | ranging_protocol, remote_id) 120 | r.setup() 121 | while True: 122 | r.loop() 123 | -------------------------------------------------------------------------------- /docs/conf.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # 3 | # Configuration file for the Sphinx documentation builder. 4 | # 5 | # This file does only contain a selection of the most common options. For a 6 | # full list see the documentation: 7 | # http://www.sphinx-doc.org/en/master/config 8 | 9 | # -- Path setup -------------------------------------------------------------- 10 | 11 | # If extensions (or modules to document with autodoc) are in another directory, 12 | # add these directories to sys.path here. If the directory is relative to the 13 | # documentation root, use os.path.abspath to make it absolute, like shown here. 14 | # 15 | # import os 16 | # import sys 17 | # sys.path.insert(0, os.path.abspath('.')) 18 | 19 | import sys 20 | import os 21 | sys.path.append(os.path.join(os.path.dirname(__name__), '..')) 22 | 23 | from pypozyx import VERSION as PYPOZYX_VERSION 24 | 25 | 26 | # -- Project information ----------------------------------------------------- 27 | 28 | project = 'pypozyx' 29 | copyright = '2018, Laurent Van Acker' 30 | author = 'Laurent Van Acker' 31 | 32 | # The short X.Y version 33 | version = '.'.join(PYPOZYX_VERSION.split('.')[:2]) 34 | # The full version, including alpha/beta/rc tags 35 | release = PYPOZYX_VERSION 36 | 37 | 38 | # -- General configuration --------------------------------------------------- 39 | 40 | # If your documentation needs a minimal Sphinx version, state it here. 41 | # 42 | # needs_sphinx = '1.0' 43 | 44 | # Add any Sphinx extension module names here, as strings. They can be 45 | # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom 46 | # ones. 47 | extensions = [ 48 | 'sphinx.ext.autodoc', 49 | 'sphinx.ext.doctest', 50 | 'sphinx.ext.intersphinx', 51 | 'sphinx.ext.todo', 52 | 'sphinx.ext.coverage', 53 | 'sphinx.ext.mathjax', 54 | 'sphinx.ext.ifconfig', 55 | 'sphinx.ext.napoleon', 56 | 'sphinx.ext.viewcode', 57 | ] 58 | 59 | # Add any paths that contain templates here, relative to this directory. 60 | templates_path = ['_templates'] 61 | 62 | # The suffix(es) of source filenames. 63 | # You can specify multiple suffix as a list of string: 64 | # 65 | # source_suffix = ['.rst', '.md'] 66 | source_suffix = '.rst' 67 | 68 | # The master toctree document. 69 | master_doc = 'index' 70 | 71 | # The language for content autogenerated by Sphinx. Refer to documentation 72 | # for a list of supported languages. 73 | # 74 | # This is also used if you do content translation via gettext catalogs. 75 | # Usually you set "language" from the command line for these cases. 76 | language = None 77 | 78 | # List of patterns, relative to source directory, that match files and 79 | # directories to ignore when looking for source files. 80 | # This pattern also affects html_static_path and html_extra_path . 81 | exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store'] 82 | 83 | # The name of the Pygments (syntax highlighting) style to use. 84 | pygments_style = 'sphinx' 85 | 86 | 87 | # -- Options for HTML output ------------------------------------------------- 88 | 89 | # The theme to use for HTML and HTML Help pages. See the documentation for 90 | # a list of builtin themes. 91 | # 92 | html_theme = 'sphinx_rtd_theme' 93 | 94 | # Theme options are theme-specific and customize the look and feel of a theme 95 | # further. For a list of options available for each theme, see the 96 | # documentation. 97 | # 98 | # html_theme_options = {} 99 | 100 | # Add any paths that contain custom static files (such as style sheets) here, 101 | # relative to this directory. They are copied after the builtin static files, 102 | # so a file named "default.css" will overwrite the builtin "default.css". 103 | html_static_path = ['_static'] 104 | 105 | # Custom sidebar templates, must be a dictionary that maps document names 106 | # to template names. 107 | # 108 | # The default sidebars (for documents that don't match any pattern) are 109 | # defined by theme itself. Builtin themes are using these templates by 110 | # default: ``['localtoc.html', 'relations.html', 'sourcelink.html', 111 | # 'searchbox.html']``. 112 | # 113 | # html_sidebars = {} 114 | 115 | 116 | # -- Options for HTMLHelp output --------------------------------------------- 117 | 118 | # Output file base name for HTML help builder. 119 | htmlhelp_basename = 'pypozyxdoc' 120 | 121 | 122 | # -- Options for LaTeX output ------------------------------------------------ 123 | 124 | latex_elements = { 125 | # The paper size ('letterpaper' or 'a4paper'). 126 | # 127 | # 'papersize': 'letterpaper', 128 | 129 | # The font size ('10pt', '11pt' or '12pt'). 130 | # 131 | # 'pointsize': '10pt', 132 | 133 | # Additional stuff for the LaTeX preamble. 134 | # 135 | # 'preamble': '', 136 | 137 | # Latex figure (float) alignment 138 | # 139 | # 'figure_align': 'htbp', 140 | } 141 | 142 | # Grouping the document tree into LaTeX files. List of tuples 143 | # (source start file, target name, title, 144 | # author, documentclass [howto, manual, or own class]). 145 | latex_documents = [ 146 | (master_doc, 'pypozyx.tex', 'pypozyx Documentation', 147 | 'Laurent Van Acker', 'manual'), 148 | ] 149 | 150 | 151 | # -- Options for manual page output ------------------------------------------ 152 | 153 | # One entry per manual page. List of tuples 154 | # (source start file, name, description, authors, manual section). 155 | man_pages = [ 156 | (master_doc, 'pypozyx', 'pypozyx Documentation', 157 | [author], 1) 158 | ] 159 | 160 | 161 | # -- Options for Texinfo output ---------------------------------------------- 162 | 163 | # Grouping the document tree into Texinfo files. List of tuples 164 | # (source start file, target name, title, author, 165 | # dir menu entry, description, category) 166 | texinfo_documents = [ 167 | (master_doc, 'pypozyx', 'pypozyx Documentation', 168 | author, 'pypozyx', 'One line description of project.', 169 | 'Miscellaneous'), 170 | ] 171 | 172 | 173 | # -- Extension configuration ------------------------------------------------- 174 | 175 | # -- Options for intersphinx extension --------------------------------------- 176 | 177 | # Example configuration for intersphinx: refer to the Python standard library. 178 | intersphinx_mapping = {'https://docs.python.org/': None} 179 | 180 | # -- Options for todo extension ---------------------------------------------- 181 | 182 | # If true, `todo` and `todoList` produce output, else they produce nothing. 183 | todo_include_todos = True -------------------------------------------------------------------------------- /useful/system_analysis.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | """ 3 | Pozyx basic troubleshooting (c) Pozyx Labs 2017 4 | 5 | If you're experiencing trouble with Pozyx, this should be your first step to check for problems. 6 | Please read the article on https://www.pozyx.io/Documentation/Tutorials/troubleshoot_basics/Python 7 | """ 8 | 9 | import sys 10 | 11 | from pypozyx import PozyxSerial, get_first_pozyx_serial_port, PozyxConstants, POZYX_SUCCESS, UWBSettings, PozyxRegisters 12 | from pypozyx.structures.device_information import DeviceDetails 13 | from pypozyx.tools.discovery import all_discovery_uwb_settings 14 | from pypozyx.tools.device_list import all_device_coordinates_in_device_list 15 | 16 | 17 | class Device(object): 18 | def __init__(self, id_, uwb_settings): 19 | self._id = id_ 20 | self.uwb_settings = uwb_settings 21 | 22 | def __hash__(self): 23 | s = "{}{}{}{}{}".format(self._id, self.uwb_settings.channel, self.uwb_settings.bitrate, 24 | self.uwb_settings.prf, self.uwb_settings.plen) 25 | # print(s, hash(s)) 26 | return hash(s) 27 | 28 | def __eq__(self, other): 29 | return self._id == other._id and self.uwb_settings == other.uwb_settings 30 | 31 | def __str__(self): 32 | return "ID {}, UWB {}".format("0x%0.4x" % self._id, self.uwb_settings) 33 | 34 | def __repr__(self): 35 | return str(self) 36 | 37 | 38 | def device_check(pozyx, remote_id=None): 39 | # pozyx.resetSystem() 40 | 41 | system_details = DeviceDetails() 42 | status = pozyx.getDeviceDetails(system_details, remote_id=remote_id) 43 | if status != POZYX_SUCCESS: 44 | print("Unable to get device details for device with id 0x%0.4x" % remote_id) 45 | return 46 | 47 | if remote_id is None: 48 | print("Local %s with id 0x%0.4x" % (system_details.device_name, system_details.id)) 49 | else: 50 | print("%s with id 0x%0.4x" % (system_details.device_name.capitalize(), system_details.id)) 51 | 52 | print("\tWho am i: 0x%0.2x" % system_details.who_am_i) 53 | print("\tFirmware version: v%s" % system_details.firmware_version_string) 54 | print("\tHardware version: v%s" % system_details.hardware_version_string) 55 | print("\tSelftest result: %s" % system_details.selftest_string) 56 | print("\tError: 0x%0.2x" % system_details.error_code) 57 | print("\tError message: %s" % system_details.error_message) 58 | 59 | uwb_settings = UWBSettings() 60 | 61 | pozyx.getUWBSettings(uwb_settings, remote_id=remote_id) 62 | 63 | print("\tUWB settings: %s" % uwb_settings) 64 | 65 | print("\tTotal saved registers: %s" % pozyx.getNumRegistersSaved(remote_id=remote_id)) 66 | saved_registers_to_check = PozyxRegisters.ALL_POSITIONING_REGISTERS + PozyxRegisters.ALL_UWB_REGISTERS 67 | 68 | saved_registers = pozyx.getSavedRegisters(remote_id=remote_id) 69 | if saved_registers: 70 | # if system_details.device_name == "tag": 71 | print("\t\t- Positioning registers: ") 72 | for register, register_name in zip(PozyxRegisters.ALL_POSITIONING_REGISTERS, ["Filter", "Algorithm & Dimension", "Ranging protocol", "Height"]): 73 | byte_num = int(register / 8) 74 | bit_num = register % 8 75 | register_saved = bool(saved_registers[byte_num] >> bit_num & 0x1) 76 | print("\t\t\t- {}: {}".format(register_name, register_saved)) 77 | print("\t\t- UWB registers: ") 78 | for register, register_name in zip(PozyxRegisters.ALL_UWB_REGISTERS, ["Channel", "Bitrate & PRF", "Preamble length", "Gain"]): 79 | byte_num = int(register / 8) 80 | bit_num = register % 8 81 | register_saved = bool(saved_registers[byte_num] >> bit_num & 0x1) 82 | print("\t\t\t- {}: {}".format(register_name, register_saved)) 83 | else: 84 | print("\tFailed to retrieve saved registers.") 85 | 86 | 87 | if system_details.device_name == "tag" and remote_id is not None: 88 | print("\tDevices for positioning:") 89 | pozyx.printDeviceList(remote_id=remote_id, include_coordinates=True, prefix="\t\t-") 90 | 91 | 92 | def network_check_discovery(pozyx, remote_id=None): 93 | pozyx.clearDevices(remote_id) 94 | if pozyx.doDiscovery(discovery_type=PozyxConstants.DISCOVERY_ALL_DEVICES, remote_id=remote_id) == POZYX_SUCCESS: 95 | print("Found devices:") 96 | pozyx.printDeviceList(remote_id, include_coordinates=False) 97 | 98 | 99 | def discover_all_devices(pozyx): 100 | amount_of_settings = len([uwb for uwb in all_discovery_uwb_settings()]) 101 | original_uwb_settings = UWBSettings() 102 | pozyx.getUWBSettings(original_uwb_settings) 103 | devices = set() 104 | 105 | print('\nStarting discovery') 106 | print('------------------') 107 | sys.stdout.write("Discovery progress [%s]" % (" " * amount_of_settings)) 108 | sys.stdout.flush() 109 | sys.stdout.write("\b" * (amount_of_settings + 1)) # return to start of line, after '[' 110 | 111 | counter = 0 112 | s = "[" 113 | for uwb_settings in all_discovery_uwb_settings(): 114 | pozyx.setUWBSettings(uwb_settings) 115 | for i in range(2): 116 | pozyx.clearDevices() 117 | pozyx.doDiscovery(PozyxConstants.DISCOVERY_ALL_DEVICES) 118 | 119 | for device_coordinates in all_device_coordinates_in_device_list(pozyx): 120 | uwb_device = UWBSettings() 121 | pozyx.getUWBSettings(uwb_device, remote_id=device_coordinates.network_id) 122 | device = Device(device_coordinates.network_id, uwb_device) 123 | # print(device, hash(device)) 124 | # print(device in devices) 125 | devices.add(device) 126 | 127 | sys.stdout.write("=") 128 | sys.stdout.flush() 129 | 130 | sys.stdout.write("]\n\nDiscovery finished.\n") 131 | for device in devices: 132 | pozyx.setUWBSettings(device.uwb_settings) 133 | device_check(pozyx, device._id) 134 | print() 135 | 136 | pozyx.setUWBSettings(original_uwb_settings) 137 | 138 | # print(devices) 139 | 140 | 141 | if __name__ == '__main__': 142 | serial_port = get_first_pozyx_serial_port() 143 | if serial_port is None: 144 | print("No Pozyx connected. Check your USB cable or your driver!") 145 | quit() 146 | 147 | pozyx = PozyxSerial(serial_port) 148 | 149 | # change to remote ID for troubleshooting that device 150 | remote_id = None 151 | 152 | device_check(pozyx, remote_id) 153 | # network_check_discovery(pozyx, remote_id) 154 | discover_all_devices(pozyx) 155 | -------------------------------------------------------------------------------- /unit_tests/test_sensor.py: -------------------------------------------------------------------------------- 1 | from pypozyx import * 2 | from pypozyx.definitions.registers import POZYX_TEMPERATURE, POZYX_PRESSURE, POZYX_MAX_LIN_ACC 3 | 4 | from time import sleep 5 | 6 | 7 | # TODO: other structures than the new ones 8 | 9 | # CONVERSIONS 10 | 11 | 12 | def test_conversions(pozyx, remote): 13 | assert POZYX_ACCEL_DIV_MG == 16.0, "POZYX_ACCEL_DIV_MG wrong, should be 16.0" 14 | assert POZYX_MAX_LIN_ACCEL_DIV_MG == 1.0, "POZYX_MAX_LIN_ACCEL_DIV_MG wrong, should be 1.0" 15 | assert POZYX_GYRO_DIV_DPS == 16.0, "POZYX_GYRO_DIV_DPS wrong, should be 16.0" 16 | assert POZYX_PRESS_DIV_PA == 1000.0, "POZYX_PRESS_DIV_PA wrong, should be 1000.0" 17 | assert POZYX_TEMP_DIV_CELSIUS == 1.0, "POZYX_TEMP_DIV_CELSIUS wrong, should be 1.0" 18 | # TODO: more 19 | 20 | # TEMPERATURE 21 | 22 | 23 | def test_temperature_class(pozyx, remote): 24 | temperature = Temperature() 25 | other_temperature = Temperature() 26 | assert temperature == other_temperature, "default temperature objects aren't equal" 27 | raw_temperature = Data([0], 'b') 28 | assert raw_temperature == temperature, "temperature not right format of Data([0], 'b')" 29 | 30 | 31 | def test_raw_temperature_read(pozyx, remote): 32 | raw_temperature = Data([0], 'b') 33 | status = pozyx.getRead(POZYX_TEMPERATURE, raw_temperature) 34 | assert status == POZYX_SUCCESS, "raw read of temperature unsuccessful" 35 | assert type(raw_temperature[0]) == type( 36 | 1), "temperature value is not an int" 37 | 38 | 39 | def test_temperature_read(pozyx, remote): 40 | temperature = Temperature() 41 | status = pozyx.getTemperature_c(temperature, remote) 42 | assert status == POZYX_SUCCESS, "getTemperature_c unsuccessful" 43 | assert type(temperature.value) == type( 44 | 1.0), "temperature value is not a float" 45 | assert temperature.value > 0, "temperature is freezing" 46 | assert temperature.value < 150, "temperature is way too hot" 47 | float_remainder = temperature.value - int(temperature.value) 48 | assert float_remainder < 0.01, "temperature accuracy not 1 °C" 49 | 50 | 51 | def test_temperature_conversion(pozyx, remote): 52 | temperature = Temperature() 53 | status = pozyx.getTemperature_c(temperature, remote) 54 | assert status == POZYX_SUCCESS, "getTemperature_c unsuccessful" 55 | raw_temperature = Data([0], 'b') 56 | status = pozyx.getRead(POZYX_TEMPERATURE, raw_temperature) 57 | assert status == POZYX_SUCCESS, "raw read of temperature unsuccessful" 58 | assert int(temperature.value * 59 | POZYX_TEMP_DIV_CELSIUS) == raw_temperature[0], "conversion isn't right" 60 | 61 | 62 | # PRESSURE 63 | 64 | 65 | def test_pressure_class(pozyx, remote): 66 | pressure = Pressure() 67 | other_pressure = Pressure() 68 | assert pressure == other_pressure, "default pressure objects aren't equal" 69 | raw_pressure = Data([0], 'I') 70 | assert raw_pressure == pressure, "pressure not right format of Data([0], 'I')" 71 | 72 | 73 | def test_raw_pressure_read(pozyx, remote): 74 | raw_pressure = Data([0], 'I') 75 | status = pozyx.getRead(POZYX_PRESSURE, raw_pressure) 76 | assert status == POZYX_SUCCESS, "raw read of pressure unsuccessful" 77 | assert type(raw_pressure[0]) == type( 78 | 1), "pressure value is not an int" 79 | 80 | 81 | def test_pressure_read(pozyx, remote): 82 | pressure = Pressure() 83 | status = pozyx.getPressure_Pa(pressure, remote) 84 | assert status == POZYX_SUCCESS, "getPressure_c unsuccessful" 85 | assert type(pressure.value) == type( 86 | 1.0), "pressure value is not a float" 87 | 88 | 89 | def test_pressure_conversion(pozyx, remote): 90 | pressure = Pressure() 91 | status = pozyx.getPressure_Pa(pressure, remote) 92 | assert status == POZYX_SUCCESS, "getTemperature_c unsuccessful" 93 | raw_pressure = Data([0], 'I') 94 | status = pozyx.getRead(POZYX_PRESSURE, raw_pressure) 95 | assert status == POZYX_SUCCESS, "raw read of pressure unsuccessful" 96 | assert int(pressure.value * 97 | POZYX_PRESS_DIV_PA) == raw_pressure[0], "conversion isn't right" 98 | 99 | 100 | # MAX_LINEAR_ACCELERATION 101 | 102 | 103 | def test_max_linear_acceleration_class(pozyx, remote): 104 | max_linear_acceleration = MaxLinearAcceleration() 105 | other_max_linear_acceleration = MaxLinearAcceleration() 106 | assert max_linear_acceleration == other_max_linear_acceleration, "default max_linear_acceleration objects aren't equal" 107 | raw_max_linear_acceleration = Data([0], 'h') 108 | assert raw_max_linear_acceleration == max_linear_acceleration, "max_linear_acceleration not right format of Data([0], 'h')" 109 | sleep(0.01) 110 | 111 | 112 | def test_raw_max_linear_acceleration_read(pozyx, remote): 113 | raw_max_linear_acceleration = Data([0], 'h') 114 | status = pozyx.getRead(POZYX_MAX_LIN_ACC, raw_max_linear_acceleration) 115 | assert status == POZYX_SUCCESS, "raw read of max_linear_acceleration unsuccessful" 116 | assert type(raw_max_linear_acceleration[0]) == type( 117 | 1), "max_linear_acceleration value is not an int" 118 | sleep(0.01) 119 | 120 | 121 | def test_max_linear_acceleration_read(pozyx, remote): 122 | max_linear_acceleration = MaxLinearAcceleration() 123 | status = pozyx.getMaxLinearAcceleration_mg(max_linear_acceleration, remote) 124 | assert status == POZYX_SUCCESS, "getMaxLinearAcceleration_mg unsuccessful" 125 | assert type(max_linear_acceleration.value) == type( 126 | 1.0), "max_linear_acceleration value is not a float" 127 | float_remainder = max_linear_acceleration.value - \ 128 | int(max_linear_acceleration.value) 129 | assert float_remainder < 0.01, "max_linear_acceleration accuracy not 1 mg" 130 | sleep(0.01) 131 | 132 | 133 | def test_max_linear_acceleration_conversion(pozyx, remote): 134 | raw_max_linear_acceleration = Data([0], 'h') 135 | status = pozyx.getRead(POZYX_MAX_LIN_ACC, raw_max_linear_acceleration) 136 | assert status == POZYX_SUCCESS, "raw read of max_linear_acceleration unsuccessful" 137 | max_linear_acceleration = MaxLinearAcceleration( 138 | raw_max_linear_acceleration[0]) 139 | assert status == POZYX_SUCCESS, "raw read of max_linear_acceleration unsuccessful" 140 | assert int(max_linear_acceleration.value * 141 | POZYX_MAX_LIN_ACCEL_DIV_MG) == raw_max_linear_acceleration[0], "conversion isn't right" 142 | 143 | 144 | # SENSOR DATA 145 | 146 | def test_all_sensor_data(pozyx, remote): 147 | all_sensor_data = SensorData() 148 | status = pozyx.getAllSensorData(all_sensor_data) 149 | assert status == POZYX_SUCCESS, "getAllSensorData not successful" 150 | 151 | 152 | # ACCELERATION 153 | 154 | 155 | def test_acceleration(pozyx, remote): 156 | acceleration = Acceleration() 157 | status = pozyx.getAcceleration_mg(acceleration) 158 | assert status == POZYX_SUCCESS, "getAcceleration_mg not successful" 159 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | GNU LESSER GENERAL PUBLIC LICENSE 2 | Version 3, 29 June 2007 3 | 4 | Copyright (C) 2007 Free Software Foundation, Inc. 5 | Everyone is permitted to copy and distribute verbatim copies 6 | of this license document, but changing it is not allowed. 7 | 8 | 9 | This version of the GNU Lesser General Public License incorporates 10 | the terms and conditions of version 3 of the GNU General Public 11 | License, supplemented by the additional permissions listed below. 12 | 13 | 0. Additional Definitions. 14 | 15 | As used herein, "this License" refers to version 3 of the GNU Lesser 16 | General Public License, and the "GNU GPL" refers to version 3 of the GNU 17 | General Public License. 18 | 19 | "The Library" refers to a covered work governed by this License, 20 | other than an Application or a Combined Work as defined below. 21 | 22 | An "Application" is any work that makes use of an interface provided 23 | by the Library, but which is not otherwise based on the Library. 24 | Defining a subclass of a class defined by the Library is deemed a mode 25 | of using an interface provided by the Library. 26 | 27 | A "Combined Work" is a work produced by combining or linking an 28 | Application with the Library. The particular version of the Library 29 | with which the Combined Work was made is also called the "Linked 30 | Version". 31 | 32 | The "Minimal Corresponding Source" for a Combined Work means the 33 | Corresponding Source for the Combined Work, excluding any source code 34 | for portions of the Combined Work that, considered in isolation, are 35 | based on the Application, and not on the Linked Version. 36 | 37 | The "Corresponding Application Code" for a Combined Work means the 38 | object code and/or source code for the Application, including any data 39 | and utility programs needed for reproducing the Combined Work from the 40 | Application, but excluding the System Libraries of the Combined Work. 41 | 42 | 1. Exception to Section 3 of the GNU GPL. 43 | 44 | You may convey a covered work under sections 3 and 4 of this License 45 | without being bound by section 3 of the GNU GPL. 46 | 47 | 2. Conveying Modified Versions. 48 | 49 | If you modify a copy of the Library, and, in your modifications, a 50 | facility refers to a function or data to be supplied by an Application 51 | that uses the facility (other than as an argument passed when the 52 | facility is invoked), then you may convey a copy of the modified 53 | version: 54 | 55 | a) under this License, provided that you make a good faith effort to 56 | ensure that, in the event an Application does not supply the 57 | function or data, the facility still operates, and performs 58 | whatever part of its purpose remains meaningful, or 59 | 60 | b) under the GNU GPL, with none of the additional permissions of 61 | this License applicable to that copy. 62 | 63 | 3. Object Code Incorporating Material from Library Header Files. 64 | 65 | The object code form of an Application may incorporate material from 66 | a header file that is part of the Library. You may convey such object 67 | code under terms of your choice, provided that, if the incorporated 68 | material is not limited to numerical parameters, data structure 69 | layouts and accessors, or small macros, inline functions and templates 70 | (ten or fewer lines in length), you do both of the following: 71 | 72 | a) Give prominent notice with each copy of the object code that the 73 | Library is used in it and that the Library and its use are 74 | covered by this License. 75 | 76 | b) Accompany the object code with a copy of the GNU GPL and this license 77 | document. 78 | 79 | 4. Combined Works. 80 | 81 | You may convey a Combined Work under terms of your choice that, 82 | taken together, effectively do not restrict modification of the 83 | portions of the Library contained in the Combined Work and reverse 84 | engineering for debugging such modifications, if you also do each of 85 | the following: 86 | 87 | a) Give prominent notice with each copy of the Combined Work that 88 | the Library is used in it and that the Library and its use are 89 | covered by this License. 90 | 91 | b) Accompany the Combined Work with a copy of the GNU GPL and this license 92 | document. 93 | 94 | c) For a Combined Work that displays copyright notices during 95 | execution, include the copyright notice for the Library among 96 | these notices, as well as a reference directing the user to the 97 | copies of the GNU GPL and this license document. 98 | 99 | d) Do one of the following: 100 | 101 | 0) Convey the Minimal Corresponding Source under the terms of this 102 | License, and the Corresponding Application Code in a form 103 | suitable for, and under terms that permit, the user to 104 | recombine or relink the Application with a modified version of 105 | the Linked Version to produce a modified Combined Work, in the 106 | manner specified by section 6 of the GNU GPL for conveying 107 | Corresponding Source. 108 | 109 | 1) Use a suitable shared library mechanism for linking with the 110 | Library. A suitable mechanism is one that (a) uses at run time 111 | a copy of the Library already present on the user's computer 112 | system, and (b) will operate properly with a modified version 113 | of the Library that is interface-compatible with the Linked 114 | Version. 115 | 116 | e) Provide Installation Information, but only if you would otherwise 117 | be required to provide such information under section 6 of the 118 | GNU GPL, and only to the extent that such information is 119 | necessary to install and execute a modified version of the 120 | Combined Work produced by recombining or relinking the 121 | Application with a modified version of the Linked Version. (If 122 | you use option 4d0, the Installation Information must accompany 123 | the Minimal Corresponding Source and Corresponding Application 124 | Code. If you use option 4d1, you must provide the Installation 125 | Information in the manner specified by section 6 of the GNU GPL 126 | for conveying Corresponding Source.) 127 | 128 | 5. Combined Libraries. 129 | 130 | You may place library facilities that are a work based on the 131 | Library side by side in a single library together with other library 132 | facilities that are not Applications and are not covered by this 133 | License, and convey such a combined library under terms of your 134 | choice, if you do both of the following: 135 | 136 | a) Accompany the combined library with a copy of the same work based 137 | on the Library, uncombined with any other library facilities, 138 | conveyed under the terms of this License. 139 | 140 | b) Give prominent notice with the combined library that part of it 141 | is a work based on the Library, and explaining where to find the 142 | accompanying uncombined form of the same work. 143 | 144 | 6. Revised Versions of the GNU Lesser General Public License. 145 | 146 | The Free Software Foundation may publish revised and/or new versions 147 | of the GNU Lesser General Public License from time to time. Such new 148 | versions will be similar in spirit to the present version, but may 149 | differ in detail to address new problems or concerns. 150 | 151 | Each version is given a distinguishing version number. If the 152 | Library as you received it specifies that a certain numbered version 153 | of the GNU Lesser General Public License "or any later version" 154 | applies to it, you have the option of following the terms and 155 | conditions either of that published version or of any later version 156 | published by the Free Software Foundation. If the Library as you 157 | received it does not specify a version number of the GNU Lesser 158 | General Public License, you may choose any version of the GNU Lesser 159 | General Public License ever published by the Free Software Foundation. 160 | 161 | If the Library as you received it specifies that a proxy can decide 162 | whether future versions of the GNU Lesser General Public License shall 163 | apply, that proxy's public statement of acceptance of any version is 164 | permanent authorization for you to choose that version for the 165 | Library. 166 | -------------------------------------------------------------------------------- /useful/set_same_settings.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | """set_same_settings.py - sets all wanted devices on the same settings as the tag. 3 | 4 | This script discovers all the devices in the environment and sets the UWB settings 5 | of the devices whose IDs match the included ones, saving to flash if required. 6 | """ 7 | # CURRENTLY IN DEV, WON'T WORK 8 | raise NotImplementedError 9 | 10 | from pypozyx.definitions.registers import POZYX_UWB_CHANNEL, POZYX_UWB_RATES, POZYX_UWB_PLEN, POZYX_UWB_GAIN 11 | 12 | port = 'COM12' 13 | 14 | 15 | class PozyxDevice: 16 | 17 | def __init__(self, id, uwb_settings): 18 | self.id = id 19 | self.uwb_settings = uwb_settings 20 | 21 | def __eq__(self, other): 22 | if self.id == other.id: 23 | return True 24 | return False 25 | 26 | 27 | class SetSameUWBSettings: 28 | """Goes through the process of setting all IDs on same UWB settings""" 29 | max_fails = 3 30 | 31 | def __init__(self, pozyx, uwb_settings, devices, set_local=True, save_to_flash=True, slow_settings=False): 32 | """""" 33 | self.pozyx = pozyx 34 | self.uwb_settings = uwb_settings 35 | self.devices = devices 36 | self.encountered = [] 37 | 38 | self.save_to_flash = save_to_flash 39 | self.set_local = set_local 40 | self.slow_settings = slow_settings 41 | 42 | def run(self): 43 | """""" 44 | self.discover_on_all_settings() 45 | self.set_all_to_settings() 46 | 47 | def discover_on_all_settings(self): 48 | """""" 49 | channels = [1, 2, 3, 4, 5, 7] 50 | bitrates = [0, 1, 2] 51 | prfs = [1, 2] 52 | plens = [0x04, 0x14, 0x24, 0x34, 0x08, 0x18, 0x28, 0x0C] 53 | for channel in channels: 54 | for bitrate in bitrates: 55 | for prf in prfs: 56 | for plen in plens: 57 | self.pozyx.setUWBSettings(UWBSettings( 58 | channel, bitrate, prf, plen, 15.0)) 59 | self.discover_devices_on_setting() 60 | 61 | def discover_devices_on_setting(self): 62 | """""" 63 | self.pozyx.clearDevices() 64 | if self.slow_settings: 65 | self.pozyx.doDiscovery( 66 | discovery_type=POZYX_DISCOVERY_ALL_DEVICES, slots=3, slot_duration=0.15) 67 | else: 68 | self.pozyx.doDiscovery(discovery_type=POZYX_DISCOVERY_ALL_DEVICES) 69 | 70 | for device_id in self.get_device_list(): 71 | self.encounter_device(device_id) 72 | 73 | def get_device_list(self): 74 | """""" 75 | device_list_size = SingleRegister() 76 | self.pozyx.getDeviceListSize(device_list_size) 77 | device_list = DeviceList(list_size=device_list_size[0]) 78 | self.pozyx.getDeviceIds(device_list) 79 | return device_list 80 | 81 | def encounter_device(self, device_id): 82 | """""" 83 | device_uwb_settings = UWBSettings() 84 | self.pozyx.getUWBSettings(device_uwb_settings, device_id) 85 | device = PozyxDevice(device_id, device_uwb_settings) 86 | if device not in self.encountered: 87 | self.encountered.append(device) 88 | 89 | def set_all_devices(self): 90 | pass 91 | 92 | def discover_and_change(self): 93 | """Goes over every possible UWB configuration, performs discovery and then changes the UWB settings if necessary""" 94 | channels = [1, 2, 3, 4, 5, 7] 95 | bitrates = [0, 1, 2] 96 | prfs = [1, 2] 97 | plens = [0x04, 0x14, 0x24, 0x34, 0x08, 0x18, 0x28, 0x0C] 98 | for channel in channels: 99 | for bitrate in bitrates: 100 | for prf in prfs: 101 | for plen in plens: 102 | self.pozyx.setUWBSettings(UWBSettings( 103 | channel, bitrate, prf, plen, 15.0)) 104 | self.pozyx.clearDevices() 105 | fails = 0 106 | uwb_settings = UWBSettings( 107 | channel, bitrate, prf, plen, 15.0) 108 | self.pozyx.setUWBSettings(uwb_settings) 109 | # will continue when UWB settings have been correctly 110 | # set 111 | while not self.pozyx.setUWBSettings(uwb_settings): 112 | print("Not able to set UWB settings %s on local device" % str( 113 | self.uwb_settings)) 114 | fails += 1 115 | if fails == self.max_fails: 116 | print("Skipping this settings") 117 | break 118 | uwb_check = UWBSettings() 119 | self.pozyx.getUWBSettings(uwb_check) 120 | print("Set UWB settings %s on local device" % 121 | str(uwb_check)) 122 | self.change_settings(self.discover_devices()) 123 | # reset the UWB settings of the local tag to the necessary one 124 | while not self.pozyx.setUWBSettings(self.uwb_settings): 125 | print("Not able to set UWB settings %s on local device" % 126 | str(self.uwb_settings)) 127 | if save_to_flash: 128 | self.save_settings() 129 | print('DONE!') 130 | 131 | def discover_devices(self): 132 | """Discovers UWB settings """ 133 | self.pozyx.doDiscovery() 134 | list_size = SingleRegister() 135 | while not self.pozyx.getDeviceListSize(list_size): 136 | print("Couldn't get device list size") 137 | if list_size[0] == 0: 138 | return [] 139 | device_list = DeviceList(list_size=list_size[0]) 140 | if self.pozyx.getDeviceIds(device_list) == POZYX_SUCCESS: 141 | print("Discovery success! devices found: %s" % str(device_list)) 142 | return device_list.data 143 | else: 144 | print("Discovery failed, found %i devices" % list_size[0]) 145 | # tries to discover the devices again until status is success and a device list is returned 146 | # beware not to get stuck in endless loop. 147 | return self.discover_devices() 148 | 149 | def change_settings(self, device_ids): 150 | """Changes the device's UWB settings""" 151 | for device_id in device_ids: 152 | if device_id in self.devices and device_id not in self.encountered: 153 | while not self.pozyx.setUWBSettings(self.uwb_settings, device_id): 154 | print("Not able to set UWB settings on device 0x%0.4x" % 155 | device_id) 156 | self.encountered.append(device_id) 157 | print("Successfully set UWB settings on device 0x%0.4x" % 158 | device_id) 159 | 160 | def save_settings(self): 161 | """Saves the UWB settings to flash on all devices""" 162 | # set the UWB settings to the desired one 163 | self.pozyx.setUWBSettings(self.uwb_settings) 164 | registers = [POZYX_UWB_CHANNEL, POZYX_UWB_RATES, 165 | POZYX_UWB_PLEN, POZYX_UWB_GAIN] 166 | for device_id in self.devices: 167 | while not self.pozyx.saveRegisters(registers, device_id): 168 | print("Not able to save UWB settings on device 0x%0.4x" % 169 | device_id) 170 | print("Successfully saved UWB settings on device 0x%0.4x" % device_id) 171 | 172 | 173 | if __name__ == "__main__": 174 | # new uwb_settings 175 | uwb_settings = UWBSettings(channel=5, 176 | bitrate=0, 177 | prf=2, 178 | plen=0x08, 179 | gain_db=11.5) 180 | 181 | # set to True if local tag needs to change settings as well. 182 | set_local = True 183 | 184 | # set to True if needed to save to flash 185 | save_to_flash = True 186 | 187 | # list of IDs to set UWB settings for. example devices = [0x6001, 0x6002, 188 | # 0x6799] 189 | devices = [0x1, 0x2, 0x3] 190 | 191 | # serial port 192 | serial_port = get_first_pozyx_serial_port() 193 | if serial_port is None: 194 | print("No Pozyx connected. Check your USB cable or your driver!") 195 | quit() 196 | 197 | # pozyx 198 | pozyx = PozyxSerial(serial_port) 199 | 200 | s = SetSameUWBSettings(pozyx, uwb_settings, devices, save_to_flash) 201 | -------------------------------------------------------------------------------- /tutorials/multitag_positioning.py: -------------------------------------------------------------------------------- 1 | 2 | #!/usr/bin/env python 3 | """ 4 | The Pozyx ready to localize tutorial (c) Pozyx Labs 5 | Please read the tutorial that accompanies this sketch: 6 | https://www.pozyx.io/Documentation/Tutorials/ready_to_localize/Python 7 | 8 | This tutorial requires at least the contents of the Pozyx Ready to Localize kit. It demonstrates the positioning capabilities 9 | of the Pozyx device both locally and remotely. Follow the steps to correctly set up your environment in the link, change the 10 | parameters and upload this sketch. Watch the coordinates change as you move your device around! 11 | 12 | """ 13 | from time import sleep 14 | 15 | from pypozyx import (PozyxConstants, Coordinates, POZYX_SUCCESS, PozyxRegisters, version, 16 | DeviceCoordinates, PozyxSerial, get_first_pozyx_serial_port, SingleRegister) 17 | from pythonosc.udp_client import SimpleUDPClient 18 | 19 | from pypozyx.tools.version_check import perform_latest_version_check 20 | 21 | 22 | class MultitagPositioning(object): 23 | """Continuously performs multitag positioning""" 24 | 25 | def __init__(self, pozyx, osc_udp_client, tag_ids, anchors, algorithm=PozyxConstants.POSITIONING_ALGORITHM_UWB_ONLY, 26 | dimension=PozyxConstants.DIMENSION_3D, height=1000): 27 | self.pozyx = pozyx 28 | self.osc_udp_client = osc_udp_client 29 | 30 | self.tag_ids = tag_ids 31 | self.anchors = anchors 32 | self.algorithm = algorithm 33 | self.dimension = dimension 34 | self.height = height 35 | 36 | def setup(self): 37 | """Sets up the Pozyx for positioning by calibrating its anchor list.""" 38 | print("------------POZYX MULTITAG POSITIONING V{} -------------".format(version)) 39 | print("") 40 | print(" - System will manually calibrate the tags") 41 | print("") 42 | print(" - System will then auto start positioning") 43 | print("") 44 | if None in self.tag_ids: 45 | for device_id in self.tag_ids: 46 | self.pozyx.printDeviceInfo(device_id) 47 | else: 48 | for device_id in [None] + self.tag_ids: 49 | self.pozyx.printDeviceInfo(device_id) 50 | print("") 51 | print("------------POZYX MULTITAG POSITIONING V{} -------------".format(version)) 52 | print("") 53 | 54 | self.setAnchorsManual(save_to_flash=False) 55 | 56 | self.printPublishAnchorConfiguration() 57 | 58 | def loop(self): 59 | """Performs positioning and prints the results.""" 60 | for tag_id in self.tag_ids: 61 | position = Coordinates() 62 | status = self.pozyx.doPositioning( 63 | position, self.dimension, self.height, self.algorithm, remote_id=tag_id) 64 | if status == POZYX_SUCCESS: 65 | self.printPublishPosition(position, tag_id) 66 | else: 67 | self.printPublishErrorCode("positioning", tag_id) 68 | 69 | def printPublishPosition(self, position, network_id): 70 | """Prints the Pozyx's position and possibly sends it as a OSC packet""" 71 | if network_id is None: 72 | network_id = 0 73 | s = "POS ID: {}, x(mm): {}, y(mm): {}, z(mm): {}".format("0x%0.4x" % network_id, 74 | position.x, position.y, position.z) 75 | print(s) 76 | if self.osc_udp_client is not None: 77 | self.osc_udp_client.send_message( 78 | "/position", [network_id, position.x, position.y, position.z]) 79 | 80 | def setAnchorsManual(self, save_to_flash=False): 81 | """Adds the manually measured anchors to the Pozyx's device list one for one.""" 82 | for tag_id in self.tag_ids: 83 | status = self.pozyx.clearDevices(tag_id) 84 | for anchor in self.anchors: 85 | status &= self.pozyx.addDevice(anchor, tag_id) 86 | if len(anchors) > 4: 87 | status &= self.pozyx.setSelectionOfAnchors(PozyxConstants.ANCHOR_SELECT_AUTO, len(anchors), 88 | remote_id=tag_id) 89 | # enable these if you want to save the configuration to the devices. 90 | if save_to_flash: 91 | self.pozyx.saveAnchorIds(tag_id) 92 | self.pozyx.saveRegisters([PozyxRegisters.POSITIONING_NUMBER_OF_ANCHORS], tag_id) 93 | 94 | self.printPublishConfigurationResult(status, tag_id) 95 | 96 | def printPublishConfigurationResult(self, status, tag_id): 97 | """Prints the configuration explicit result, prints and publishes error if one occurs""" 98 | if tag_id is None: 99 | tag_id = 0 100 | if status == POZYX_SUCCESS: 101 | print("Configuration of tag %s: success" % tag_id) 102 | else: 103 | self.printPublishErrorCode("configuration", tag_id) 104 | 105 | def printPublishErrorCode(self, operation, network_id): 106 | """Prints the Pozyx's error and possibly sends it as a OSC packet""" 107 | error_code = SingleRegister() 108 | status = self.pozyx.getErrorCode(error_code, network_id) 109 | if network_id is None: 110 | network_id = 0 111 | if status == POZYX_SUCCESS: 112 | print("Error %s on ID %s, %s" % 113 | (operation, "0x%0.4x" % network_id, self.pozyx.getErrorMessage(error_code))) 114 | if self.osc_udp_client is not None: 115 | self.osc_udp_client.send_message( 116 | "/error_%s" % operation, [network_id, error_code[0]]) 117 | else: 118 | # should only happen when not being able to communicate with a remote Pozyx. 119 | self.pozyx.getErrorCode(error_code) 120 | print("Error % s, local error code %s" % (operation, str(error_code))) 121 | if self.osc_udp_client is not None: 122 | self.osc_udp_client.send_message("/error_%s" % operation, [0, error_code[0]]) 123 | 124 | def printPublishAnchorConfiguration(self): 125 | for anchor in self.anchors: 126 | print("ANCHOR,0x%0.4x,%s" % (anchor.network_id, str(anchor.pos))) 127 | if self.osc_udp_client is not None: 128 | self.osc_udp_client.send_message( 129 | "/anchor", [anchor.network_id, anchor.pos.x, anchor.pos.y, anchor.pos.z]) 130 | sleep(0.025) 131 | 132 | 133 | if __name__ == "__main__": 134 | # Check for the latest PyPozyx version. Skip if this takes too long or is not needed by setting to False. 135 | check_pypozyx_version = True 136 | if check_pypozyx_version: 137 | perform_latest_version_check() 138 | 139 | # shortcut to not have to find out the port yourself. 140 | serial_port = get_first_pozyx_serial_port() 141 | if serial_port is None: 142 | print("No Pozyx connected. Check your USB cable or your driver!") 143 | quit() 144 | 145 | # enable to send position data through OSC 146 | use_processing = True 147 | 148 | # configure if you want to route OSC to outside your localhost. Networking knowledge is required. 149 | ip = "127.0.0.1" 150 | network_port = 8888 151 | 152 | 153 | # IDs of the tags to position, add None to position the local tag as well. 154 | tag_ids = [None, 0x6e66] 155 | 156 | # necessary data for calibration 157 | anchors = [DeviceCoordinates(0x6058, 1, Coordinates(0, 0, 2210)), 158 | DeviceCoordinates(0x6005, 1, Coordinates(9200, -700, 2400)), 159 | DeviceCoordinates(0x605C, 1, Coordinates(0, 17000, 2470)), 160 | DeviceCoordinates(0x6027, 1, Coordinates(2700, 17000, 2470)), 161 | DeviceCoordinates(0x682e, 1, Coordinates(2700, 2500, 2350)), 162 | DeviceCoordinates(0x6044, 1, Coordinates(11230, 2750, 2300))] 163 | 164 | # positioning algorithm to use, other is PozyxConstants.POSITIONING_ALGORITHM_TRACKING 165 | algorithm = PozyxConstants.POSITIONING_ALGORITHM_UWB_ONLY 166 | # positioning dimension. Others are PozyxConstants.DIMENSION_2D, PozyxConstants.DIMENSION_2_5D 167 | dimension = PozyxConstants.DIMENSION_3D 168 | # height of device, required in 2.5D positioning 169 | height = 1000 170 | 171 | osc_udp_client = None 172 | if use_processing: 173 | osc_udp_client = SimpleUDPClient(ip, network_port) 174 | 175 | pozyx = PozyxSerial(serial_port) 176 | 177 | r = MultitagPositioning(pozyx, osc_udp_client, tag_ids, anchors, 178 | algorithm, dimension, height) 179 | r.setup() 180 | while True: 181 | r.loop() 182 | -------------------------------------------------------------------------------- /tutorials/ready_to_localize.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | """ 3 | The Pozyx ready to localize tutorial (c) Pozyx Labs 4 | Please read the tutorial that accompanies this sketch: 5 | https://www.pozyx.io/Documentation/Tutorials/ready_to_localize/Python 6 | 7 | This tutorial requires at least the contents of the Pozyx Ready to Localize kit. It demonstrates the positioning capabilities 8 | of the Pozyx device both locally and remotely. Follow the steps to correctly set up your environment in the link, change the 9 | parameters and upload this sketch. Watch the coordinates change as you move your device around! 10 | """ 11 | from time import sleep 12 | 13 | from pypozyx import (POZYX_POS_ALG_UWB_ONLY, POZYX_3D, Coordinates, POZYX_SUCCESS, PozyxConstants, version, 14 | DeviceCoordinates, PozyxSerial, get_first_pozyx_serial_port, SingleRegister, DeviceList, PozyxRegisters) 15 | from pythonosc.udp_client import SimpleUDPClient 16 | 17 | from pypozyx.tools.version_check import perform_latest_version_check 18 | 19 | 20 | class ReadyToLocalize(object): 21 | """Continuously calls the Pozyx positioning function and prints its position.""" 22 | 23 | def __init__(self, pozyx, osc_udp_client, anchors, algorithm=POZYX_POS_ALG_UWB_ONLY, dimension=POZYX_3D, height=1000, remote_id=None): 24 | self.pozyx = pozyx 25 | self.osc_udp_client = osc_udp_client 26 | 27 | self.anchors = anchors 28 | self.algorithm = algorithm 29 | self.dimension = dimension 30 | self.height = height 31 | self.remote_id = remote_id 32 | 33 | def setup(self): 34 | """Sets up the Pozyx for positioning by calibrating its anchor list.""" 35 | print("------------POZYX POSITIONING V{} -------------".format(version)) 36 | print("") 37 | print("- System will manually configure tag") 38 | print("") 39 | print("- System will auto start positioning") 40 | print("") 41 | if self.remote_id is None: 42 | self.pozyx.printDeviceInfo(self.remote_id) 43 | else: 44 | for device_id in [None, self.remote_id]: 45 | self.pozyx.printDeviceInfo(device_id) 46 | print("") 47 | print("------------POZYX POSITIONING V{} -------------".format(version)) 48 | print("") 49 | 50 | self.setAnchorsManual(save_to_flash=False) 51 | self.printPublishConfigurationResult() 52 | 53 | def loop(self): 54 | """Performs positioning and displays/exports the results.""" 55 | position = Coordinates() 56 | status = self.pozyx.doPositioning( 57 | position, self.dimension, self.height, self.algorithm, remote_id=self.remote_id) 58 | if status == POZYX_SUCCESS: 59 | self.printPublishPosition(position) 60 | else: 61 | self.printPublishErrorCode("positioning") 62 | 63 | def printPublishPosition(self, position): 64 | """Prints the Pozyx's position and possibly sends it as a OSC packet""" 65 | network_id = self.remote_id 66 | if network_id is None: 67 | network_id = 0 68 | print("POS ID {}, x(mm): {pos.x} y(mm): {pos.y} z(mm): {pos.z}".format( 69 | "0x%0.4x" % network_id, pos=position)) 70 | if self.osc_udp_client is not None: 71 | self.osc_udp_client.send_message( 72 | "/position", [network_id, int(position.x), int(position.y), int(position.z)]) 73 | 74 | def printPublishErrorCode(self, operation): 75 | """Prints the Pozyx's error and possibly sends it as a OSC packet""" 76 | error_code = SingleRegister() 77 | network_id = self.remote_id 78 | if network_id is None: 79 | self.pozyx.getErrorCode(error_code) 80 | print("LOCAL ERROR %s, %s" % (operation, self.pozyx.getErrorMessage(error_code))) 81 | if self.osc_udp_client is not None: 82 | self.osc_udp_client.send_message("/error", [operation, 0, error_code[0]]) 83 | return 84 | status = self.pozyx.getErrorCode(error_code, self.remote_id) 85 | if status == POZYX_SUCCESS: 86 | print("ERROR %s on ID %s, %s" % 87 | (operation, "0x%0.4x" % network_id, self.pozyx.getErrorMessage(error_code))) 88 | if self.osc_udp_client is not None: 89 | self.osc_udp_client.send_message( 90 | "/error", [operation, network_id, error_code[0]]) 91 | else: 92 | self.pozyx.getErrorCode(error_code) 93 | print("ERROR %s, couldn't retrieve remote error code, LOCAL ERROR %s" % 94 | (operation, self.pozyx.getErrorMessage(error_code))) 95 | if self.osc_udp_client is not None: 96 | self.osc_udp_client.send_message("/error", [operation, 0, -1]) 97 | # should only happen when not being able to communicate with a remote Pozyx. 98 | 99 | def setAnchorsManual(self, save_to_flash=False): 100 | """Adds the manually measured anchors to the Pozyx's device list one for one.""" 101 | status = self.pozyx.clearDevices(remote_id=self.remote_id) 102 | for anchor in self.anchors: 103 | status &= self.pozyx.addDevice(anchor, remote_id=self.remote_id) 104 | if len(self.anchors) > 4: 105 | status &= self.pozyx.setSelectionOfAnchors(PozyxConstants.ANCHOR_SELECT_AUTO, len(self.anchors), 106 | remote_id=self.remote_id) 107 | 108 | if save_to_flash: 109 | self.pozyx.saveAnchorIds(remote_id=self.remote_id) 110 | self.pozyx.saveRegisters([PozyxRegisters.POSITIONING_NUMBER_OF_ANCHORS], remote_id=self.remote_id) 111 | return status 112 | 113 | def printPublishConfigurationResult(self): 114 | """Prints and potentially publishes the anchor configuration result in a human-readable way.""" 115 | list_size = SingleRegister() 116 | 117 | self.pozyx.getDeviceListSize(list_size, self.remote_id) 118 | print("List size: {0}".format(list_size[0])) 119 | if list_size[0] != len(self.anchors): 120 | self.printPublishErrorCode("configuration") 121 | return 122 | device_list = DeviceList(list_size=list_size[0]) 123 | self.pozyx.getDeviceIds(device_list, self.remote_id) 124 | print("Calibration result:") 125 | print("Anchors found: {0}".format(list_size[0])) 126 | print("Anchor IDs: ", device_list) 127 | 128 | for i in range(list_size[0]): 129 | anchor_coordinates = Coordinates() 130 | self.pozyx.getDeviceCoordinates(device_list[i], anchor_coordinates, self.remote_id) 131 | print("ANCHOR, 0x%0.4x, %s" % (device_list[i], str(anchor_coordinates))) 132 | if self.osc_udp_client is not None: 133 | self.osc_udp_client.send_message( 134 | "/anchor", [device_list[i], int(anchor_coordinates.x), int(anchor_coordinates.y), int(anchor_coordinates.z)]) 135 | sleep(0.025) 136 | 137 | def printPublishAnchorConfiguration(self): 138 | """Prints and potentially publishes the anchor configuration""" 139 | for anchor in self.anchors: 140 | print("ANCHOR,0x%0.4x,%s" % (anchor.network_id, str(anchor.coordinates))) 141 | if self.osc_udp_client is not None: 142 | self.osc_udp_client.send_message( 143 | "/anchor", [anchor.network_id, int(anchor.coordinates.x), int(anchor.coordinates.y), int(anchor.coordinates.z)]) 144 | sleep(0.025) 145 | 146 | 147 | if __name__ == "__main__": 148 | # Check for the latest PyPozyx version. Skip if this takes too long or is not needed by setting to False. 149 | check_pypozyx_version = True 150 | if check_pypozyx_version: 151 | perform_latest_version_check() 152 | 153 | # shortcut to not have to find out the port yourself 154 | serial_port = get_first_pozyx_serial_port() 155 | if serial_port is None: 156 | print("No Pozyx connected. Check your USB cable or your driver!") 157 | quit() 158 | 159 | remote_id = 0x6e66 # remote device network ID 160 | remote = False # whether to use a remote device 161 | if not remote: 162 | remote_id = None 163 | 164 | # enable to send position data through OSC 165 | use_processing = True 166 | 167 | # configure if you want to route OSC to outside your localhost. Networking knowledge is required. 168 | ip = "127.0.0.1" 169 | network_port = 8888 170 | 171 | osc_udp_client = None 172 | if use_processing: 173 | osc_udp_client = SimpleUDPClient(ip, network_port) 174 | 175 | # necessary data for calibration, change the IDs and coordinates yourself according to your measurement 176 | anchors = [DeviceCoordinates(0xA001, 1, Coordinates(0, 0, 2790)), 177 | DeviceCoordinates(0xA002, 1, Coordinates(10490, 0, 2790)), 178 | DeviceCoordinates(0xA003, 1, Coordinates(-405, 6000, 2790)), 179 | DeviceCoordinates(0xA004, 1, Coordinates(10490, 6500, 2790))] 180 | 181 | # positioning algorithm to use, other is PozyxConstants.POSITIONING_ALGORITHM_TRACKING 182 | algorithm = PozyxConstants.POSITIONING_ALGORITHM_UWB_ONLY 183 | # positioning dimension. Others are PozyxConstants.DIMENSION_2D, PozyxConstants.DIMENSION_2_5D 184 | dimension = PozyxConstants.DIMENSION_3D 185 | # height of device, required in 2.5D positioning 186 | height = 1000 187 | 188 | pozyx = PozyxSerial(serial_port) 189 | r = ReadyToLocalize(pozyx, osc_udp_client, anchors, algorithm, dimension, height, remote_id) 190 | r.setup() 191 | while True: 192 | r.loop() 193 | -------------------------------------------------------------------------------- /pypozyx/structures/generic.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # TODO move this in the RST files. 3 | """ 4 | pypozyx.structures.generic - introduces generic data structures derived from ByteStructure 5 | 6 | Generic Structures 7 | 8 | As the name implies, contains generic structures whose specific use is up to the 9 | user. You should use SingleRegister where applicable when reading/writing 10 | a single register, and use Data for larger data structures. 11 | 12 | Structures contained: 13 | Data 14 | THE generic data structure, a powerful way of constructing arbitrarily 15 | formed packed data structures 16 | XYZ 17 | A generic XYZ data structure that is used in much 3D sensor data 18 | SingleRegister 19 | Data resembling a single register. Can choose size and whether signed. 20 | UniformData 21 | A variation on Data with all data being a uniform format. Questionably useful. 22 | 23 | The use of Data: 24 | Data creates a packed data structure with size and format that is entirely the user's choice. 25 | The format follows the one used in struct, where b is a byte, h is a 2-byte int, and 26 | i is a default-sized integer, and f is a float. In capitals, these are signed. 27 | So, to create a custom construct consisting of 4 uint16 and a single int, the 28 | following code can be used. 29 | 30 | >>> d = Data([0] * 5, 'HHHHi') 31 | 32 | or 33 | 34 | >>> data_format = 'HHHHi' 35 | >>> d = Data([0] * len(data_format), data_format) 36 | """ 37 | 38 | from pypozyx.structures.byte_structure import ByteStructure 39 | 40 | 41 | def is_reg_readable(reg): 42 | """Returns whether a Pozyx register is readable.""" 43 | if (0x00 <= reg < 0x07) or (0x10 <= reg < 0x12) or (0x14 <= reg < 0x22) or (0x22 <= reg <= 0x24) or ( 44 | 0x26 <= reg < 0x2B) or (0x30 <= reg < 0x48) or (0x4E <= reg < 0x89): 45 | return True 46 | return False 47 | 48 | 49 | def is_reg_writable(reg): 50 | """Returns whether a Pozyx register is writeable.""" 51 | if (0x10 <= reg < 0x12) or (0x14 <= reg < 0x22) or (0x22 <= reg <= 0x24) or (0x26 <= reg < 0x2B) or ( 52 | 0x30 <= reg < 0x3C) or (0x85 <= reg < 0x89): 53 | return True 54 | return False 55 | 56 | 57 | def is_functioncall(reg): 58 | """Returns whether a Pozyx register is a Pozyx function.""" 59 | if (0xB0 <= reg <= 0xBC) or (0xC0 <= reg < 0xC9): 60 | return True 61 | return False 62 | 63 | 64 | def dataCheck(data): 65 | """Returns whether an object is part of the ByteStructure-derived classes or not. 66 | 67 | The function checks the base classes of the passed data object. This function enables 68 | many library functions to be passed along its data as either an int/list or the properly 69 | intended data structure. For example, the following code will result in the 70 | same behaviour:: 71 | 72 | >>> p.setCoordinates([0, 0, 0]) 73 | >>> # or 74 | >>> coords = Coordinates() 75 | >>> p.setCoordinates(coords) 76 | 77 | AND 78 | 79 | >>> p.setNetworkId(0x6000) 80 | >>> # or 81 | >>> n = NetworkID(0x6000) 82 | >>> p.setNetworkId(n) 83 | 84 | Note that this only works for functions where you change one of the Pozyx's 85 | settings. When reading data from the Pozyx, you have to pass along the correct 86 | data structure. 87 | 88 | Using dataCheck: 89 | You might want to use this in your own function, as it makes it more robust 90 | to whether an int or list gets sent as a parameter to your function, or a 91 | ByteStructure-like object. If so, you can perform:: 92 | 93 | >>> if not dataCheck(sample): # assume a is an int but you want it to be a SingleRegister 94 | >>> sample = SingleRegister(sample) 95 | 96 | """ 97 | if not(Data in type(data).__bases__ or ByteStructure in type(data).__bases__ or Data is type(data) or XYZ in type(data).__bases__ or SingleRegister in type(data).__bases__): 98 | return False 99 | return True 100 | 101 | 102 | class XYZ(ByteStructure): 103 | """ 104 | Generic XYZ data structure consisting of 3 integers x, y, and z. 105 | 106 | Not recommended to use in practice, as relevant sensor data classes are derived from this. 107 | """ 108 | physical_convert = 1 109 | 110 | byte_size = 12 111 | data_format = 'iii' 112 | 113 | def __init__(self, x=0, y=0, z=0): 114 | """Initializes the XYZ or XYZ-derived object.""" 115 | self.data = [x, y, z] 116 | 117 | def load(self, data, convert=True): 118 | self.data = data 119 | 120 | def __str__(self): 121 | return 'X: {}, Y: {}, Z: {}'.format(self.x, self.y, self.z) 122 | 123 | @property 124 | def x(self): 125 | return self.data[0] / self.physical_convert 126 | 127 | @x.setter 128 | def x(self, value): 129 | self.data[0] = value * self.physical_convert 130 | 131 | @property 132 | def y(self): 133 | return self.data[1] / self.physical_convert 134 | 135 | @y.setter 136 | def y(self, value): 137 | self.data[1] = value * self.physical_convert 138 | 139 | @property 140 | def z(self): 141 | return self.data[2] / self.physical_convert 142 | 143 | @z.setter 144 | def z(self, value): 145 | self.data[2] = value * self.physical_convert 146 | 147 | # TODO maybe use asdict()? Move to dataclasses? 148 | def to_dict(self): 149 | return { 150 | "x": self.x, 151 | "y": self.y, 152 | "z": self.z, 153 | } 154 | 155 | 156 | class Data(ByteStructure): 157 | """Data allows the user to define arbitrary data structures to use with Pozyx. 158 | 159 | The Leatherman of ByteStructure-derived classes, Data allows you to create your own 160 | library-compatible packed data structures. Also for empty data, this is used. 161 | 162 | The use of Data: 163 | Data creates a packed data structure with size and format that is entirely the user's choice. 164 | The format follows the one used in struct, where b is a byte, h is a 2-byte int, and 165 | i is a default-sized integer, and f is a float. In capitals, these are unsigned. 166 | So, to create a custom construct consisting of 4 uint16 and a single int, the 167 | following code can be used. 168 | 169 | >>> d = Data([0] * 5, 'HHHHi') 170 | 171 | or 172 | 173 | >>> data_format = 'HHHHi' 174 | >>> d = Data([0] * len(data_format), data_format) 175 | 176 | Args: 177 | data (optional): Data contained in the data structure. When no data_format is passed, these are assumed UInt8 values. 178 | data_format (optional): Custom data format for the data passed. 179 | """ 180 | 181 | def __init__(self, data=None, data_format=None): 182 | if data is None: 183 | data = [] 184 | self.data = data 185 | if data_format is None: 186 | data_format = 'B' * len(data) 187 | self.data_format = data_format 188 | self.set_packed_size() 189 | self.byte_data = '00' * self.byte_size 190 | 191 | def load(self, data, convert=True): 192 | self.data = data 193 | 194 | 195 | class SingleRegister(Data): 196 | """ SingleRegister is container for the data from a single Pozyx register. 197 | 198 | By default, this represents a UInt8 register. Used for both reading and writing. 199 | The size and whether the data is a 'signed' integer are both changeable by the 200 | user using the size and signed keyword arguments. 201 | 202 | Args: 203 | value (optional): Value of the register. 204 | size (optional): Size of the register. 1, 2, or 4. Default 1. 205 | signed (optional): Whether the data is signed. unsigned by default. 206 | print_hex (optional): How to print the register output. Hex by default. Special options are 'hex' and 'bin' 207 | other things, such as 'dec', will return decimal output. 208 | """ 209 | byte_size = 1 210 | data_format = 'B' 211 | 212 | def __init__(self, value=0, size=1, signed=False, print_style='hex'): 213 | self.print_style = print_style 214 | if size == 1: 215 | data_format = 'b' 216 | elif size == 2: 217 | data_format = 'h' 218 | elif size == 4: 219 | data_format = 'i' 220 | else: 221 | raise ValueError("Size should be 1, 2, or 4") 222 | if not signed: 223 | data_format = data_format.capitalize() 224 | Data.__init__(self, [value], data_format) 225 | 226 | def load(self, data, convert=True): 227 | self.data = data 228 | 229 | @property 230 | def value(self): 231 | return self.data[0] 232 | 233 | @value.setter 234 | def value(self, new_value): 235 | self.data[0] = new_value 236 | 237 | def __str__(self): 238 | if self.print_style is 'hex': 239 | return hex(self.value).capitalize() 240 | elif self.print_style is 'bin': 241 | return bin(self.value) 242 | else: 243 | return str(self.value) 244 | 245 | def __eq__(self, other): 246 | if type(other) == SingleRegister: 247 | return self.value == other.value 248 | elif type(other) == int: 249 | return self.value == other 250 | else: 251 | raise ValueError("Can't compare SingleRegister value with non-integer values or registers") 252 | 253 | def __le__(self, other): 254 | if type(other) == SingleRegister: 255 | return self.value <= other.value 256 | elif type(other) == int: 257 | return self.value <= other 258 | else: 259 | raise ValueError("Can't compare SingleRegister value with non-integer values or registers") 260 | 261 | def __lt__(self, other): 262 | if type(other) == SingleRegister: 263 | return self.value < other.value 264 | elif type(other) == int: 265 | return self.value < other 266 | else: 267 | raise ValueError("Can't compare SingleRegister value with non-integer values or registers") 268 | 269 | def __gt__(self, other): 270 | return not self.__le__(other) 271 | 272 | def __ge__(self, other): 273 | return not self.__lt__(other) 274 | 275 | 276 | class SingleSensorValue(ByteStructure): 277 | """ 278 | Generic Single Sensor Value data structure. 279 | 280 | Not recommended to use in practice, as relevant sensor data classes are derived from this. 281 | """ 282 | physical_convert = 1 283 | 284 | byte_size = 4 285 | data_format = 'i' 286 | 287 | def __init__(self, value=0): 288 | """Initializes the XYZ or XYZ-derived object.""" 289 | self.data = [0] 290 | 291 | self.load([value]) 292 | 293 | @property 294 | def value(self): 295 | return self.data[0] 296 | 297 | @value.setter 298 | def value(self, new_value): 299 | self.data[0] = new_value 300 | 301 | def load(self, data=None, convert=True): 302 | self.data = [0] if data is None else data 303 | 304 | if convert: 305 | self.data[0] = float(self.data[0]) / self.physical_convert 306 | 307 | def __str__(self): 308 | return 'Value: {}'.format(self.value) 309 | -------------------------------------------------------------------------------- /docs/getting_started/index.rst: -------------------------------------------------------------------------------- 1 | Getting started 2 | --------------- 3 | 4 | Finding your serial port 5 | ~~~~~~~~~~~~~~~~~~~~~~~~ 6 | 7 | There's a helper in the library for identifying the first serial port that is a Pozyx. This is easily done with a Python snippet 8 | 9 | .. TODO link to the function in the docs. 10 | 11 | .. code-block:: python 12 | 13 | import pypozyx 14 | 15 | print(pypozyx.get_first_pozyx_serial_port()) 16 | 17 | Or from the command line 18 | 19 | ``python -c "from pypozyx import *;print(get_first_pozyx_serial_port())"`` 20 | 21 | If there is no Pozyx device recognized, the function will return ``None`` and thus nothing will be printed. 22 | 23 | 24 | Connecting to the Pozyx 25 | ~~~~~~~~~~~~~~~~~~~~~~~ 26 | 27 | Connecting with the Pozyx is very straightforward. A safe way is presented here: 28 | 29 | .. code-block:: python 30 | 31 | from pypozyx import PozyxSerial, get_first_pozyx_serial_port 32 | 33 | serial_port = get_first_pozyx_serial_port() 34 | 35 | if serial_port is not None: 36 | pozyx = PozyxSerial(serial_port) 37 | print("Connection success!") 38 | else: 39 | print("No Pozyx port was found") 40 | 41 | With this, you have a pozyx object with the full API at your fingertips. For example, you can read the `UWB settings `_ with the following snippet. 42 | 43 | .. code-block:: python 44 | 45 | from pypozyx import PozyxSerial, get_first_pozyx_serial_port, UWBSettings 46 | 47 | serial_port = get_first_pozyx_serial_port() 48 | 49 | if serial_port is not None: 50 | pozyx = PozyxSerial(serial_port) 51 | uwb_settings = UWBSettings() 52 | pozyx.getUWBSettings(uwb_settings) 53 | print(uwb_settings) 54 | else: 55 | print("No Pozyx port was found") 56 | 57 | 58 | General philosophy 59 | ~~~~~~~~~~~~~~~~~~ 60 | 61 | As said in the introduction, the pypozyx library was heavily inspired by the Arduino library, making it less pythonic. 62 | 63 | * The functions are camelCased 64 | * Almost all functions return a status and take the relevant data container as an argument. 65 | 66 | This had as an advantage that users coming from Arduino could easily adapt their code, and that the documentation was very similar. However, I'd love to change these things when I make a 2.0 release. 67 | 68 | Essentially, you can do three things with Pozyx: 69 | 70 | 1. Reading register data, which includes sensors and the device's configuration 71 | 2. Writing data to registers, making it possible to change the device's configuration ranging from its positioning algorithm to its very ID. 72 | 3. Performing Pozyx functions like ranging, positioning, saving the device's configuration to its flash memory... 73 | 74 | All these things are possible to do on the device connected to your computer, and powered remote devices as well. In this section we'll go over all of these. 75 | 76 | Reading data 77 | ~~~~~~~~~~~~ 78 | 79 | To read data from the Pozyx, a simple pattern is followed. This pattern can be used with almost all methods starting with the words 'get': 80 | 81 | 1. Initialize the appropriate container for your data read. 82 | 2. Pass this container along with the get functions. 83 | 3. Check the status to see if the operation was successful and thus the data trustworthy. 84 | 85 | You can see the same pattern in action above when reading the UWB data. 86 | 87 | .. TODO An overview of all data containers, their usage and their particularities can be found here: 88 | 89 | .. TODO also mention that they all have human readable __str__ conversions 90 | 91 | .. code-block:: python 92 | 93 | from pypozyx import PozyxSerial, get_first_pozyx_serial_port, POZYX_SUCCESS, SingleRegister, EulerAngles, Acceleration 94 | # initalize the Pozyx as above 95 | 96 | # initialize the data container 97 | who_am_i = SingleRegister() 98 | # get the data, passing along the container 99 | status = pozyx.getWhoAmI(who_am_i) 100 | 101 | # check the status to see if the read was successful. Handling failure is covered later. 102 | if status == POZYX_SUCCESS: 103 | # print the container. Note how a SingleRegister will print as a hex string by default. 104 | print(who_am_i) # will print '0x43' 105 | 106 | # and repeat 107 | # initialize the data container 108 | acceleration = Acceleration() 109 | # get the data, passing along the container 110 | pozyx.getAcceleration_mg(acceleration) 111 | 112 | # initialize the data container 113 | euler_angles = EulerAngles() 114 | # get the data, passing along the container 115 | pozyx.getEulerAngles_deg(euler_angles) 116 | 117 | 118 | Writing data 119 | ~~~~~~~~~~~~ 120 | 121 | Writing data follows a similar pattern as reading, but making a container for the data is optional. This pattern can be used with all methods starting with the words 'set': 122 | 123 | 1. (Optional) Initialize the appropriate container with the right contents for your data write. 124 | 2. Pass this container or the right value along with the set functions. 125 | 3. Check the status to see if the operation was successful and thus the data written. 126 | 127 | .. note:: 128 | 129 | All set functions are tolerant for values that aren't per se a data object. An integer value or respectively fitting array with the relevant data as contained in the register will pass as well. 130 | 131 | .. code-block:: python 132 | 133 | # method 1: making a data object 134 | uwb_channel = SingleRegister(5) 135 | pozyx.setUWBChannel(uwb_channel) 136 | # method 2: or just using the channel number directly 137 | pozyx.setUWBChannel(5) 138 | 139 | # both have the same effect! 140 | 141 | The advantage of using the data object approach lies especially with more complex data, where your Python editor will give you more information on what content you're putting in, or where the object will convert data to the right form for you. 142 | 143 | .. code-block:: python 144 | 145 | # method 1: making a data object 146 | # this is much more readable 147 | uwb_settings = UWBSettings(channel=5, bitrate=1, prf=2, plen=0x08, gain_db=25.0) 148 | pozyx.setUWBChannel(uwb_channel) 149 | # method 2: using the register values directly 150 | # this isn't readable and also not writable (need to search in depth register documentation) 151 | pozyx.setUWBSettings([5, 0b10000001, 0x08, 50]) 152 | 153 | # both still have the same effect, but note how bitrate and prf combine in a register value, 154 | # and gain is doubled when converted to its register contents. 155 | 156 | Some typical write operations 157 | 158 | .. code-block:: python 159 | 160 | from pypozyx import PozyxSerial, get_first_pozyx_serial_port, POZYX_SUCCESS, SingleRegister, PozyxConstants 161 | 162 | # initialize Pozyx as above 163 | 164 | pozyx.setPositionAlgorithm(PozyxConstants.POSITIONING_ALGORITHM_UWB_ONLY) 165 | 166 | new_id = NetworkId(0x1) 167 | pozyx.setNetworkId(new_id) 168 | 169 | pozyx.setPositioningFilter(PozyxConstant.FILTER_TYPE_MOVING_AVERAGE, 10) 170 | 171 | Note that you seemingly need to know that the positioning filter has ``PozyxConstant.FILTER_TYPE_MOVING_AVERAGE`` as a possible type of filter. This is pretty low-level knowledge and may remain hidden when not knowing about, and so in a recent version we added a lot of helpers that do away with having to know the appropriate constants for certain operations. 172 | 173 | .. code-block:: python 174 | 175 | # instead of pozyx.setPositionAlgorithm(PozyxConstants.POSITIONING_ALGORITHM_UWB_ONLY) 176 | pozyx.setPositionAlgorithmNormal() 177 | 178 | # instead of pozyx.setPositioningFilter(PozyxConstant.FILTER_TYPE_MOVING_AVERAGE, 10) 179 | pozyx.setPositioningFilterMovingAverage(10) 180 | 181 | Performing functions 182 | ~~~~~~~~~~~~~~~~~~~~ 183 | 184 | Positioning, ranging, configuring the anchors for a tag to use... While the line is sometimes thin, these aren't per se writes or reads as they are functions on the Pozyx. 185 | 186 | A Pozyx device function typically can take a container object for storing the function's return data, and a container object for the function parameters. 187 | 188 | For example, when adding an anchor to a tag's device list, the anchor's ID and position are the function's parameters, but there is no return data. Thus, the function addDevice only needs a container object containing the anchor's properties. 189 | 190 | In the library, function wrappers are written in such a way that when no parameters are required, they are hidden from the user, and the same goes for return data. 191 | 192 | .. code-block:: python 193 | 194 | from pypozyx import ..., Coordinates, DeviceCoordinates 195 | 196 | # assume an anchor 0x6038 that we want to add to the device list and immediately save the device list after. 197 | anchor = DeviceCoordinates(0x6038), 0, Coordinates(5000, 5000, 0)) 198 | pozyx.addDevice(anchor) 199 | pozyx.saveNetwork() 200 | 201 | # after, we can start positioning. Positioning takes its parameters from the configuration in the tag's 202 | # registers, and so we only need the coordinates. 203 | position = Coordinates() 204 | pozyx.doPositioning(position) 205 | 206 | .. TODO find better example than positioning since that's a lie 207 | 208 | Remote 209 | ~~~~~~ 210 | 211 | To interface with a remote device, every function has a remote_id optional parameter. Thus, every function you just saw can be performed on a remote device as well! 212 | 213 | .. code-block:: python 214 | 215 | # let's assume there is another tag present with ID 0x6039 216 | remote_device_id = 0x6039 217 | 218 | # this will read the WHO_AM_I register of the remote tag 219 | who_am_i = SingleRegister() 220 | pozyx.getWhoAmI(who_am_i) 221 | print(who_am_i) # will print 0x43 222 | 223 | 224 | Saving writable register data 225 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 226 | 227 | Basically, every register you can write data to as a user can be saved in the device's flash memory. This means that when the device is powered on, its configuration will remain. Otherwise, the device will use its default values again. 228 | 229 | .. TODO add default values for registers so that users know what to expect. 230 | 231 | This is useful for multiple things: 232 | 233 | * Saving the UWB settings so all your devices remain on the same UWB settings. 234 | * Saving the anchors the tag uses for positioning. This means that after a reset, the tag can resume positioning immediately and doesn't need to be reconfigured! 235 | * Saving positioning algorithm, dimension, filter... you'll never lose your favorite settings when the device shuts down. 236 | 237 | There are various helpers in the library to help you save the settings you prefer, not requiring you to look up the relevant registers. 238 | 239 | .. code-block:: python 240 | 241 | # Saves the positioning settings 242 | pozyx.savePositioningSettings() 243 | # Saves the device list used for positioning 244 | pozyx.saveNetwork() 245 | # Saves the device's UWB settings 246 | pozyx.saveUWBSettings() 247 | 248 | Finding out the error 249 | ~~~~~~~~~~~~~~~~~~~~~ 250 | 251 | Pozyx functions typically return a status to indicate the success of the function. This is useful to indicate failure especially. When things go wrong, it's advised to read the error as well. 252 | 253 | A code snippet shows how this is typically done 254 | 255 | .. code-block:: python 256 | 257 | from pypozyx import PozyxSerial, get_first_pozyx_serial_port, POZYX_SUCCESS, SingleRegister 258 | 259 | # initialize Pozyx as above 260 | 261 | if pozyx.saveUWBSettings() != POZYX_SUCCESS: 262 | # this is one way which retrieves the error code 263 | error_code = SingleRegister() 264 | pozyx.getErrorCode(error_code) 265 | print('Pozyx error code: %s' % error_code) 266 | # the other method returns a descriptive string 267 | print(pozyx.getSystemError()) 268 | -------------------------------------------------------------------------------- /pypozyx/pozyx_serial.py: -------------------------------------------------------------------------------- 1 | """pypozyx.pozyx_serial - contains the serial interface with Pozyx through PozyxSerial.""" 2 | from time import sleep 3 | from pypozyx.core import PozyxConnectionError 4 | 5 | from pypozyx.definitions.constants import (POZYX_SUCCESS, POZYX_FAILURE, 6 | MAX_SERIAL_SIZE) 7 | 8 | from pypozyx.lib import PozyxLib 9 | from pypozyx.structures.generic import SingleRegister 10 | from serial import Serial, VERSION as PYSERIAL_VERSION, SerialException 11 | from serial.tools.list_ports import comports 12 | 13 | from warnings import warn 14 | 15 | # \addtogroup auxiliary_serial 16 | # @{ 17 | 18 | 19 | def list_serial_ports(): 20 | """Prints the open serial ports line per line""" 21 | warn("list_serial_ports now deprecated, use print_all_serial_ports instead", DeprecationWarning) 22 | ports = comports() 23 | for port in ports: 24 | print(port) 25 | 26 | 27 | def print_all_serial_ports(): 28 | """Prints the open serial ports line per line""" 29 | ports = comports() 30 | for port in ports: 31 | print(port) 32 | 33 | 34 | def get_serial_ports(): 35 | """Returns the open serial ports""" 36 | return comports() 37 | 38 | 39 | def is_pozyx_port(port): 40 | """Returns whether the port is a Pozyx device""" 41 | try: 42 | if "Pozyx Labs" in port.manufacturer: 43 | return True 44 | except TypeError: 45 | pass 46 | try: 47 | if "Pozyx" in port.product: 48 | return True 49 | except TypeError: 50 | pass 51 | try: 52 | if "0483:" in port.hwid: 53 | return True 54 | except TypeError: 55 | pass 56 | return False 57 | 58 | 59 | def get_port_object(device): 60 | """Returns the PySerial port object from a given port path""" 61 | for port in get_serial_ports(): 62 | if port.device == device: 63 | return port 64 | 65 | 66 | def is_pozyx(device): 67 | """Returns whether the device is a recognized Pozyx device""" 68 | port = get_port_object(device) 69 | if port is not None and is_pozyx_port(port): 70 | return True 71 | return False 72 | 73 | 74 | def get_pozyx_ports(): 75 | """Returns the Pozyx serial ports. Windows only. Needs driver installed""" 76 | pozyx_ports = [] 77 | for port in get_serial_ports(): 78 | if is_pozyx_port(port): 79 | pozyx_ports.append(port.device) 80 | return pozyx_ports 81 | 82 | 83 | def get_first_pozyx_serial_port(): 84 | """Returns the first encountered Pozyx serial port's identifier""" 85 | for port in get_serial_ports(): 86 | if is_pozyx_port(port): 87 | return port.device 88 | 89 | 90 | def get_pozyx_ports_windows(): 91 | """Returns the Pozyx serial ports. Windows only. Needs driver installed""" 92 | ports = get_serial_ports() 93 | pozyx_ports = [] 94 | for port in ports: 95 | if "STMicroelectronics Virtual COM Port" in port.description: 96 | pozyx_ports.append(port.device) 97 | 98 | 99 | def is_correct_pyserial_version(): 100 | """Returns whether the pyserial version is supported""" 101 | version_tags = [int(version_tag) 102 | for version_tag in PYSERIAL_VERSION.split('.')] 103 | if version_tags[0] >= 3: 104 | if version_tags[0] == 3 and version_tags[1] <= 3: 105 | warn("PySerial out of date, please update to v3.4 if possible", stacklevel=0) 106 | return True 107 | return False 108 | 109 | 110 | # @} 111 | 112 | 113 | class PozyxSerial(PozyxLib): 114 | """This class provides the Pozyx Serial interface, and opens and locks the serial 115 | port to use with Pozyx. All functionality from PozyxLib and PozyxCore is included. 116 | 117 | Args: 118 | port (str): Name of the serial port. On UNIX this will be '/dev/ttyACMX', on 119 | Windows this will be 'COMX', with X a random number. 120 | baudrate (optional): the baudrate of the serial port. Default value is 115200. 121 | timeout (optional): timeout for the serial port communication in seconds. Default is 0.1s or 100ms. 122 | print_output (optional): boolean for printing the serial exchanges, mainly for debugging purposes 123 | suppress_warnings (optional): boolean for suppressing warnings in the Pozyx use, usage not recommended 124 | debug_trace (optional): boolean for printing the trace on bad serial init (DEPRECATED) 125 | show_trace (optional): boolean for printing the trace on bad serial init (DEPRECATED) 126 | 127 | Example usage: 128 | >>> pozyx = PozyxSerial('COMX') # Windows 129 | >>> pozyx = PozyxSerial('/dev/ttyACMX', print_output=True) # Linux and OSX. Also puts debug output on. 130 | 131 | Finding the serial port can be easily done with the following code: 132 | >>> import serial.tools.list_ports 133 | >>> print serial.tools.list_ports.comports()[0] 134 | 135 | Putting one and two together, automating the correct port selection with one Pozyx attached: 136 | >>> import serial.tools.list_ports 137 | >>> pozyx = PozyxSerial(serial.tools.list_ports.comports()[0]) 138 | """ 139 | 140 | # \addtogroup core 141 | # @{ 142 | def __init__(self, port, baudrate=115200, timeout=0.1, write_timeout=0.1, 143 | print_output=False, debug_trace=False, show_trace=False, 144 | suppress_warnings=False): 145 | """Initializes the PozyxSerial object. See above for details.""" 146 | super(PozyxSerial, self).__init__() 147 | self.print_output = print_output 148 | if debug_trace is True or show_trace is True: 149 | if not suppress_warnings: 150 | warn("debug_trace or show_trace are on their way out, exceptions of the type PozyxException are now raised.", 151 | DeprecationWarning) 152 | self.suppress_warnings = suppress_warnings 153 | 154 | self.connectToPozyx(port, baudrate, timeout, write_timeout) 155 | 156 | sleep(0.25) 157 | 158 | self.validatePozyx() 159 | 160 | def connectToPozyx(self, port, baudrate, timeout, write_timeout): 161 | """Attempts to connect to the Pozyx via a serial connection""" 162 | self.port = port 163 | self.baudrate = baudrate 164 | self.timeout = timeout 165 | self.write_timeout = write_timeout 166 | 167 | try: 168 | if is_correct_pyserial_version(): 169 | if not is_pozyx(port) and not self.suppress_warnings: 170 | warn("The passed device is not a recognized Pozyx device, is %s" % get_port_object(port).description, stacklevel=2) 171 | self.ser = Serial(port=port, baudrate=baudrate, timeout=timeout, 172 | write_timeout=write_timeout) 173 | else: 174 | if not self.suppress_warnings: 175 | warn("PySerial version %s not supported, please upgrade to 3.0 or (prefferably) higher" % 176 | PYSERIAL_VERSION, stacklevel=0) 177 | self.ser = Serial(port=port, baudrate=baudrate, timeout=timeout, 178 | writeTimeout=write_timeout) 179 | except SerialException as exc: 180 | raise PozyxConnectionError("Wrong or busy serial port, SerialException: {}".format(str(exc))) 181 | except Exception as exc: 182 | raise PozyxConnectionError("Couldn't connect to Pozyx, {}: {}".format(exc.__class__.__name__, str(exc))) 183 | 184 | def validatePozyx(self): 185 | """Validates whether the connected device is indeed a Pozyx device""" 186 | whoami = SingleRegister() 187 | if self.getWhoAmI(whoami) != POZYX_SUCCESS: 188 | raise PozyxConnectionError("Connected to device, but couldn't read serial data. Is it a Pozyx?") 189 | if whoami.value != 0x43: 190 | raise PozyxConnectionError("POZYX_WHO_AM_I returned 0x%0.2x, something is wrong with Pozyx." % whoami.value) 191 | 192 | # @} 193 | 194 | def regWrite(self, address, data): 195 | """ 196 | Writes data to the Pozyx registers, starting at a register address, 197 | if registers are writable. 198 | 199 | Args: 200 | address: Register address to start writing at. 201 | data: Data to write to the Pozyx registers. 202 | Has to be ByteStructure-derived object. 203 | 204 | Returns: 205 | POZYX_SUCCESS, POZYX_FAILURE 206 | """ 207 | data.load_hex_string() 208 | index = 0 209 | runs = int(data.byte_size / MAX_SERIAL_SIZE) 210 | for i in range(runs): 211 | s = 'W,%0.2x,%s\r' % ( 212 | address + index, data.byte_data[2 * index: 2 * (index + MAX_SERIAL_SIZE)]) 213 | index += MAX_SERIAL_SIZE 214 | try: 215 | self.ser.write(s.encode()) 216 | except SerialException: 217 | return POZYX_FAILURE 218 | # delay(POZYX_DELAY_LOCAL_WRITE) 219 | s = 'W,%0.2x,%s\r' % (address + index, data.byte_data[2 * index:]) 220 | try: 221 | self.ser.write(s.encode()) 222 | except SerialException: 223 | return POZYX_FAILURE 224 | return POZYX_SUCCESS 225 | 226 | def serialExchange(self, s): 227 | """ 228 | Auxiliary. Performs a serial write to and read from the Pozyx. 229 | 230 | Args: 231 | s: Serial message to send to the Pozyx 232 | Returns: 233 | Serial message the Pozyx returns, stripped from 'D,' at its start 234 | and NL+CR at the end. 235 | """ 236 | self.ser.write(s.encode()) 237 | response = self.ser.readline().decode() 238 | if self.print_output: 239 | print('The response to %s is %s.' % (s.strip(), response.strip())) 240 | if len(response) == 0: 241 | raise SerialException 242 | if response[0] == 'D': 243 | return response[2:-2] 244 | raise SerialException 245 | 246 | def regRead(self, address, data): 247 | """ 248 | Reads data from the Pozyx registers, starting at a register address, 249 | if registers are readable. 250 | 251 | Args: 252 | address: Register address to start writing at. 253 | data: Data to write to the Pozyx registers. 254 | Has to be ByteStructure-derived object. 255 | 256 | Returns: 257 | POZYX_SUCCESS, POZYX_FAILURE 258 | """ 259 | runs = int(data.byte_size / MAX_SERIAL_SIZE) 260 | r = '' 261 | for i in range(runs): 262 | s = 'R,%0.2x,%i\r' % ( 263 | address + i * MAX_SERIAL_SIZE, MAX_SERIAL_SIZE) 264 | try: 265 | r += self.serialExchange(s) 266 | except SerialException: 267 | return POZYX_FAILURE 268 | s = 'R,%0.2x,%i\r' % ( 269 | address + runs * MAX_SERIAL_SIZE, data.byte_size - runs * MAX_SERIAL_SIZE) 270 | try: 271 | r += self.serialExchange(s) 272 | except SerialException: 273 | return POZYX_FAILURE 274 | data.load_bytes(r) 275 | return POZYX_SUCCESS 276 | 277 | def regFunction(self, address, params, data): 278 | """ 279 | Performs a register function on the Pozyx, if the address is a register 280 | function. 281 | 282 | Args: 283 | address: Register function address of function to perform. 284 | params: Parameters for the register function. 285 | Has to be ByteStructure-derived object. 286 | data: Container for the data the register function returns. 287 | Has to be ByteStructure-derived object. 288 | 289 | Returns: 290 | POZYX_SUCCESS, POZYX_FAILURE 291 | """ 292 | params.load_hex_string() 293 | s = 'F,%0.2x,%s,%i\r' % (address, params.byte_data, data.byte_size + 1) 294 | try: 295 | r = self.serialExchange(s) 296 | except SerialException: 297 | return POZYX_FAILURE 298 | if len(data) > 0: 299 | data.load_bytes(r[2:]) 300 | return int(r[0:2], 16) 301 | 302 | def waitForFlag(self, interrupt_flag, timeout_s, interrupt=None): 303 | """ 304 | Waits for a certain interrupt flag to be triggered, indicating that 305 | that type of interrupt occured. 306 | 307 | Args: 308 | interrupt_flag: Flag indicating interrupt type. 309 | timeout_s: time in seconds that POZYX_INT_STATUS will be checked 310 | for the flag before returning POZYX_TIMEOUT. 311 | 312 | Kwargs: 313 | interrupt: Container for the POZYX_INT_STATUS data 314 | 315 | Returns: 316 | POZYX_SUCCESS, POZYX_FAILURE, POZYX_TIMEOUT 317 | """ 318 | if interrupt is None: 319 | interrupt = SingleRegister() 320 | return self.waitForFlagSafe(interrupt_flag, timeout_s, interrupt) 321 | -------------------------------------------------------------------------------- /pypozyx/definitions/registers.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | """ 3 | pypozyx.definitions.registers - contains all register definitions used in Pozyx. 4 | 5 | It shouldn't be necessary to use these in basic applications as the library functions 6 | should provide a lot of functionality already, but for advanced users looking to 7 | implement their own low-level functionality, these might be very useful. 8 | """ 9 | 10 | 11 | class PozyxRegisters: 12 | WHO_AM_I = 0x0 # Returns the constant value 0x43. 13 | FIRMWARE_VERSION = 0x1 # Returns the POZYX firmware version. 14 | HARDWARE_VERSION = 0x2 # Returns the POZYX hardware version. 15 | SELFTEST_RESULT = 0x3 # Returns the self-test result 16 | ERROR_CODE = 0x4 # Describes a possibly system error. 17 | INTERRUPT_STATUS = 0x5 # Indicates the source of the interrupt. 18 | CALIBRATION_STATUS = 0x6 # Returns the calibration status. 19 | 20 | # Configuration registers 21 | INTERRUPT_MASK = 0x10 # Indicates which interrupts are enabled. 22 | INTERRUPT_PIN = 0x11 # Configure the interrupt pin 23 | POSITIONING_FILTER = 0x14 # Filter used for positioning 24 | LED_CONFIGURATION = 0x15 # Configure the LEDs 25 | POSITIONING_ALGORITHM = 0x16 # Algorithm used for positioning 26 | # Configure the number of anchors and selection procedure 27 | POSITIONING_NUMBER_OF_ANCHORS = 0x17 28 | ALL_POSITIONING_REGISTERS = [0x14, 0x16, 0x21, 0x38] 29 | # Defines the update interval in ms in continuous positioning. 30 | POSITIONING_INTERVAL = 0x18 31 | NETWORK_ID = 0x1A # The network id. 32 | UWB_CHANNEL = 0x1C # UWB channel number. 33 | # Configure the UWB datarate and pulse repetition frequency (PRF) 34 | UWB_RATES = 0x1D 35 | UWB_PLEN = 0x1E # Configure the UWB preamble length. 36 | UWB_GAIN = 0x1F # Configure the power gain for the UWB transmitter 37 | ALL_UWB_REGISTERS = [0x1C, 0x1D, 0x1E, 0x1F] 38 | UWB_CRYSTAL_TRIM = 0x20 # Trimming value for the uwb crystal. 39 | RANGING_PROTOCOL = 0x21 # The ranging protocol 40 | # Configure the mode of operation of the pozyx device 41 | OPERATION_MODE = 0x22 42 | SENSORS_MODE = 0x23 # Configure the mode of operation of the sensors 43 | CONFIG_BLINK_PAYLOAD = 0x24 # Configure payload that needs to be transmitted with ALOHA 44 | ALOHA_VARIATION = 0x26 # Configure the variation on the ALOHA 45 | CONFIG_GPIO_1 = 0x27 # Configure GPIO pin 1. 46 | CONFIG_GPIO_2 = 0x28 # Configure GPIO pin 2. 47 | CONFIG_GPIO_3 = 0x29 # Configure GPIO pin 3. 48 | CONFIG_GPIO_4 = 0x2A # Configure GPIO pin 4. 49 | 50 | ALL_ALOHA_REGISTERS = [0x18, 0x19, 0x24, 0x25, 0x26] 51 | 52 | 53 | # Positioning data 54 | POSITION_X = 0x30 # x-coordinate of the device in mm. 55 | POSITION_Y = 0x34 # y-coordinate of the device in mm. 56 | POSITION_Z = 0x38 # z-coordinate of the device in mm. 57 | HEIGHT = 0x38 # z-coordinate of the device in mm. 58 | POSITIONING_ERROR_X = 0x3C # estimated error covariance of x 59 | POSITIONING_ERROR_Y = 0x3E # estimated error covariance of y 60 | POSITIONING_ERROR_Z = 0x40 # estimated error covariance of z 61 | POSITIONING_ERROR_XY = 0x42 # estimated covariance of xy 62 | POSITIONING_ERROR_XZ = 0x44 # estimated covariance of xz 63 | POSITIONING_ERROR_YZ = 0x46 # estimated covariance of yz 64 | 65 | # Sensor data 66 | MAX_LINEAR_ACCELERATION = 0x4E # Return the max linear acceleration and reset it to 0 67 | PRESSURE = 0x50 # Pressure data in mPa 68 | ACCELERATION_X = 0x54 # Accelerometer data (in mg) 69 | ACCELERATION_Y = 0x56 70 | ACCELERATION_Z = 0x58 71 | MAGNETIC_X = 0x5A # Magnemtometer data 72 | MAGNETIC_Y = 0x5C 73 | MAGNETIC_Z = 0x5E 74 | GYRO_X = 0x60 # Gyroscope data 75 | GYRO_Y = 0x62 76 | GYRO_Z = 0x64 77 | # Euler angles heading (or yaw) (1 degree = 16 LSB ) 78 | EULER_ANGLE_HEADING = 0x66 79 | EULER_ANGLE_YAW = 0x66 80 | EULER_ANGLE_ROLL = 0x68 # Euler angles roll ( 1 degree = 16 LSB ) 81 | EULER_ANGLE_PITCH = 0x6A # Euler angles pitch ( 1 degree = 16 LSB ) 82 | QUATERNION_W = 0x6C # Weight of quaternion. 83 | QUATERNION_X = 0x6E # x of quaternion 84 | QUATERNION_Y = 0x70 # y of quaternion 85 | QUATERNION_Z = 0x72 # z of quaternion 86 | LINEAR_ACCELERATION_X = 0x74 # Linear acceleration in x-direction 87 | LINEAR_ACCELERATION_Y = 0x76 # Linear acceleration in y-direction 88 | LINEAR_ACCELERATION_Z = 0x78 # Linear acceleration in z-direction 89 | GRAVITY_VECTOR_X = 0x7A # x-component of gravity vector 90 | GRAVITY_VECTOR_Y = 0x7C # y-component of gravity vector 91 | GRAVITY_VECTOR_Z = 0x7E # z-component of gravity vector 92 | TEMPERATURE = 0x80 # Temperature 93 | 94 | # General data 95 | # Returns the number of devices stored internally 96 | DEVICE_LIST_SIZE = 0x81 97 | RX_NETWORK_ID = 0x82 # The network id of the latest received message 98 | RX_DATA_LENGTH = 0x84 # The length of the latest received message 99 | GPIO_1 = 0x85 # Value of the GPIO pin 1 100 | GPIO_2 = 0x86 # Value of the GPIO pin 2 101 | GPIO_3 = 0x87 # Value of the GPIO pin 3 102 | GPIO_4 = 0x88 # Value of the GPIO pin 4 103 | BLINK_INDEX = 0x89 # The blink index of the ALOHA transmissions 104 | 105 | # Functions 106 | RESET_SYSTEM = 0xB0 # Reset the Pozyx device 107 | LED_CONTROL = 0xB1 # Control LEDS 1 to 4 on the board 108 | WRITE_TX_DATA = 0xB2 # Write data in the UWB transmit (TX) buffer 109 | SEND_TX_DATA = 0xB3 # Transmit the TX buffer to some other pozyx device 110 | READ_RX_DATA = 0xB4 # Read data from the UWB receive (RX) buffer 111 | DO_RANGING = 0xB5 # Initiate ranging measurement 112 | DO_POSITIONING = 0xB6 # Initiate the positioning process. 113 | # Set the list of anchor ID's used for positioning. 114 | SET_POSITIONING_ANCHOR_IDS = 0xB7 115 | # Read the list of anchor ID's used for positioning. 116 | GET_POSITIONING_ANCHOR_IDS = 0xB8 117 | RESET_FLASH_MEMORY = 0xB9 # Reset a portion of the configuration in flash memory 118 | SAVE_FLASH_MEMORY = 0xBA # Store a portion of the configuration in flash memory 119 | GET_FLASH_DETAILS = 0xBB # Return information on what is stored in flash 120 | DO_ALOHA = 0xBC # Start / stop ALOHA mode 121 | 122 | # Device list functions 123 | # Get all the network IDs's of devices in the device list. 124 | GET_DEVICE_LIST_IDS = 0xC0 125 | # Obtain the network ID's of all pozyx devices within range. 126 | DO_DISCOVERY = 0xC1 127 | 128 | CLEAR_DEVICES = 0xC3 # Clear the list of all pozyx devices. 129 | ADD_DEVICE = 0xC4 # Add a pozyx device to the devices list 130 | # Get the stored device information for a given pozyx device 131 | GET_DEVICE_INFO = 0xC5 132 | # Get the stored coordinates of a given pozyx device 133 | GET_DEVICE_COORDINATES = 0xC6 134 | # Get the stored range information of a given pozyx device 135 | GET_DEVICE_RANGE_INFO = 0xC7 136 | CIR_DATA = 0xC8 # Get the channel impulse response (CIR) coefficients 137 | 138 | DO_POSITIONING_WITH_DATA = 0xCC 139 | 140 | 141 | # Status registers 142 | POZYX_WHO_AM_I = 0x0 # Returns the constant value 0x43. 143 | POZYX_FIRMWARE_VER = 0x1 # Returns the POZYX firmware version. 144 | POZYX_HARDWARE_VER = 0x2 # Returns the POZYX hardware version. 145 | POZYX_ST_RESULT = 0x3 # Returns the self-test result 146 | POZYX_ERRORCODE = 0x4 # Describes a possibly system error. 147 | POZYX_INT_STATUS = 0x5 # Indicates the source of the interrupt. 148 | POZYX_CALIB_STATUS = 0x6 # Returns the calibration status. 149 | 150 | # Configuration registers 151 | POZYX_INT_MASK = 0x10 # Indicates which interrupts are enabled. 152 | POZYX_INT_CONFIG = 0x11 # Configure the interrupt pin 153 | POZYX_CONFIG_LEDS = 0x15 # Configure the LEDs 154 | POZYX_POS_FILTER = 0x14 # Filter used for positioning 155 | POZYX_POS_ALG = 0x16 # Algorithm used for positioning 156 | # Configure the number of anchors and selection procedure 157 | POZYX_POS_NUM_ANCHORS = 0x17 158 | # Defines the update interval in ms in continuous positioning. 159 | POZYX_POS_INTERVAL = 0x18 160 | POZYX_NETWORK_ID = 0x1A # The network id. 161 | POZYX_UWB_CHANNEL = 0x1C # UWB channel number. 162 | # Configure the UWB datarate and pulse repetition frequency (PRF) 163 | POZYX_UWB_RATES = 0x1D 164 | POZYX_UWB_PLEN = 0x1E # Configure the UWB preamble length. 165 | POZYX_UWB_GAIN = 0x1F # Configure the power gain for the UWB transmitter 166 | POZYX_UWB_XTALTRIM = 0x20 # Trimming value for the uwb crystal. 167 | POZYX_RANGE_PROTOCOL = 0x21 # The ranging protocol 168 | # Configure the mode of operation of the pozyx device 169 | POZYX_OPERATION_MODE = 0x22 170 | POZYX_SENSORS_MODE = 0x23 # Configure the mode of operation of the sensors 171 | POZYX_CONFIG_BLINK_PAYLOAD = 0x24 # Configure payload that needs to be transmitted with ALOHA 172 | POZYX_ALOHA_VARIATION = 0x26 # Configure the variation on the ALOHA 173 | POZYX_CONFIG_GPIO1 = 0x27 # Configure GPIO pin 1. 174 | POZYX_CONFIG_GPIO2 = 0x28 # Configure GPIO pin 2. 175 | POZYX_CONFIG_GPIO3 = 0x29 # Configure GPIO pin 3. 176 | POZYX_CONFIG_GPIO4 = 0x2A # Configure GPIO pin 4. 177 | 178 | # Positioning data 179 | POZYX_POS_X = 0x30 # x-coordinate of the device in mm. 180 | POZYX_POS_Y = 0x34 # y-coordinate of the device in mm. 181 | POZYX_POS_Z = 0x38 # z-coordinate of the device in mm. 182 | POZYX_POS_ERR_X = 0x3C # estimated error covariance of x 183 | POZYX_POS_ERR_Y = 0x3E # estimated error covariance of y 184 | POZYX_POS_ERR_Z = 0x40 # estimated error covariance of z 185 | POZYX_POS_ERR_XY = 0x42 # estimated covariance of xy 186 | POZYX_POS_ERR_XZ = 0x44 # estimated covariance of xz 187 | POZYX_POS_ERR_YZ = 0x46 # estimated covariance of yz 188 | 189 | # Sensor data 190 | POZYX_MAX_LIN_ACC = 0x4E # Return the max linear acceleration and reset it to 0 191 | POZYX_PRESSURE = 0x50 # Pressure data in mPa 192 | POZYX_ACCEL_X = 0x54 # Accelerometer data (in mg) 193 | POZYX_ACCEL_Y = 0x56 194 | POZYX_ACCEL_Z = 0x58 195 | POZYX_MAGN_X = 0x5A # Magnemtometer data 196 | POZYX_MAGN_Y = 0x5C 197 | POZYX_MAGN_Z = 0x5E 198 | POZYX_GYRO_X = 0x60 # Gyroscope data 199 | POZYX_GYRO_Y = 0x62 200 | POZYX_GYRO_Z = 0x64 201 | # Euler angles heading (or yaw) (1 degree = 16 LSB ) 202 | POZYX_EUL_HEADING = 0x66 203 | POZYX_EUL_ROLL = 0x68 # Euler angles roll ( 1 degree = 16 LSB ) 204 | POZYX_EUL_PITCH = 0x6A # Euler angles pitch ( 1 degree = 16 LSB ) 205 | POZYX_QUAT_W = 0x6C # Weight of quaternion. 206 | POZYX_QUAT_X = 0x6E # x of quaternion 207 | POZYX_QUAT_Y = 0x70 # y of quaternion 208 | POZYX_QUAT_Z = 0x72 # z of quaternion 209 | POZYX_LIA_X = 0x74 # Linear acceleration in x-direction 210 | POZYX_LIA_Y = 0x76 # Linear acceleration in y-direction 211 | POZYX_LIA_Z = 0x78 # Linear acceleration in z-direction 212 | POZYX_GRAV_X = 0x7A # x-component of gravity vector 213 | POZYX_GRAV_Y = 0x7C # y-component of gravity vector 214 | POZYX_GRAV_Z = 0x7E # z-component of gravity vector 215 | POZYX_TEMPERATURE = 0x80 # Temperature 216 | 217 | # General data 218 | # Returns the number of devices stored internally 219 | POZYX_DEVICE_LIST_SIZE = 0x81 220 | POZYX_RX_NETWORK_ID = 0x82 # The network id of the latest received message 221 | POZYX_RX_DATA_LEN = 0x84 # The length of the latest received message 222 | POZYX_GPIO1 = 0x85 # Value of the GPIO pin 1 223 | POZYX_GPIO2 = 0x86 # Value of the GPIO pin 2 224 | POZYX_GPIO3 = 0x87 # Value of the GPIO pin 3 225 | POZYX_GPIO4 = 0x88 # Value of the GPIO pin 4 226 | POZYX_BLINK_INDEX = 0x89 # The blink index of the ALOHA transmissions 227 | 228 | # Functions 229 | POZYX_RESET_SYS = 0xB0 # Reset the Pozyx device 230 | POZYX_LED_CTRL = 0xB1 # Control LEDS 1 to 4 on the board 231 | POZYX_TX_DATA = 0xB2 # Write data in the UWB transmit (TX) buffer 232 | POZYX_TX_SEND = 0xB3 # Transmit the TX buffer to some other pozyx device 233 | POZYX_RX_DATA = 0xB4 # Read data from the UWB receive (RX) buffer 234 | POZYX_DO_RANGING = 0xB5 # Initiate ranging measurement 235 | POZYX_DO_POSITIONING = 0xB6 # Initiate the positioning process. 236 | # Set the list of anchor ID's used for positioning. 237 | POZYX_POS_SET_ANCHOR_IDS = 0xB7 238 | # Read the list of anchor ID's used for positioning. 239 | POZYX_POS_GET_ANCHOR_IDS = 0xB8 240 | POZYX_FLASH_RESET = 0xB9 # Reset a portion of the configuration in flash memory 241 | POZYX_FLASH_SAVE = 0xBA # Store a portion of the configuration in flash memory 242 | POZYX_FLASH_DETAILS = 0xBB # Return information on what is stored in flash 243 | POZYX_DO_ALOHA = 0xBC 244 | 245 | # Device list functions 246 | # Get all the network IDs's of devices in the device list. 247 | POZYX_DEVICES_GETIDS = 0xC0 248 | # Obtain the network ID's of all pozyx devices within range. 249 | POZYX_DEVICES_DISCOVER = 0xC1 250 | # Obtain the coordinates of the pozyx (anchor) devices within range. 251 | POZYX_DEVICES_CALIBRATE = 0xC2 252 | POZYX_DEVICES_CLEAR = 0xC3 # Clear the list of all pozyx devices. 253 | POZYX_DEVICE_ADD = 0xC4 # Add a pozyx device to the devices list 254 | # Get the stored device information for a given pozyx device 255 | POZYX_DEVICE_GETINFO = 0xC5 256 | # Get the stored coordinates of a given pozyx device 257 | POZYX_DEVICE_GETCOORDS = 0xC6 258 | # Get the stored range inforamation of a given pozyx device 259 | POZYX_DEVICE_GETRANGEINFO = 0xC7 260 | POZYX_CIR_DATA = 0xC8 # Get the channel impulse response (CIR) coefficients 261 | -------------------------------------------------------------------------------- /pypozyx/definitions/constants.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | """pypozyx.definitions.constants - contains all Pozyx constants, such as error definitions, delays, physical convertions.""" 3 | 4 | # Pozyx status returns 5 | POZYX_FAILURE = 0x0 6 | POZYX_SUCCESS = 0x1 7 | POZYX_TIMEOUT = 0x8 8 | 9 | 10 | class PozyxConstants: 11 | # Pozyx serial buffer sizes 12 | MAX_BUF_SIZE = 100 13 | MAX_SERIAL_SIZE = 28 14 | 15 | # Remote operations 16 | REMOTE_READ = 0x02 17 | REMOTE_WRITE = 0x04 18 | REMOTE_DATA = 0x06 19 | REMOTE_FUNCTION = 0x08 20 | 21 | # Pozyx delay constants 22 | DELAY_POLLING = 0.002 23 | DELAY_POLLING_FAST = 0.0005 24 | DELAY_LOCAL_WRITE = 0.001 25 | DELAY_LOCAL_FUNCTION = 0.005 26 | DELAY_REMOTE_WRITE = 0.005 27 | DELAY_REMOTE_FUNCTION = 0.01 28 | DELAY_INTERRUPT = 0.1 29 | DELAY_RANGING = 0.2 30 | DELAY_MODE_CHANGE = 0.02 31 | DELAY_FLASH = 0.5 32 | 33 | # Pozyx timeout constants 34 | TIMEOUT_RANGING = 0.025 35 | TIMEOUT_REMOTE_RANGING = 0.1 36 | TIMEOUT_POSITIONING = 0.2 37 | TIMEOUT_REMOTE_POSITIONING = 0.4 38 | TIMEOUT_POSITIONING_DATA = 1.0 39 | TIMEOUT_REMOTE_POSITIONING_DATA = 1.0 40 | TIMEOUT_OPTIMAL_DISCOVERY = 0.1 41 | 42 | # Pozyx status returns 43 | STATUS_FAILURE = 0x0 44 | STATUS_SUCCESS = 0x1 45 | STATUS_TIMEOUT = 0x8 46 | 47 | # Pozyx interrupt pin 48 | INT_PIN0 = 0x0 49 | INT_PIN1 = 0x1 50 | 51 | # Pozyx led control indexes 52 | LED_CTRL_LED_RX = 0x10 53 | LED_CTRL_LED_TX = 0x20 54 | LED_ON = True 55 | LED_OFF = False 56 | 57 | # Pozyx device modes 58 | TAG_MODE = 0 59 | ANCHOR_MODE = 1 60 | ALOHA_MODE = 2 61 | 62 | # The GPIO modes 63 | GPIO_DIGITAL_INPUT = 0 64 | GPIO_PUSH_PULL = 1 65 | GPIO_OPEN_DRAIN = 1 66 | 67 | ALL_GPIO_MODES = [GPIO_DIGITAL_INPUT, GPIO_PUSH_PULL, GPIO_OPEN_DRAIN] 68 | 69 | # The GPIO pull resistor configuration 70 | GPIO_NO_PULL = 0 71 | GPIO_PULL_UP = 1 72 | GPIO_PULL_DOWN = 2 73 | 74 | ALL_GPIO_PULLS = [GPIO_NO_PULL, GPIO_PULL_UP, GPIO_PULL_DOWN] 75 | 76 | # anchor selection modes 77 | ANCHOR_SELECT_MANUAL = 0 78 | ANCHOR_SELECT_AUTO = 1 79 | 80 | # discovery options 81 | DISCOVERY_ANCHORS_ONLY = 0 82 | DISCOVERY_TAGS_ONLY = 1 83 | DISCOVERY_ALL_DEVICES = 2 84 | 85 | DISCOVERY_TYPES = [DISCOVERY_ALL_DEVICES, DISCOVERY_ANCHORS_ONLY, DISCOVERY_TAGS_ONLY] 86 | 87 | # Pozyx positioning dimensions 88 | DIMENSION_3D = 3 89 | DIMENSION_2D = 2 90 | DIMENSION_2_5D = 1 91 | 92 | DIMENSIONS = [DIMENSION_3D, DIMENSION_2D, DIMENSION_2_5D] 93 | 94 | # positioning algorithm options 95 | POSITIONING_ALGORITHM_UWB_ONLY = 0 96 | POSITIONING_ALGORITHM_TRACKING = 4 97 | POSITIONING_ALGORITHM_NONE = 3 98 | 99 | POSITIONING_ALGORITHMS = [POSITIONING_ALGORITHM_UWB_ONLY, POSITIONING_ALGORITHM_TRACKING, POSITIONING_ALGORITHM_NONE] 100 | 101 | # ranging protocol options 102 | RANGE_PROTOCOL_PRECISION = 0x00 103 | RANGE_PROTOCOL_FAST = 0x01 104 | RANGE_PROTOCOL_TEST = 0x02 105 | 106 | RANGING_PROTOCOLS = [RANGE_PROTOCOL_PRECISION, RANGE_PROTOCOL_FAST] 107 | 108 | # positioning filters 109 | FILTER_TYPE_NONE = 0 110 | FILTER_TYPE_FIR = 1 111 | FILTER_TYPE_MOVING_AVERAGE = 3 112 | FILTER_TYPE_MOVING_MEDIAN = 4 113 | 114 | FILTER_TYPES = [FILTER_TYPE_NONE, FILTER_TYPE_FIR, FILTER_TYPE_MOVING_AVERAGE, FILTER_TYPE_MOVING_MEDIAN] 115 | 116 | # how to intercept pozyx events: by polling or by interrupts 117 | MODE_POLLING = 0 118 | MODE_INTERRUPT = 1 119 | 120 | # Division factors for converting the raw register values to meaningful 121 | # physical quantities 122 | POSITION_DIV_MM = 1.0 123 | PRESSURE_DIV_PA = 1000.0 124 | MAX_LINEAR_ACCELERATION_DIV_MG = 1.0 125 | ACCELERATION_DIV_MG = 1.0 126 | GYRO_DIV_DPS = 16.0 127 | MAGNETOMETER_DIV_UT = 16.0 128 | EULER_ANGLES_DIV_DEG = 16.0 129 | QUATERNION_DIV = 16384.0 130 | TEMPERATURE_DIV_CELSIUS = 1.0 131 | 132 | # flash configuration types 133 | FLASH_SAVE_REGISTERS = 1 134 | FLASH_SAVE_ANCHOR_IDS = 2 135 | FLASH_SAVE_NETWORK = 3 136 | FLASH_SAVE_ALL = 4 137 | 138 | ALL_FLASH_SAVE_TYPES = [FLASH_SAVE_REGISTERS, FLASH_SAVE_ANCHOR_IDS, FLASH_SAVE_NETWORK, FLASH_SAVE_ALL] 139 | 140 | # possible pin configuration settings 141 | INTERRUPT_CONFIG = 0x24 142 | PIN_MODE_PUSH_PULL = 0 143 | PIN_MODE_OPEN_DRAIN = 1 144 | 145 | # Possible pin activity states 146 | PIN_ACTIVE_LOW = 0 147 | PIN_ACTIVE_HIGH = 1 148 | 149 | # Possible UWB settings 150 | 151 | UWB_BITRATE_110_KBPS = 0 152 | UWB_BITRATE_850_KBPS = 1 153 | UWB_BITRATE_6810_KBPS = 2 154 | 155 | UWB_PRF_16_MHZ = 1 156 | UWB_PRF_64_MHZ = 2 157 | 158 | UWB_PLEN_64 = 0x04 159 | UWB_PLEN_128 = 0x14 160 | UWB_PLEN_256 = 0x24 161 | UWB_PLEN_512 = 0x34 162 | UWB_PLEN_1024 = 0x08 163 | UWB_PLEN_1536 = 0x18 164 | UWB_PLEN_2048 = 0x28 165 | UWB_PLEN_4096 = 0x0C 166 | 167 | ALL_UWB_CHANNELS = [1, 2, 3, 4, 5, 7] 168 | 169 | ALL_UWB_BITRATES = [ 170 | UWB_BITRATE_110_KBPS, 171 | UWB_BITRATE_850_KBPS, 172 | UWB_BITRATE_6810_KBPS, 173 | ] 174 | 175 | ALL_UWB_PRFS = [ 176 | UWB_PRF_16_MHZ, 177 | UWB_PRF_64_MHZ, 178 | ] 179 | 180 | ALL_UWB_PLENS = [ 181 | UWB_PLEN_64, 182 | UWB_PLEN_128, 183 | UWB_PLEN_256, 184 | UWB_PLEN_512, 185 | UWB_PLEN_1024, 186 | UWB_PLEN_1536, 187 | UWB_PLEN_2048, 188 | UWB_PLEN_4096, 189 | ] 190 | 191 | # ALOHA send types 192 | ALOHA_SEND_CUSTOM_QUEUED = 0 193 | ALOHA_SEND_CUSTOM_IMMEDIATE = 1 194 | ALOHA_SEND_IMMEDIATE = 2 195 | 196 | ALL_ALOHA_TYPES = [ALOHA_SEND_CUSTOM_QUEUED, ALOHA_SEND_CUSTOM_IMMEDIATE, ALOHA_SEND_IMMEDIATE] 197 | 198 | 199 | # Pozyx firmware identifiers 200 | POZYX_FW_MAJOR = 0xF0 201 | POZYX_FW_MINOR = 0xF 202 | 203 | # Pozyx device identifier for hardware 204 | POZYX_ANCHOR = 0x00 205 | POZYX_TAG = 0x20 206 | 207 | # Pozyx serial buffer sizes 208 | MAX_BUF_SIZE = 100 209 | MAX_SERIAL_SIZE = 28 210 | 211 | # Pozyx delay constants 212 | POZYX_DELAY_POLLING = 0.001 213 | POZYX_DELAY_LOCAL_WRITE = 0.001 214 | POZYX_DELAY_LOCAL_FUNCTION = 0.005 215 | POZYX_DELAY_REMOTE_WRITE = 0.005 216 | POZYX_DELAY_REMOTE_FUNCTION = 0.01 217 | POZYX_DELAY_INTERRUPT = 0.1 218 | POZYX_DELAY_CALIBRATION = 1 219 | POZYX_DELAY_MODE_CHANGE = 0.02 220 | POZYX_DELAY_RANGING = 0.025 221 | POZYX_DELAY_REMOTE_RANGING = 0.1 222 | POZYX_DELAY_POSITIONING = 0.2 223 | POZYX_DELAY_REMOTE_POSITIONING = 0.4 224 | POZYX_DELAY_FLASH = 0.5 225 | 226 | # Pozyx positioning dimensions 227 | POZYX_3D = 3 228 | POZYX_2D = 2 229 | POZYX_2_5D = 1 230 | 231 | # Pozyx interrupt pin 232 | POZYX_INT_PIN0 = 0x0 233 | POZYX_INT_PIN1 = 0x1 234 | 235 | # Pozyx led control indexes 236 | POZYX_LED_CTRL_LEDRX = 0x10 237 | POZYX_LED_CTRL_LEDTX = 0x20 238 | POZYX_LED_ON = True 239 | POZYX_LED_OFF = False 240 | 241 | # Pozyx device modes 242 | POZYX_ANCHOR_MODE = 0 243 | POZYX_TAG_MODE = 1 244 | 245 | # The GPIO modes 246 | POZYX_GPIO_DIGITAL_INPUT = 0 247 | POZYX_GPIO_PUSHPULL = 1 248 | POZYX_GPIO_OPENDRAIN = 1 249 | 250 | # The GPIO pull resistor configuration 251 | POZYX_GPIO_NOPULL = 0 252 | POZYX_GPIO_PULLUP = 1 253 | POZYX_GPIO_PULLDOWN = 2 254 | 255 | # anchor selection modes 256 | POZYX_ANCHOR_SEL_MANUAL = 0 257 | POZYX_ANCHOR_SEL_AUTO = 1 258 | 259 | # discovery options 260 | POZYX_DISCOVERY_ANCHORS_ONLY = 0 261 | POZYX_DISCOVERY_TAGS_ONLY = 1 262 | POZYX_DISCOVERY_ALL_DEVICES = 2 263 | 264 | # positioning algorithm options 265 | POZYX_POS_ALG_UWB_ONLY = 0 266 | POZYX_POS_ALG_TRACKING = 4 267 | 268 | # ranging protocol options 269 | POZYX_RANGE_PROTOCOL_PRECISION = 0x00 270 | POZYX_RANGE_PROTOCOL_FAST = 0x01 271 | POZYX_RANGE_PROTOCOL_TEST = 0x02 272 | 273 | # positioning filters 274 | FILTER_TYPE_NONE = 0 275 | FILTER_TYPE_FIR = 1 276 | FILTER_TYPE_MOVINGAVERAGE = 3 277 | FILTER_TYPE_MOVINGMEDIAN = 4 278 | 279 | # how to intercept pozyx events: by polling or by interrupts 280 | MODE_POLLING = 0 281 | MODE_INTERRUPT = 1 282 | 283 | # Division factors for converting the raw register values to meaningful 284 | # physical quantities 285 | POZYX_POS_DIV_MM = 1.0 286 | POZYX_PRESS_DIV_PA = 1000.0 287 | POZYX_MAX_LIN_ACCEL_DIV_MG = 1.0 288 | POZYX_ACCEL_DIV_MG = 1.0 289 | POZYX_GYRO_DIV_DPS = 16.0 290 | POZYX_MAG_DIV_UT = 16.0 291 | POZYX_EULER_DIV_DEG = 16.0 292 | POZYX_QUAT_DIV = 16384.0 293 | POZYX_TEMP_DIV_CELSIUS = 1.0 294 | 295 | # flash configuration types 296 | POZYX_FLASH_REGS = 1 297 | POZYX_FLASH_ANCHOR_IDS = 2 298 | POZYX_FLASH_NETWORK = 3 299 | POZYX_FLASH_ALL = 4 300 | 301 | # ALOHA send types 302 | POZYX_QUEUE_CUSTOM_ALOHA = 0 303 | POZYX_SEND_CUSTOM_ALOHA_IMMEDIATE = 1 304 | POZYX_SEND_ALOMA_IMMEDIATE = 2 305 | 306 | # possible pin configuration settings 307 | POZYX_INT_CONFIG = 0x24 308 | PIN_MODE_PUSHPULL = 0 309 | PIN_MODE_OPENDRAIN = 1 310 | 311 | PIN_ACTIVE_LOW = 0 312 | PIN_ACTIVE_HIGH = 1 313 | 314 | POZYX_ALL_CHANNELS = [1, 2, 3, 4, 5, 7] 315 | POZYX_ALL_BITRATES = [0, 1, 2] 316 | POZYX_ALL_PRFS = [1, 2] 317 | POZYX_ALL_PLENS = [0x04, 0x14, 0x24, 0x34, 0x08, 0x18, 0x28, 0x0C] 318 | 319 | class PozyxErrorCodes: 320 | POZYX_ERROR_NONE = 0x00 321 | POZYX_ERROR_I2C_WRITE = 0x01 322 | POZYX_ERROR_I2C_CMDFULL = 0x02 323 | POZYX_ERROR_ANCHOR_ADD = 0x03 324 | POZYX_ERROR_COMM_QUEUE_FULL = 0x04 325 | POZYX_ERROR_I2C_READ = 0x05 326 | POZYX_ERROR_UWB_CONFIG = 0x06 327 | POZYX_ERROR_OPERATION_QUEUE_FULL = 0x07 328 | POZYX_ERROR_TDMA = 0xA0 329 | POZYX_ERROR_STARTUP_BUSFAULT = 0x08 330 | POZYX_ERROR_FLASH_INVALID = 0x09 331 | POZYX_ERROR_NOT_ENOUGH_ANCHORS = 0x0A 332 | POZYX_ERROR_DISCOVERY = 0X0B 333 | POZYX_ERROR_CALIBRATION = 0x0C 334 | POZYX_ERROR_FUNC_PARAM = 0x0D 335 | POZYX_ERROR_ANCHOR_NOT_FOUND = 0x0E 336 | POZYX_ERROR_FLASH = 0x0F 337 | POZYX_ERROR_MEMORY = 0x10 338 | POZYX_ERROR_RANGING = 0x11 339 | POZYX_ERROR_RTIMEOUT1 = 0x12 340 | POZYX_ERROR_RTIMEOUT2 = 0x13 341 | POZYX_ERROR_TXLATE = 0x14 342 | POZYX_ERROR_UWB_BUSY = 0x15 343 | POZYX_ERROR_POSALG = 0x16 344 | POZYX_ERROR_NOACK = 0x17 345 | POZYX_ERROR_SNIFF_OVERFLOW = 0xE0 346 | POZYX_ERROR_NO_PPS = 0xF0 347 | POZYX_ERROR_NEW_TASK = 0xF1 348 | POZYX_ERROR_UNRECDEV = 0xFE 349 | POZYX_ERROR_GENERAL = 0xFF 350 | 351 | # error-code definitions 352 | POZYX_ERROR_NONE = 0x00 353 | POZYX_ERROR_I2C_WRITE = 0x01 354 | POZYX_ERROR_I2C_CMDFULL = 0x02 355 | POZYX_ERROR_ANCHOR_ADD = 0x03 356 | POZYX_ERROR_COMM_QUEUE_FULL = 0x04 357 | POZYX_ERROR_I2C_READ = 0x05 358 | POZYX_ERROR_UWB_CONFIG = 0x06 359 | POZYX_ERROR_OPERATION_QUEUE_FULL = 0x07 360 | POZYX_ERROR_TDMA = 0xA0 361 | POZYX_ERROR_STARTUP_BUSFAULT = 0x08 362 | POZYX_ERROR_FLASH_INVALID = 0x09 363 | POZYX_ERROR_NOT_ENOUGH_ANCHORS = 0x0A 364 | POZYX_ERROR_DISCOVERY = 0X0B 365 | POZYX_ERROR_CALIBRATION = 0x0C 366 | POZYX_ERROR_FUNC_PARAM = 0x0D 367 | POZYX_ERROR_ANCHOR_NOT_FOUND = 0x0E 368 | POZYX_ERROR_FLASH = 0x0F 369 | POZYX_ERROR_MEMORY = 0x10 370 | POZYX_ERROR_RANGING = 0x11 371 | POZYX_ERROR_RTIMEOUT1 = 0x12 372 | POZYX_ERROR_RTIMEOUT2 = 0x13 373 | POZYX_ERROR_TXLATE = 0x14 374 | POZYX_ERROR_UWB_BUSY = 0x15 375 | POZYX_ERROR_POSALG = 0x16 376 | POZYX_ERROR_NOACK = 0x17 377 | POZYX_ERROR_SNIFF_OVERFLOW = 0xE0 378 | POZYX_ERROR_NO_PPS = 0xF0 379 | POZYX_ERROR_NEW_TASK = 0xF1 380 | POZYX_ERROR_UNRECDEV = 0xFE 381 | POZYX_ERROR_GENERAL = 0xFF 382 | 383 | ERROR_CODES = { 384 | PozyxErrorCodes.POZYX_ERROR_NONE: "NO ERROR", 385 | PozyxErrorCodes.POZYX_ERROR_I2C_WRITE: "ERROR 0x01: Error writing to a register through the I2C bus", 386 | PozyxErrorCodes.POZYX_ERROR_I2C_CMDFULL: "ERROR 0x02: Pozyx cannot handle all the I2C commands at once", 387 | PozyxErrorCodes.POZYX_ERROR_ANCHOR_ADD: "ERROR 0x03: Cannot add anchor to the internal device list", 388 | PozyxErrorCodes.POZYX_ERROR_COMM_QUEUE_FULL: "ERROR 0x04: Communication queue is full, too many UWB messages", 389 | PozyxErrorCodes.POZYX_ERROR_I2C_READ: "ERROR 0x05: Error reading from a register from the I2C bus", 390 | PozyxErrorCodes.POZYX_ERROR_UWB_CONFIG: "ERROR 0x06: Cannot change the UWB configuration", 391 | PozyxErrorCodes.POZYX_ERROR_OPERATION_QUEUE_FULL: "ERROR 0x07: Pozyx cannot handle all the operations at once", 392 | PozyxErrorCodes.POZYX_ERROR_STARTUP_BUSFAULT: "ERROR 0x08: Internal bus error", 393 | PozyxErrorCodes.POZYX_ERROR_FLASH_INVALID: "ERROR 0x09: Flash memory is corrupted or invalid", 394 | PozyxErrorCodes.POZYX_ERROR_NOT_ENOUGH_ANCHORS: "ERROR 0x0A: Not enough anchors available for positioning", 395 | PozyxErrorCodes.POZYX_ERROR_DISCOVERY: "ERROR 0x0B: Error during the Discovery process", 396 | PozyxErrorCodes.POZYX_ERROR_CALIBRATION: "ERROR 0x0C: Error during the auto calibration process", 397 | PozyxErrorCodes.POZYX_ERROR_FUNC_PARAM: "ERROR 0x0D: Invalid function parameters for the register function", 398 | PozyxErrorCodes.POZYX_ERROR_ANCHOR_NOT_FOUND: "ERROR 0x0E: The coordinates of an anchor are not found", 399 | PozyxErrorCodes.POZYX_ERROR_FLASH: "ERROR 0x0F: Flash error", 400 | PozyxErrorCodes.POZYX_ERROR_MEMORY: "ERROR 0x10: Memory error", 401 | PozyxErrorCodes.POZYX_ERROR_RANGING: "ERROR 0x11: Ranging failed", 402 | PozyxErrorCodes.POZYX_ERROR_RTIMEOUT1: "ERROR 0x12: Ranging timeout", 403 | PozyxErrorCodes.POZYX_ERROR_RTIMEOUT2: "ERROR 0x13: Ranging timeout", 404 | PozyxErrorCodes.POZYX_ERROR_TXLATE: "ERROR 0x14: Tx was late", 405 | PozyxErrorCodes.POZYX_ERROR_UWB_BUSY: "ERROR 0x15: UWB is busy", 406 | PozyxErrorCodes.POZYX_ERROR_POSALG: "ERROR 0x16: Positioning failed", 407 | PozyxErrorCodes.POZYX_ERROR_NOACK: "ERROR 0x17: No Acknowledge received", 408 | PozyxErrorCodes.POZYX_ERROR_NEW_TASK: "ERROR 0xF1: Cannot create task", 409 | PozyxErrorCodes.POZYX_ERROR_UNRECDEV: "ERROR 0xFE: Hardware not recognized. Please contact support@pozyx.io", 410 | PozyxErrorCodes.POZYX_ERROR_GENERAL: "ERROR 0xFF: General error", 411 | } 412 | 413 | ERROR_MESSAGES = ERROR_CODES 414 | -------------------------------------------------------------------------------- /pypozyx/structures/sensor_data.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | """pypozyx.structures.sensor_data - Contains container classes for data from the Pozyx's many sensors.""" 4 | from math import sqrt 5 | 6 | from pypozyx.definitions.constants import PozyxConstants 7 | from pypozyx.structures.byte_structure import ByteStructure 8 | from pypozyx.structures.generic import XYZ, SingleSensorValue, SingleRegister 9 | 10 | 11 | class Coordinates(XYZ): 12 | """Container for coordinates in x, y, and z (in mm).""" 13 | byte_size = 12 14 | data_format = 'iii' 15 | 16 | def load(self, data, convert=False): 17 | self.data = data 18 | 19 | 20 | class Acceleration(XYZ): 21 | """Container for acceleration in x, y, and z (in mg).""" 22 | physical_convert = PozyxConstants.ACCELERATION_DIV_MG 23 | 24 | byte_size = 6 25 | data_format = 'hhh' 26 | 27 | 28 | class Magnetic(XYZ): 29 | """Container for coordinates in x, y, and z (in uT).""" 30 | physical_convert = PozyxConstants.MAGNETOMETER_DIV_UT 31 | 32 | byte_size = 6 33 | data_format = 'hhh' 34 | 35 | 36 | class AngularVelocity(XYZ): 37 | """Container for angular velocity in x, y, and z (in dps).""" 38 | physical_convert = PozyxConstants.GYRO_DIV_DPS 39 | 40 | byte_size = 6 41 | data_format = 'hhh' 42 | 43 | 44 | class LinearAcceleration(XYZ): 45 | """Container for linear acceleration in x, y, and z (in mg), as floats.""" 46 | physical_convert = PozyxConstants.ACCELERATION_DIV_MG 47 | 48 | byte_size = 6 49 | data_format = 'hhh' 50 | 51 | 52 | class PositionError(ByteStructure): 53 | """Container for position error in x, y, z, xy, xz, and yz (in mm).""" 54 | physical_convert = 1 55 | byte_size = 12 56 | data_format = 'hhhhhh' 57 | 58 | def __init__(self, x=0, y=0, z=0, xy=0, xz=0, yz=0): 59 | """Initializes the PositionError object.""" 60 | self.data = [x, y, z, xy, xz, yz] 61 | 62 | def load(self, data, convert=False): 63 | self.data = data 64 | 65 | def __str__(self): 66 | return 'X: {}, Y: {}, Z: {}, XY: {}, XZ: {}, YZ: {}'.format(self.x, self.y, self.z, self.xy, self.xz, self.yz) 67 | 68 | @property 69 | def x(self): 70 | return self.data[0] / self.physical_convert 71 | 72 | @x.setter 73 | def x(self, value): 74 | self.data[0] = value * self.physical_convert 75 | 76 | @property 77 | def y(self): 78 | return self.data[1] / self.physical_convert 79 | 80 | @y.setter 81 | def y(self, value): 82 | self.data[1] = value * self.physical_convert 83 | 84 | @property 85 | def z(self): 86 | return self.data[2] / self.physical_convert 87 | 88 | @z.setter 89 | def z(self, value): 90 | self.data[2] = value * self.physical_convert 91 | 92 | @property 93 | def xy(self): 94 | return self.data[3] / self.physical_convert 95 | 96 | @xy.setter 97 | def xy(self, value): 98 | self.data[3] = value * self.physical_convert 99 | 100 | @property 101 | def xz(self): 102 | return self.data[4] / self.physical_convert 103 | 104 | @xz.setter 105 | def xz(self, value): 106 | self.data[4] = value * self.physical_convert 107 | 108 | @property 109 | def yz(self): 110 | return self.data[5] / self.physical_convert 111 | 112 | @yz.setter 113 | def yz(self, value): 114 | self.data[5] = value * self.physical_convert 115 | 116 | 117 | class Quaternion(ByteStructure): 118 | """Container for quaternion data in x, y, z and w.""" 119 | physical_convert = PozyxConstants.QUATERNION_DIV 120 | 121 | byte_size = 8 122 | data_format = 'hhhh' 123 | 124 | def __init__(self, w=0, x=0, y=0, z=0): 125 | """Initializes the Quaternion object.""" 126 | self.data = [w, x, y, z] 127 | 128 | def load(self, data, convert=True): 129 | for i in range(len(data)): 130 | data[i] = float(data[i]) 131 | self.data = data 132 | 133 | def get_sum(self): 134 | """Returns the normalization value of the quaternion""" 135 | return sqrt((self.x * self.x) + (self.y * self.y) + (self.z * self.z) + (self.w * self.w)) 136 | 137 | def normalize(self): 138 | """Normalizes the quaternion's data""" 139 | sum = self.get_sum() 140 | for i in range(len(self.data)): 141 | self.data[i] /= sum 142 | 143 | def __str__(self): 144 | return 'X: {self.x}, Y: {self.y}, Z: {self.z}, W: {self.w}'.format(self=self) 145 | 146 | @property 147 | def w(self): 148 | return self.data[0] / self.physical_convert 149 | 150 | @w.setter 151 | def w(self, value): 152 | self.data[0] = value * self.physical_convert 153 | 154 | @property 155 | def x(self): 156 | return self.data[1] / self.physical_convert 157 | 158 | @x.setter 159 | def x(self, value): 160 | self.data[1] = value * self.physical_convert 161 | 162 | @property 163 | def y(self): 164 | return self.data[2] / self.physical_convert 165 | 166 | @y.setter 167 | def y(self, value): 168 | self.data[2] = value * self.physical_convert 169 | 170 | @property 171 | def z(self): 172 | return self.data[3] / self.physical_convert 173 | 174 | @z.setter 175 | def z(self, value): 176 | self.data[3] = value * self.physical_convert 177 | 178 | def to_dict(self): 179 | return { 180 | "x": self.x, 181 | "y": self.y, 182 | "z": self.z, 183 | "w": self.w, 184 | } 185 | 186 | 187 | class MaxLinearAcceleration(SingleSensorValue): 188 | physical_convert = PozyxConstants.MAX_LINEAR_ACCELERATION_DIV_MG 189 | 190 | byte_size = 2 191 | data_format = 'h' 192 | 193 | 194 | class Temperature(SingleSensorValue): 195 | physical_convert = PozyxConstants.TEMPERATURE_DIV_CELSIUS 196 | 197 | byte_size = 1 198 | data_format = 'b' 199 | 200 | def __str__(self): 201 | return "{self.value} °C" 202 | 203 | 204 | class Pressure(SingleSensorValue): 205 | physical_convert = PozyxConstants.PRESSURE_DIV_PA 206 | 207 | byte_size = 4 208 | data_format = 'I' 209 | 210 | 211 | class EulerAngles(ByteStructure): 212 | """Container for euler angles as heading, roll, and pitch (in degrees).""" 213 | physical_convert = PozyxConstants.EULER_ANGLES_DIV_DEG 214 | 215 | byte_size = 6 216 | data_format = 'hhh' 217 | 218 | def __init__(self, heading=0, roll=0, pitch=0): 219 | """Initializes the EulerAngles object.""" 220 | self.data = [heading, roll, pitch] 221 | 222 | def load(self, data, convert=True): 223 | self.data = data 224 | 225 | def __str__(self): 226 | return 'Heading: {}, Roll: {}, Pitch: {}'.format(self.heading, self.roll, self.pitch) 227 | 228 | @property 229 | def heading(self): 230 | return self.data[0] / self.physical_convert 231 | 232 | @heading.setter 233 | def heading(self, value): 234 | self.data[0] = value * self.physical_convert 235 | 236 | @property 237 | def roll(self): 238 | return self.data[1] / self.physical_convert 239 | 240 | @roll.setter 241 | def roll(self, value): 242 | self.data[1] = value * self.physical_convert 243 | 244 | @property 245 | def pitch(self): 246 | return self.data[2] / self.physical_convert 247 | 248 | @pitch.setter 249 | def pitch(self, value): 250 | self.data[2] = value * self.physical_convert 251 | 252 | 253 | class SensorData(ByteStructure): 254 | """ 255 | Container for all sensor data. 256 | 257 | This includes, in order, with respective structure: 258 | - pressure : UInt32 259 | - acceleration : Acceleration 260 | - magnetic : Magnetic 261 | - angular_vel : AngularVelocity 262 | - euler_angles : EulerAngles 263 | - quaternion : Quaternion 264 | - linear_acceleration: LinearAcceleration 265 | - gravity_vector: LinearAcceleration 266 | - temperature: Int8 267 | """ 268 | byte_size = 49 # 4 + 6 + 6 + 6 + 6 + 8 + 6 + 6 + 1 269 | 270 | # 'I' + 'h'*3 + 'h'*3 + 'h'*3 + 'h'*3 + 'f'*4 + 'h'*3 + 'h'*3 + 'b' 271 | data_format = 'Ihhhhhhhhhhhhhhhhhhhhhhb' 272 | 273 | def __init__(self, data=[0] * 24): 274 | """Initializes the SensorData object.""" 275 | self.data = data 276 | self.pressure = Pressure(data[0]) 277 | self.acceleration = Acceleration(data[1], data[2], data[3]) 278 | self.magnetic = Magnetic(data[4], data[5], data[6]) 279 | self.angular_vel = AngularVelocity(data[7], data[8], data[9]) 280 | self.euler_angles = EulerAngles(data[10], data[11], data[12]) 281 | self.quaternion = Quaternion(data[13], data[14], data[15], data[16]) 282 | self.linear_acceleration = LinearAcceleration( 283 | data[17], data[18], data[19]) 284 | self.gravity_vector = LinearAcceleration(data[20], data[21], data[22]) 285 | self.temperature = Temperature(data[23]) 286 | 287 | def load(self, data, convert=True): 288 | self.data = data 289 | self.pressure.load([data[0]], convert) 290 | self.acceleration.load(data[1:4], convert) 291 | self.magnetic.load(data[4:7], convert) 292 | self.angular_vel.load(data[7:10], convert) 293 | self.euler_angles.load(data[10:13], convert) 294 | self.quaternion.load(data[13:17], convert) 295 | self.linear_acceleration.load(data[17:20], convert) 296 | self.gravity_vector.load(data[20:23], convert) 297 | self.temperature.load([data[23]], convert) 298 | 299 | 300 | class RawSensorData(SensorData): 301 | """Container for raw sensor data 302 | 303 | This includes, in order, with respective structure: 304 | - pressure : UInt32 305 | - acceleration : Acceleration 306 | - magnetic : Magnetic 307 | - angular_vel : AngularVelocity 308 | - euler_angles : EulerAngles 309 | - quaternion : Quaternion 310 | - linear_acceleration: LinearAcceleration 311 | - gravity_vector: LinearAcceleration 312 | - temperature: Int8 313 | """ 314 | 315 | def __init__(self, data=None): 316 | """Initializes the RawSensorData object""" 317 | data = [0] * 24 if data is None else data 318 | SensorData.__init__(self, data) 319 | 320 | def load(self, data, convert=False): 321 | SensorData.load(self, data, convert=False) 322 | 323 | 324 | class CoordinatesWithStatus(ByteStructure): 325 | """Container for coordinates in x, y, and z (in mm).""" 326 | byte_size = 13 327 | data_format = 'iiiB' 328 | 329 | def __init__(self, x=0, y=0, z=0, status=0): 330 | """Initializes the XYZ or XYZ-derived object.""" 331 | self.data = [x, y, z, status] 332 | 333 | def load(self, data, convert=False): 334 | self.data = data 335 | 336 | def __str__(self): 337 | return 'STATUS: {}, X: {}, Y: {}, Z: {}'.format(self.status, self.x, self.y, self.z) 338 | 339 | @property 340 | def x(self): 341 | return self.data[0] 342 | 343 | @x.setter 344 | def x(self, value): 345 | self.data[0] = value 346 | 347 | @property 348 | def y(self): 349 | return self.data[1] 350 | 351 | @y.setter 352 | def y(self, value): 353 | self.data[1] = value 354 | 355 | @property 356 | def z(self): 357 | return self.data[2] 358 | 359 | @z.setter 360 | def z(self, value): 361 | self.data[2] = value 362 | 363 | @property 364 | def status(self): 365 | return self.data[3] 366 | 367 | @status.setter 368 | def status(self, value): 369 | self.data[3] = value 370 | 371 | def to_dict(self): 372 | return { 373 | "x": self.x, 374 | "y": self.y, 375 | "z": self.z, 376 | } 377 | 378 | 379 | class RangeInformation(ByteStructure): 380 | byte_size = 6 381 | data_format = 'HI' 382 | 383 | def __init__(self, device_id=0, distance=0): 384 | self.data = [device_id, distance] 385 | 386 | def load(self, data, convert=0): 387 | self.data = data 388 | 389 | def __str__(self): 390 | return "0x{:04x}: {} mm".format(self.device_id, self.distance) 391 | 392 | @property 393 | def device_id(self): 394 | return self.data[0] 395 | 396 | @device_id.setter 397 | def device_id(self, value): 398 | self.data[0] = value 399 | 400 | @property 401 | def distance(self): 402 | return self.data[1] 403 | 404 | @distance.setter 405 | def distance(self, value): 406 | self.data[1] = value 407 | 408 | 409 | class PositioningData(ByteStructure): 410 | SENSOR_ORDER = [CoordinatesWithStatus, Acceleration, AngularVelocity, Magnetic, EulerAngles, Quaternion, 411 | LinearAcceleration, Acceleration, Pressure, MaxLinearAcceleration] 412 | 413 | def __init__(self, flags): 414 | self.data = [0] 415 | self.flags = flags 416 | self.containers = [] 417 | self.byte_size = 0 418 | self.data_format = '' 419 | self.amount_of_ranges = 0 420 | 421 | self.set_data_structures() 422 | 423 | def load(self, data, convert=0): 424 | self.data = data 425 | data_index = 0 426 | for container in self.containers: 427 | data_length = len(container.data_format) 428 | container.load(data[data_index:data_index + data_length]) 429 | data_index += data_length 430 | 431 | def add_sensor(self, sensor_class): 432 | self.byte_size += sensor_class.byte_size 433 | self.data_format += sensor_class.data_format 434 | self.containers.append(sensor_class()) 435 | 436 | def set_data_structures(self): 437 | self.containers = [] 438 | self.byte_size = 0 439 | self.data_format = '' 440 | 441 | for index, sensor in enumerate(self.SENSOR_ORDER): 442 | if self.flags & (1 << index): 443 | self.add_sensor(sensor) 444 | 445 | if self.has_ranges(): 446 | self.byte_size += 1 447 | self.data_format += 'B' 448 | self.containers.append(SingleRegister()) 449 | 450 | def set_amount_of_ranges(self, amount_of_ranges): 451 | self.amount_of_ranges = amount_of_ranges 452 | if self.has_ranges(): 453 | for i in range(amount_of_ranges): 454 | self.add_sensor(RangeInformation) 455 | 456 | def has_ranges(self): 457 | return self.flags & (1 << 15) 458 | 459 | def load_bytes(self, byte_data): 460 | """Loads a hex byte array in the structure's data""" 461 | self.byte_data = byte_data 462 | self.bytes_to_data() 463 | 464 | @property 465 | def number_of_ranges(self): 466 | if self.has_ranges(): 467 | return self.amount_of_ranges 468 | else: 469 | # TODO right error? 470 | raise ValueError("Ranges not given as a flag") 471 | 472 | # TODO 473 | def __str__(self): 474 | return 'TODO' 475 | -------------------------------------------------------------------------------- /pypozyx/structures/device.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | """ 3 | pypozyx.structures.device - contains various classes representing device data 4 | 5 | Structures contained 6 | -------------------- 7 | DeviceCoordinates 8 | consists of a device's ID, flag, and coordinates 9 | DeviceRange 10 | consists of a range measurements timestamp, distance, and RSS 11 | NetworkID 12 | container for a device's ID. Prints in 0xID format. 13 | DeviceList 14 | container for a list of IDs. Can be initialized through size and/or IDs. 15 | UWBSettings 16 | contains all of the UWB settings: channel, bitrate, prf, plen, and gain. 17 | """ 18 | 19 | from pypozyx.definitions.constants import PozyxConstants 20 | from pypozyx.structures.byte_structure import ByteStructure 21 | from pypozyx.structures.generic import Data, SingleRegister 22 | from pypozyx.structures.sensor_data import Coordinates 23 | 24 | 25 | class DeviceCoordinates(ByteStructure): 26 | """ 27 | Container for both reading and writing device coordinates from and to Pozyx. 28 | 29 | The keyword arguments are at once its properties. 30 | 31 | Kwargs: 32 | network_id: Network ID of the device 33 | flag: Type of the device. Tag or anchor. 34 | pos: Coordinates of the device. Coordinates(). 35 | """ 36 | byte_size = 15 37 | data_format = 'HBiii' 38 | 39 | def __init__(self, network_id=0, flag=0, pos=Coordinates()): 40 | """ 41 | Initializes the DeviceCoordinates object. 42 | 43 | Kwargs: 44 | network_id: Network ID of the device 45 | flag: Type of the device. Tag or anchor. 46 | pos: Coordinates of the device. Coordinates(). 47 | """ 48 | self.data = [network_id, flag, int(pos.x), int(pos.y), int(pos.z)] 49 | 50 | def load(self, data): 51 | self.data = data 52 | 53 | def __str__(self): 54 | return "ID: 0x{:04X}, flag: {}, ".format(self.network_id, self.flag) + str(self.pos) 55 | 56 | @property 57 | def network_id(self): 58 | return self.data[0] 59 | 60 | @network_id.setter 61 | def network_id(self, value): 62 | self.data[0] = value 63 | 64 | @property 65 | def flag(self): 66 | return self.data[1] 67 | 68 | @flag.setter 69 | def flag(self, value): 70 | self.data[1] = value 71 | 72 | @property 73 | def pos(self): 74 | return Coordinates(self.data[2], self.data[3], self.data[4]) 75 | 76 | @pos.setter 77 | def pos(self, value): 78 | self.data[2] = value.x 79 | self.data[3] = value.y 80 | self.data[4] = value.z 81 | 82 | 83 | class DeviceRange(ByteStructure): 84 | """ 85 | Container for the device range data, resulting from a range measurement. 86 | 87 | The keyword arguments are at once its properties. 88 | 89 | Kwargs: 90 | timestamp: Timestamp of the range measurement 91 | distance: Distance measured by the device. 92 | RSS: Signal strength during the ranging measurement. 93 | """ 94 | byte_size = 10 95 | data_format = 'IIh' 96 | 97 | # TODO should ideally be rss not RSS 98 | def __init__(self, timestamp=0, distance=0, RSS=0): 99 | """Initializes the DeviceRange object.""" 100 | self.data = [timestamp, distance, RSS] 101 | 102 | def load(self, data): 103 | self.data = data 104 | 105 | def __str__(self): 106 | return '{self.timestamp} ms, {self.distance} mm, {self.RSS} dBm'.format(self=self) 107 | 108 | @property 109 | def timestamp(self): 110 | return self.data[0] 111 | 112 | @timestamp.setter 113 | def timestamp(self, value): 114 | self.data[0] = value 115 | 116 | @property 117 | def distance(self): 118 | return self.data[1] 119 | 120 | @distance.setter 121 | def distance(self, value): 122 | self.data[1] = value 123 | 124 | @property 125 | def RSS(self): 126 | return self.data[2] 127 | 128 | @RSS.setter 129 | def RSS(self, value): 130 | self.data[2] = value 131 | 132 | 133 | def parse_id_str(id_): 134 | if id_ is None: 135 | return None 136 | if isinstance(id_, str): 137 | if id_.startswith("0x"): 138 | return str(int(id_, 16)) 139 | else: 140 | return id_ 141 | elif isinstance(id_, int): 142 | return str(id_) 143 | 144 | 145 | def parse_id_int(id_): 146 | if id_ is None: 147 | return None 148 | return int(parse_id_str(id_)) 149 | 150 | 151 | class NetworkID(SingleRegister): 152 | """ 153 | Container for a device's network ID. 154 | 155 | Kwargs: 156 | network_id: The network ID of the device. 157 | """ 158 | 159 | def __init__(self, network_id=0): 160 | """Initializes the NetworkID object.""" 161 | super(NetworkID, self).__init__(parse_id_int(network_id), 2, signed=False) 162 | 163 | def __str__(self): 164 | return "0x{:04X}".format(self.id) 165 | 166 | @property 167 | def id(self): 168 | return self.data[0] 169 | 170 | @id.setter 171 | def id(self, value): 172 | self.data[0] = value 173 | 174 | @property 175 | def network_id(self): 176 | return self.data[0] 177 | 178 | @network_id.setter 179 | def network_id(self, value): 180 | self.data[0] = value 181 | 182 | 183 | class DeviceList(Data): 184 | """ 185 | Container for a list of device IDs. 186 | 187 | Using list_size is recommended when having just used getDeviceListSize, while ids 188 | is recommended when one knows the IDs. When using one, the other automatically 189 | gets its respective value. Therefore, only use on of both. 190 | 191 | Note also that DeviceList(list_size=1) is the same as NetworkID(). 192 | 193 | Kwargs: 194 | ids: Array of known or unknown device IDs. Empty by default. 195 | list_size: Size of the device list. 196 | """ 197 | 198 | def __init__(self, ids=None, list_size=0): 199 | """Initializes the DeviceList object with either IDs or its size.""" 200 | ids = [] if ids is None else ids 201 | if list_size != 0 and ids == []: 202 | Data.__init__(self, [0] * list_size, 'H' * list_size) 203 | else: 204 | Data.__init__(self, ids, 'H' * len(ids)) 205 | 206 | def __str__(self): 207 | return 'IDs: ' + ', '.join([str(id) for id in self]) 208 | 209 | def load(self, data, convert=False): 210 | self.data = data 211 | 212 | @property 213 | def list_size(self): 214 | return len(self.data) 215 | 216 | 217 | class RXInfo(ByteStructure): 218 | byte_size = 3 219 | data_format = 'HB' 220 | 221 | def __init__(self, remote_id=0, amount_of_bytes=0): 222 | """Initialises the RX Info structure""" 223 | self.data = [remote_id, amount_of_bytes] 224 | 225 | def load(self, data): 226 | self.data = data 227 | 228 | @property 229 | def remote_id(self): 230 | return self.data[0] 231 | 232 | @remote_id.setter 233 | def remote_id(self, value): 234 | self.data[0] = value 235 | 236 | @property 237 | def amount_of_bytes(self): 238 | return self.data[1] 239 | 240 | @amount_of_bytes.setter 241 | def amount_of_bytes(self, value): 242 | self.data[1] = value 243 | 244 | 245 | class TXInfo(ByteStructure): 246 | """Container for data transmission meta information 247 | 248 | Args: 249 | remote_id: ID to transmit to 250 | operation: remote operation to execute 251 | """ 252 | byte_size = 3 253 | data_format = 'HB' 254 | 255 | def __init__(self, remote_id, operation=PozyxConstants.REMOTE_DATA): 256 | self.data = [remote_id, operation] 257 | 258 | @property 259 | def remote_id(self): 260 | return self.data[0] 261 | 262 | @remote_id.setter 263 | def remote_id(self, value): 264 | self.data[0] = value 265 | 266 | @property 267 | def operation(self): 268 | return self.data[1] 269 | 270 | @operation.setter 271 | def operation(self, value): 272 | self.data[1] = value 273 | 274 | 275 | class UWBMapping: 276 | BITRATES = {0: '110 kbit/s', 1: '850 kbit/s', 2: '6.81 Mbit/s'} 277 | PRFS = {1: '16 MHz', 2: '64 MHz'} 278 | PREAMBLE_LENGTHS = {0x0C: '4096 symbols', 0x28: '2048 symbols', 0x18: '1536 symbols', 0x08: '1024 symbols', 279 | 0x34: '512 symbols', 0x24: '256 symbols', 0x14: '128 symbols', 0x04: '64 symbols'} 280 | 281 | class UWBSettings(ByteStructure): 282 | """ 283 | Container for a device's UWB settings. 284 | 285 | Its keyword arguments are at once its properties. 286 | 287 | It also provides parsing functions for all its respective properties, 288 | which means this doesn't need to be done by users. These functions are 289 | parse_prf, parse_plen and parse_bitrate. 290 | 291 | You can also directly print the UWB settings, resulting in the following 292 | example output: 293 | "CH: 1, bitrate: 850kbit/s, prf: 16MHz, plen: 1024 symbols, gain: 15.0dB" 294 | 295 | Kwargs: 296 | channel: UWB channel of the device. See POZYX_UWB_CHANNEL. 297 | bitrate: Bitrate of the UWB commmunication. See POZYX_UWB_RATES. 298 | prf: Pulse repeat frequency of the UWB. See POZYX_UWB_RATES. 299 | plen: Preamble length of the UWB packets. See POZYX_UWB_PLEN. 300 | gain_db: Gain of the UWB transceiver, a float value. See POZYX_UWB_GAIN. 301 | """ 302 | byte_size = 7 303 | data_format = 'BBBBf' 304 | 305 | def __init__(self, channel=0, bitrate=0, prf=0, plen=0, gain_db=0.0): 306 | """Initializes the UWB settings.""" 307 | self.data = [channel, bitrate + (prf << 6), plen, int(2 * gain_db)] 308 | 309 | def load(self, data, convert=False): 310 | self.data = data 311 | 312 | def parse_bitrate(self): 313 | """Parses the bitrate to be humanly readable.""" 314 | try: 315 | return UWBMapping.BITRATES[self.bitrate] 316 | except KeyError: 317 | return 'invalid bitrate' 318 | 319 | def parse_prf(self): 320 | """Parses the pulse repetition frequency to be humanly readable.""" 321 | 322 | try: 323 | return UWBMapping.PRFS[self.prf] 324 | except KeyError: 325 | return 'invalid pulse repetitions frequency (PRF)' 326 | 327 | def parse_plen(self): 328 | """Parses the preamble length to be humanly readable.""" 329 | 330 | try: 331 | return UWBMapping.PREAMBLE_LENGTHS[self.plen] 332 | except KeyError: 333 | return 'invalid preamble length' 334 | 335 | def __str__(self): 336 | return "CH: {}, bitrate: {}, prf: {}, plen: {}, gain: {} dB".format(self.channel, self.parse_bitrate(), 337 | self.parse_prf(), self.parse_plen(), 338 | self.gain_db) 339 | 340 | @property 341 | def channel(self): 342 | return self.data[0] 343 | 344 | @channel.setter 345 | def channel(self, value): 346 | self.data[0] = value 347 | 348 | @property 349 | def bitrate(self): 350 | return self.data[1] & 0x3F 351 | 352 | @bitrate.setter 353 | def bitrate(self, value): 354 | self.data[1] = (self.data[1] & 0xC0) + value 355 | 356 | @property 357 | def prf(self): 358 | return (self.data[1] & 0xC0) >> 6 359 | 360 | @prf.setter 361 | def prf(self, value): 362 | self.data[1] = (self.data[1] & 0x3F) + (value << 6) 363 | 364 | @property 365 | def plen(self): 366 | return self.data[2] 367 | 368 | @plen.setter 369 | def plen(self, value): 370 | self.data[2] = value 371 | 372 | @property 373 | def gain_db(self): 374 | return float(self.data[3]) / 2 375 | 376 | @gain_db.setter 377 | def gain_db(self, value): 378 | self.data[3] = int(value * 2) 379 | 380 | 381 | # TODO maybe change with properties one day? 382 | class FilterData(ByteStructure): 383 | byte_size = 1 384 | data_format = 'B' 385 | 386 | def __init__(self, filter_type=0, filter_strength=0): 387 | self.data = [filter_type + (filter_strength << 4)] 388 | 389 | def load(self, data, convert=False): 390 | self.data = data 391 | 392 | def get_filter_name(self): 393 | filter_types = { 394 | PozyxConstants.FILTER_TYPE_NONE: "No filter", 395 | PozyxConstants.FILTER_TYPE_FIR: "FIR filter", 396 | PozyxConstants.FILTER_TYPE_MOVING_AVERAGE: "Moving average filter", 397 | PozyxConstants.FILTER_TYPE_MOVING_MEDIAN: "Moving median filter", 398 | } 399 | return filter_types.get(self.filter_type, "Unknown filter {}".format(self.filter_type)) 400 | 401 | def __str__(self): 402 | return "{} with strength {}".format(self.get_filter_name(), self.filter_strength) 403 | 404 | @property 405 | def value(self): 406 | return self.data[0] 407 | 408 | @value.setter 409 | def value(self, value): 410 | self.data[0] = value 411 | 412 | @property 413 | def filter_type(self): 414 | return self.data[0] & 0xF 415 | 416 | @filter_type.setter 417 | def filter_type(self, value): 418 | self.data[0] = value + (self.filter_strength << 4) 419 | 420 | @property 421 | def filter_strength(self): 422 | return self.data[0] >> 4 423 | 424 | @filter_strength.setter 425 | def filter_strength(self, value): 426 | self.data[0] = self.filter_type + (value << 4) 427 | 428 | 429 | class AlgorithmData(ByteStructure): 430 | byte_size = 1 431 | data_format = 'B' 432 | 433 | def __init__(self, algorithm=0, dimension=0): 434 | self.data = [algorithm + (dimension << 4)] 435 | 436 | def load(self, data, convert=False): 437 | self.data = data 438 | 439 | def get_algorithm_name(self): 440 | algorithms = { 441 | PozyxConstants.POSITIONING_ALGORITHM_UWB_ONLY: "UWB only", 442 | PozyxConstants.POSITIONING_ALGORITHM_TRACKING: "Tracking", 443 | PozyxConstants.POSITIONING_ALGORITHM_NONE: "None", 444 | } 445 | return algorithms.get(self.algorithm, "Unknown filter {}".format(self.algorithm)) 446 | 447 | def get_dimension_name(self): 448 | dimensions = { 449 | PozyxConstants.DIMENSION_2D: "2D", 450 | PozyxConstants.DIMENSION_2_5D: "2.5D", 451 | PozyxConstants.DIMENSION_3D: "3D", 452 | } 453 | return dimensions.get(self.dimension, "Unknown filter {}".format(self.algorithm)) 454 | 455 | def __str__(self): 456 | return "Algorithm {}, dimension {}".format(self.get_algorithm_name(), self.get_dimension_name()) 457 | 458 | @property 459 | def value(self): 460 | return self.data[0] 461 | 462 | @value.setter 463 | def value(self, value): 464 | self.data[0] = value 465 | 466 | @property 467 | def algorithm(self): 468 | return self.data[0] & 0xF 469 | 470 | @algorithm.setter 471 | def algorithm(self, value): 472 | self.data[0] = value + (self.dimension << 4) 473 | 474 | @property 475 | def dimension(self): 476 | return self.data[0] >> 4 477 | 478 | @dimension.setter 479 | def dimension(self, value): 480 | self.data[0] = self.algorithm + (value << 4) 481 | --------------------------------------------------------------------------------