Endpoints and Autodetection

LUCIDAC Endpoints are URL-like notations for addressing the connection to a LUCIDAC device. The concept is described in the Firmware docs and is similar to the NI VISA resource syntax

Available endpoints in lucipy

Which endpoint notation and protocol support is available depends on the client implementation. Lucipy understands the following endpoints:

USB Serial (virtual terminal) speaking JSONL: serial:/

A device name is expected afterwards which is immediately passed to the pySerial constructor. Examples are serial://dev/ttyACM0 on GNU Linux/Mac OS X or serial:/COM0 on MS Windows. USB Serial connection is typically error-prone, in particular at startup when the buffers still can be filled with old contents. When using the serial connection, calls to hc.slurp() can help to clear the buffers.

“Raw” TCP/IP speaking JSONL: tcp:/

This is the native protocol of the LUCIDAC and the most realiable way to connect to the LUCIDAC. Typical examples are a Host name tcp://my-lucidac.local. or an IPv4 address tcp://192.168.150.229. An optional port can be given, tcp://192.168.150.229:5732.

Integrated Simulator/Emulator: emu:/

This will emulate a socket to the lucipy-integrated LUCIDAC Emulator (see LUCIDAC emulation). Note that this is also possible by starting the Emulator at another place and connecting via something like tcp://localhost:1234. However, this way it acts as a shorthand for starting up the emulator at the same time as the client, one does not have to transfer the TCP port information. Furthermore, the connection does not use TCP/IP but is just a python-internal function call (this is what was refered to an emulated socket in the beginning).

To use this endpoint, just instanciate with LUCIDAC("emu:/"). There are no further paths in this URL. However, the optional argument ?debug can be attached to start the python debugger if the Emulator crashes.

Device autodetection: zeroconf:/

Use this endpoint string to explicitely use autodetection even in the presence of an environment variable LUCIDAC_ENDPOINT. In the same way as emu:/, this endpoint URL supports no further arguments.

Other endpoints not listed here are explicitely not supported, in particular websocket and HTTP endpoints. If you need to make use of them, consider using a proxy such as lucigo. In particular, a typical need is to proxy an USB virtual serial terminal over TCP/IP and there are many solutions for this listed at the previous link.

LUCIDAC autodetection

For convenience, the client code allows for autodetection of the endpoint using MDNS/Zeroconf. This works by making an instance without providing the endpoint:

>>> from lucipy import LUCIDAC
>>> hc = LUCIDAC() # this will trigger the autodetection

Typically, the autodetection connects to the first LUCIDAC found in the network and warns if multiple have been found.

Direct access to the underlying API should not be neccessary, but is possible with import lucipy.detect. The folling reference shows the exposed functions.

Note

As a design philosophy in lucipy, there are no dependencies. Therefore, autodetection will only work if you have the zeroconf and/or pySerial libraries installed.

If these dependencies are not installed, the code will print warnings suggesting you to install them in order to make autodetection work.

Environment variable

Conveniently, you can use the operating system environment variable LUCIDAC_ENDPOINT. This can help to keep your scripts clean of volatile, frequently changing and probably meaningless IP addresses and ports.

Usage is, for instance, in your operating systems shell

export LUCIDAC_ENDPOINT="tcp://10.10.77.123"``
python some-script-using-luci.py

Note that if you have set the environment variable LUCIDAC_ENDPOINT, this will effectively overwrite (and thus disable) the autodetection, except you call LUCIDAC("zeroconf:/") explicitely in your code.

Code refererence

This is a small interactive command line script for discovering/detecting LUCIDACs. It will look for the Teensy Microcontroller (Hybrid Controller) both directly connected over USB Serial Device as well as Network services announced by MDNS/Zeroconf over the network (local IPv4 broadcast domain). The network lookup happens typically within a few hundred milliseconds. If you want to wait for more then one device, use the –all option.

class lucipy.detect.Endpoint(endpoint)[source]

This class models the LUCIDAC/REDAC endpoint URI convention in lucipy. Given there is no proper URI object in python, we make use of urllib.urlparse. This class is used for instance in lucipy.synchc.endpoint2socket().

Create instances either directly or via helpers which prepend the correct scheme:

>>> Endpoint("tcp://123.123.123.123:7890")
Endpoint("tcp://123.123.123.123:7890")
>>> Endpoint.fromJSONL("localhost", 1234)
Endpoint("tcp://localhost:1234")

For serial devices, there is a little extra work done ontop of URL parsing, which makes the syntax less weird in contexts where URIs are typically not used. Serial devices just don’t follow the double slash convention but more a kio/gfs single slash convention:

>>> Endpoint("serial:/dev/ttyACM0") # device file at Linux/Mac
Endpoint("serial:/dev/ttyACM0")
>>> Endpoint.fromDevice("/dev/ttyACM0")
Endpoint("serial:/dev/ttyACM0")
>>> Endpoint("serial:/COM0")
Endpoint("serial:/COM0")
>>> Endpoint.fromDevice("COM0") # port name at Windows
Endpoint("serial:/COM0")

Endpoints with scheme only are fine and used in the code. Note that schemes are the only part of the URL which is always canonically lowercased:

>>> Endpoint("EMU:")
Endpoint("emu:")

An example of an endpoint which uses all fields:

>>> e = Endpoint("TCP://myuser:mypass@FOO.bar:4711?flitz=bums&baz=bla")
>>> e.user
'myuser'
>>> e.args
{'flitz': 'bums', 'baz': 'bla'}
>>> e
Endpoint("tcp://myuser:foo.bar:4711?flitz=bums&baz=bla")
scheme

Scheme, such as “serial”, “tcp”, etc.

user

Username for login, if present. None is a valid value.

password

Password for login, if present. None is a valid value.

port

TCP/IP Port as integer. If not given, defaults to default TCP port.

args

Further query arguments from the URL

host

Host or device name. Note that hostnames are transfered to lowercase while pathnames will not.

static fromDevice(device_name)[source]

Initialize for a device name

static fromJSONL(addr, port=None)[source]

Initialize for a TCP addr/port address tuple

lucipy.detect.can_resolve(hostname, target_ip: str = None) → Optional[str][source]

gethostbyname but without the exceptions, i.e. with a boolean representation of the return value

lucipy.detect.can_resolve_to(hostname, expected_ip: str)[source]

Checks whether the host system can resolve a given (zeroconf) DNS name

lucipy.detect.detect_usb_teensys() → List[lucipy.detect.Endpoint][source]

Yields all found endpoints on local system using serial.tools.list_ports, requires pyserial

lucipy.detect.detect_network_teensys(zeroconf_timeout=500) → List[lucipy.detect.Endpoint][source]

Yields all endpoints in the local broadcast domain using Zeroconf, requires python zeroconf package

lucipy.detect.detect(single=False, prefer_network=True, zeroconf_timeout=500)[source]

Yields or returns possible endpoints using all methods. This function will raise an ModuleNotFoundError if a library is not available which might have found more.

Parameters
  • single – Return only first found instance or None, if nothing found. If this option is False, this function will return an array of endpoints discovered using all methods.

  • zeroconf_timeout – Maximum search time: How long to wait for zeroconf answers, in milliseconds. Set to 0 or None for unlimited search.

  • prefer_network – Return network result first. Typically a TCP/IP connection is faster and more reliable then the USBSerial connection.