From a504bc1de71a3e4165e25aaa62e8380038cb40bd Mon Sep 17 00:00:00 2001 From: Philipp Klaus Date: Wed, 19 May 2021 11:10:55 +0200 Subject: [PATCH] libtrbnet_python v1.0.6 --- libtrbnet_python/README.md | 120 +++++++++++++++++++++- libtrbnet_python/setup.cfg | 38 +++++++ libtrbnet_python/setup.py | 59 +---------- libtrbnet_python/trbnet/core/highlevel.py | 16 ++- libtrbnet_python/trbnet/core/lowlevel.py | 92 ++++++++++++----- 5 files changed, 238 insertions(+), 87 deletions(-) create mode 100644 libtrbnet_python/setup.cfg diff --git a/libtrbnet_python/README.md b/libtrbnet_python/README.md index 49171ed..590d5c9 100644 --- a/libtrbnet_python/README.md +++ b/libtrbnet_python/README.md @@ -1,6 +1,7 @@ # PyTrbNet -This is a Python package wrapping libtrbnet.so. +This is a Python package wrapping libtrbnet.so +from trbnettools. It allows accessing trbnet registers from Python. The package comes with two additional features: @@ -11,7 +12,124 @@ The package comes with two additional features: status information to the EPICS detector control system. +### Installation + +This package can be installed from the Python Package +Index via: + + pip install trbnet + +### Configuration / Setup + +As this Python package depends on the shared library +libtrbnet.so for the communication with TrbNet, +its location needs to be provided. +This can be done by setting the environment variable +`LIBTRBNET`. + +libtrbnet.so in turn requires the environment variables +`DAQOPSERVER` (if talking to TrbNet via a trbnetd daemon) +or the IP of a TRB Board: `TRB3_SERVER` (if talking to +TrbNet directly). + +A fourth environment variable `XMLDB` comes into play, +if the xmldb capabilities of this Python package are +to be used. It should point to the location of the +xml-db for your system. + +Those environment variables can also be set from within +Python with their lowercase variants +`libtrbnet`, `daqopserver`, and `trb3_server` upon +instantiating the TrbNet() class. + +Here's an example using environment variables in the shell: + + +```sh +# Setting the relevant environment variables +export LIBTRBNET=/local/gitrepos/trbnettools/trbnetd/libtrbnet.so +export DAQOPSERVER=jspc55.x-matter.uni-frankfurt.de:1 +export XMLDB=/local/gitrepos/daqtools/xml-db/database + +# example call to get the value in the compile time +# register for all reachable TRBs: +trbcmd.py xmlget 0xffff TrbNet CompileTime + +# With the environment variables set, you could also +# run Python and instantiate TrbNet(). It would +# pick up the settings from the exported variables. +``` + +or by setting the variables from within Python: + +```python +import os +from trbnet import TrbNet + +lib = '/local/gitrepos/trbnettools/trbnetd/libtrbnet.so' +host = 'trbnetd_host:8888' +t = TrbNet(libtrbnet=lib, daqopserver=host) +# 0x40 is the register address of CompileTime +t.register_read(0xffff, 0x40) +``` + +### Usage with Python + +To read the content of the register address 0x0 for all +connected TrbNet boards (broadcast address 0xffff), do: + +```python +import os +from trbnet import TrbNet + +t = TrbNet() + +response = t.register_read(0xffff, 0x0) +for endpoint in response: + print("endpoint 0x{:08X} responded with: 0x{:08X}".format(endpoint, response[endpoint])) +``` + +The TrbNet() class has the following methods: + +* `register_read(trb_address, reg_address)` +* `register_write(trb_address, reg_address, value)` +* `register_read_mem(trb_address, reg_address, option, size)` +* `read_uid(trb_address)` +* Furthermore, multiple methods starting with `trb_` (e.g. `trb_set_address(uid, endpoint, trb_address)`) + can be called as they are inherited from [the parent class `_TrbNet`][trbnet/core/lowlevel.py]. + +### Usage of the Terminal Utility trbcmd.py + +The package comes with a simple command line utility called `trbcmd.py`. +It is a Python counterpart for the trbcmd utility provided +by trbnettools. + +What it can do: + +**read register values** + +``` +trbcmd.py r 0xffff 0x0 +``` + +**read memory (subsequent register addresses)** + +Read three registers starting at 0x8005 from all boards: +``` +trbcmd.py rm 0xffff 0x8005 0x3 0x0 +``` + +**xml-db queries** + +Ask all TrbNet nodes (broadcast 0xffff) for the register value of CompileTime as set in TrbNet.xml: + +``` +trbcmd.py xmlget 0xffff TrbNet CompileTime +``` + ### Resources * [The TRB Website](http://trb.gsi.de) * [TrbNet Manual](http://jspc29.x-matter.uni-frankfurt.de/docu/trbnetdocu.pdf) + +[trbnet/core/lowlevel.py]: https://github.com/pklaus/pytrbnet/blob/master/trbnet/core/lowlevel.py diff --git a/libtrbnet_python/setup.cfg b/libtrbnet_python/setup.cfg new file mode 100644 index 0000000..c579df8 --- /dev/null +++ b/libtrbnet_python/setup.cfg @@ -0,0 +1,38 @@ +[metadata] +name = trbnet +version = 1.0.6 +description = Interface to TrbNet (wrapping libtrbnet.so with ctypes) +long_description = file: README.md +long_description_content_type = text/markdown +author = Philipp Klaus +author_email = klaus@physik.uni-frankfurt.de +url = https://github.com/pklaus/pytrbnet +license = GPL +platforms = Linux +keywords = TrbNet, PyTrbNet, FPGA, low-latency, network, libtrbnet, wrapper +classifiers = + Development Status :: 4 - Beta + Operating System :: OS Independent + License :: OSI Approved :: GNU General Public License v3 or later (GPLv3+) + Programming Language :: Python + Programming Language :: Python :: 3 + Topic :: Scientific/Engineering :: Physics + Topic :: Scientific/Engineering :: Interface Engine/Protocol Translator + Topic :: System :: Hardware :: Hardware Drivers + +[options] +packages = trbnet, trbnet.core, trbnet.xmldb, trbnet.util, trbnet.epics +zip_safe = True +include_package_data = False +install_requires = + lxml + click + enum34; python_version < "3.4" + typing; python_version < "3.5" + +[options.entry_points] +console_scripts = + trbcmd.py = trbnet.util.trbcmd:cli + +[options.extras_require] +epics: pcaspy diff --git a/libtrbnet_python/setup.py b/libtrbnet_python/setup.py index 691ef76..29abe93 100644 --- a/libtrbnet_python/setup.py +++ b/libtrbnet_python/setup.py @@ -1,56 +1,7 @@ -# -*- coding: utf-8 -*- +from setuptools import setup -try: - from setuptools import setup -except ImportError: - from distutils.core import setup - -try: - import pypandoc - LDESC = open('README.md', 'r').read() - LDESC = pypandoc.convert_text(LDESC, 'rst', format='md') -except (ImportError, IOError, RuntimeError) as e: - print("Could not create long description:") - print(str(e)) - LDESC = '' - -setup(name='trbnet', - version = '1.0.dev0', - description = 'Interface to TrbNet (wrapping libtrbnet.so with ctypes)', - long_description = LDESC, - author = 'Philipp Klaus', - author_email = 'klaus@physik.uni-frankfurt.de', - url = 'https://github.com/pklaus/pytrbnet', - license = 'GPL', - packages = [ - 'trbnet', - 'trbnet.core', - 'trbnet.xmldb', - 'trbnet.util', - 'trbnet.epics', - ], - entry_points = { - 'console_scripts': [ - 'trbcmd.py = trbnet.util.trbcmd:cli', - ], - }, - include_package_data = False, - zip_safe = True, - platforms = 'Linux', - install_requires = [ - "lxml", - "click", - "enum34", # for support of enum.IntEnum on Python < 3.4 - ], - keywords = 'TrbNet PyTrbNet FPGA Low-latency network wrapper', - classifiers = [ - 'Development Status :: 4 - Beta', - 'Operating System :: OS Independent', - 'License :: OSI Approved :: GNU General Public License v3 or later (GPLv3+)', - 'Programming Language :: Python', - 'Programming Language :: Python :: 3', - 'Topic :: Scientific/Engineering :: Physics', - 'Topic :: Scientific/Engineering :: Interface Engine/Protocol Translator', - 'Topic :: System :: Hardware :: Hardware Drivers', - ] +setup( + setup_requires=[ + 'setuptools >= 38.6.0' + ] ) diff --git a/libtrbnet_python/trbnet/core/highlevel.py b/libtrbnet_python/trbnet/core/highlevel.py index 2d441b6..15bd4a8 100644 --- a/libtrbnet_python/trbnet/core/highlevel.py +++ b/libtrbnet_python/trbnet/core/highlevel.py @@ -1,4 +1,6 @@ # -*- coding: utf-8 -*- +from typing import List, Tuple, Dict + from .lowlevel import _TrbNet @@ -7,18 +9,18 @@ class TrbNet(_TrbNet): High level wrapper providing utility functions for the TrbNet class ''' - def register_read(self, trb_address, reg_address): + def register_read(self, trb_address: int, reg_address: int) -> Dict[int, int]: lin_data = super().trb_register_read(trb_address, reg_address) if (len(lin_data) % 2) != 0: raise ValueError("len(lin_data) == %d - expected a multiple of %d" % (len(lin_data), 2)) result = self._get_dynamic_trb_address_dict(lin_data, force_length=1) return {key: value[0] for key, value in result.items()} - def register_read_mem(self, trb_address, reg_address, option, size): + def register_read_mem(self, trb_address: int, reg_address: int, option: int, size: int) -> Dict[int, List[int]]: lin_data = super().trb_register_read_mem(trb_address, reg_address, option, size) return self._get_dynamic_trb_address_dict(lin_data) - def read_uid(self, trb_address): + def read_uid(self, trb_address: int) -> Dict[Tuple[int, int], int]: ''' Read unique id of TrbNet nodes @@ -35,7 +37,7 @@ class TrbNet(_TrbNet): uid_dict = {((r[0] << 32) + r[1], r[2]): r[3] for r in responses} return uid_dict - def _get_dynamic_trb_address_dict(self, lin_data, force_length=0): + def _get_dynamic_trb_address_dict(self, lin_data: List[int], force_length: int = 0) -> Dict[int, List[int]]: """ A utility function to structure response data from the trb_register_read() and trb_register_read_mem() functions. @@ -57,3 +59,9 @@ class TrbNet(_TrbNet): trb_address_responses[trb_address] = lin_data[offset:offset+length] offset += length return trb_address_responses + + def register_write(self, trb_address: int, reg_address: int, value: int): + """ + Convenience wrapper for trb_register_write() + """ + super().trb_register_write(trb_address, reg_address, value) diff --git a/libtrbnet_python/trbnet/core/lowlevel.py b/libtrbnet_python/trbnet/core/lowlevel.py index 2f1be02..f2b989e 100644 --- a/libtrbnet_python/trbnet/core/lowlevel.py +++ b/libtrbnet_python/trbnet/core/lowlevel.py @@ -2,6 +2,8 @@ import ctypes import os +from typing import List, Tuple, Union + from .error import TrbException # TODO: use warnings to indicate access to wrong register or no data @@ -22,7 +24,7 @@ class _TrbNet(object): Wrapper class for trbnet access using python ''' - def __init__(self, libtrbnet=None, daqopserver=None, trb3_server=None, buffersize=4194304): + def __init__(self, libtrbnet: str = None, daqopserver: str = None, trb3_server: str = None, buffersize: int = 4194304): ''' Constructor for the low level TrbNet class. Loads the shared library (libtrbnet), sets enviromental variables and initialises ports. @@ -45,13 +47,13 @@ class _TrbNet(object): trb3_server -- optional override of the TRB3_SERVER enviromental variable buffersize -- Size of the buffer in 32-bit words when reading back data (default: 16MiB) ''' - if trb3_server: os.environ['TRB3_SERVER'] = trb3_server - if daqopserver: os.environ['DAQOPSERVER'] = daqopserver - self.buffersize = buffersize if not libtrbnet: from .libutils import _find_lib libtrbnet =_find_lib('trbnet') self.libtrbnet = libtrbnet + if daqopserver: os.environ['DAQOPSERVER'] = daqopserver + if trb3_server: os.environ['TRB3_SERVER'] = trb3_server + self.buffersize = buffersize self.trblib = ctypes.cdll.LoadLibrary(libtrbnet) self.declare_types() status = self.trblib.init_ports() @@ -68,13 +70,17 @@ class _TrbNet(object): except AttributeError: pass - def trb_errno(self): + def trb_errno(self) -> int: ''' Returns trb_errno flag value ''' return ctypes.c_int.in_dll(self.trblib, 'trb_errno').value - def trb_term(self): + def trb_term(self) -> Tuple[int, int, int, int]: + ''' + Return the TRB_TERM info as tuple consisting of: + (status_common, status_channel, sequence, channel) + ''' term = TrbTerm.in_dll(self.trblib, 'trb_term') return (term.status_common, term.status_channel, term.sequence, term.channel) @@ -91,6 +97,9 @@ class _TrbNet(object): self.trblib.trb_register_read_mem.argtypes = [ctypes.c_uint16, ctypes.c_uint16, ctypes.c_uint8, ctypes.c_uint16, ctypes.POINTER(ctypes.c_uint32), ctypes.c_uint] self.trblib.trb_register_read_mem.restype = ctypes.c_int + self.trblib.trb_register_write_mem.argtypes = [ctypes.c_uint16, ctypes.c_uint16, ctypes.c_uint8, + ctypes.POINTER(ctypes.c_uint32), ctypes.c_uint16] + self.trblib.trb_register_read_mem.restype = ctypes.c_int self.trblib.trb_registertime_read_mem.argtypes = [ctypes.c_uint16, ctypes.c_uint16, ctypes.c_uint8, ctypes.c_uint16, ctypes.POINTER(ctypes.c_uint32), ctypes.c_uint] self.trblib.trb_registertime_read_mem.restype = ctypes.c_int @@ -127,7 +136,7 @@ class _TrbNet(object): self.trblib.trb_termstr.restype = ctypes.c_char_p - def trb_errorstr(self, errno): + def trb_errorstr(self, errno: int) -> str: ''' Get error string for an integer error number. @@ -141,7 +150,7 @@ class _TrbNet(object): _result = self.trblib.trb_errorstr(errno) return _result.decode('ascii') - def trb_register_read(self, trb_address, reg_address): + def trb_register_read(self, trb_address: int, reg_address: int) -> List[int]: ''' Read value from trb register. @@ -161,7 +170,7 @@ class _TrbNet(object): raise TrbException('Error while reading trb register.', errno, self.trb_errorstr(errno)) return [data_array[i] for i in range(status)] - def trb_register_write(self, trb_address, reg_address, value): + def trb_register_write(self, trb_address: int, reg_address: int, value: int): ''' Write trb register @@ -178,7 +187,7 @@ class _TrbNet(object): errno = self.trb_errno() raise TrbException('Error while writing trb register.', errno, self.trb_errorstr(errno)) - def trb_register_read_mem(self, trb_address, reg_address, option, size): + def trb_register_read_mem(self, trb_address: int, reg_address: int, option: int, size: int) -> List[int]: ''' Perform several trb register reads @@ -196,14 +205,37 @@ class _TrbNet(object): trb_address = ctypes.c_uint16(trb_address) reg_address = ctypes.c_uint16(reg_address) option = ctypes.c_uint8(option) - status = self.trblib.trb_register_read_mem(trb_address, reg_address, option, ctypes.c_uint16(size), data_array, ctypes.c_uint(self.buffersize)) + size = ctypes.c_uint16(size) + dsize = ctypes.c_uint(self.buffersize) + status = self.trblib.trb_register_read_mem(trb_address, reg_address, option, size, data_array, dsize) if status == -1: errno = self.trb_errno() raise TrbException('Error while reading trb register memory.', errno, self.trb_errorstr(errno)) return [data_array[i] for i in range(status)] - def trb_read_uid(self, trb_address): + def trb_register_write_mem(self, trb_address: int, reg_address: int, option: int, values: List[int], size: int = None): + ''' + Write several trb registers + + Arguments: + trb_address -- node(s) to write to + reg_address -- register address + option -- write option, 0 = write same register several times 1 = write adjacent registers + values -- list of values to write to register(s) + ''' + + data_array = (ctypes.c_uint32 * len(values))(*values) + trb_address = ctypes.c_uint16(trb_address) + reg_address = ctypes.c_uint16(reg_address) + option = ctypes.c_uint8(option) + size = size or ctypes.c_uint16(len(values)) + status = self.trblib.trb_register_write_mem(trb_address, reg_address, option, data_array, size) + if status == -1: + errno = self.trb_errno() + raise TrbException('Error while writing trb register memory.', errno, self.trb_errorstr(errno)) + + def trb_read_uid(self, trb_address: int) -> List[int]: ''' Read unique id(s) of TrbNet node(s) @@ -226,7 +258,7 @@ class _TrbNet(object): errno, self.trb_errorstr(errno)) return [data_array[i] for i in range(status)] - def trb_set_address(self, uid, endpoint, trb_address): + def trb_set_address(self, uid: int, endpoint: int, trb_address: int): ''' Set trb net address @@ -246,15 +278,15 @@ class _TrbNet(object): # rarely used funtions without documentation in trbnet.h # meaning of arguments and returned data unknown - def network_reset(self): + def network_reset(self) -> int: '''TRB network reset''' return self.trblib.network_reset() - def com_reset(self): + def com_reset(self) -> int: '''communication reset''' return self.trblib.com_reset() - def trb_fifo_flush(self, channel): + def trb_fifo_flush(self, channel: int) -> int: '''flush trb fifo Arguments: @@ -262,7 +294,7 @@ class _TrbNet(object): channel = ctypes.c_uint8(channel) return self.trblib.trb_fifo_flush(channel) - def trb_send_trigger(self, trigtype, info, random, number): + def trb_send_trigger(self, trigtype: int, info: int, random: int, number: int) -> int: '''send trigger to trb Arguments: @@ -277,21 +309,21 @@ class _TrbNet(object): number = ctypes.c_uint16(number) return self.trblib.trb_send_trigger(trigtype, info, random, number) - def trb_register_setbit(self, trb_address, reg_address, bitmask): + def trb_register_setbit(self, trb_address: int, reg_address: int, bitmask: int) -> int: trb_address = ctypes.c_uint16(trb_address) reg_address = ctypes.c_uint16(reg_address) bitmask = ctypes.c_uint32(bitmask) return self.trblib.trb_register_setbit(trb_address, reg_address, bitmask) - def trb_register_clearbit(self, trb_address, reg_address, bitmask): + def trb_register_clearbit(self, trb_address: int, reg_address: int, bitmask: int) -> int: trb_address = ctypes.c_uint16(trb_address) reg_address = ctypes.c_uint16(reg_address) bitmask = ctypes.c_uint32(bitmask) return self.trblib.trb_register_clearbit(trb_address, reg_address, bitmask) - def trb_register_loadbit(self, trb_address, reg_address, bitmask, bitvalue): + def trb_register_loadbit(self, trb_address: int, reg_address: int, bitmask: int, bitvalue: int) -> int: trb_address = ctypes.c_uint16(trb_address) reg_address = ctypes.c_uint16(reg_address) bitmask = ctypes.c_uint32(bitmask) @@ -299,39 +331,43 @@ class _TrbNet(object): return self.trblib.trb_register_loadbit(trb_address, reg_address, bitmask, bitvalue) - def trb_registertime_read_mem(self, trb_address, reg_address, option, size): - data_array = (ctypes.c_uint32 * self.buffersize)() + def trb_registertime_read_mem(self, trb_address: int, reg_address: int, option: int, size: int) -> List[int]: trb_address = ctypes.c_uint16(trb_address) reg_address = ctypes.c_uint16(reg_address) option = ctypes.c_uint8(option) - status = self.trblib.trb_register_read_mem(trb_address, reg_address, option, ctypes.c_uint16(size), data_array, ctypes.c_uint(self.buffersize)) + size = ctypes.c_uint16(size) + data_array = (ctypes.c_uint32 * self.buffersize)() + dsize = ctypes.c_uint(self.buffersize) + status = self.trblib.trb_registertime_read_mem(trb_address, reg_address, option, size, data_array, dsize) if status == -1: errno = self.trb_errno() raise TrbException('Error while reading trb register memory.', errno, self.trb_errorstr(errno)) return [data_array[i] for i in range(status)] - def trb_ipu_data_read(self, trg_type, trg_info, trg_random, trg_number, size): + def trb_ipu_data_read(self, trg_type: int, trg_info: int, trg_random: int, trg_number: int, size: int) -> List[int]: trg_type = ctypes.c_uint8(trg_type) trg_info = ctypes.c_uint8(trg_info) trg_random = ctypes.c_uint8(trg_random) trg_number = ctypes.c_uint16(trg_number) data_array = (ctypes.c_uint32 * self.buffersize)() - status = self.trblib.trb_ipu_data_read(trg_type, trg_info, trg_random, trg_number, data_array, ctypes.c_uint(self.buffersize)) + dsize = ctypes.c_uint(self.buffersize) + status = self.trblib.trb_ipu_data_read(trg_type, trg_info, trg_random, trg_number, data_array, dsize) if status == -1: errno = self.trb_errno() raise TrbException('Error while reading trb ipu data.', errno, self.trb_errorstr(errno)) return [data_array[i] for i in range(status)] - def trb_nettrace(self, trb_address, size): + def trb_nettrace(self, trb_address: int): trb_address = ctypes.c_uint16(trb_address) data_array = (ctypes.c_uint32 * self.buffersize)() - status = self.trblib.trb_nettrace(trb_address, data_array, ctypes.c_uint(self.buffersize)) + dsize = ctypes.c_uint(self.buffersize) + status = self.trblib.trb_nettrace(trb_address, data_array, dsize) if status == -1: errno = self.trb_errno() raise TrbException('Error while doing net trace.', errno, self.trb_errorstr(errno)) return [data_array[i] for i in range(status)] - def trb_termstr(self, term): + def trb_termstr(self, term: Union[Tuple[int, int, int, int], TrbTerm]) -> str: ''' Get string representation for network termination packet info tuple. -- 2.43.0