Metadata-Version: 2.4
Name: ataraxis-transport-layer-pc
Version: 5.0.0
Summary: Provides methods for establishing and maintaining bidirectional communication with Arduino and Teensy microcontrollers over USB and UART serial interfaces.
Project-URL: Homepage, https://github.com/Sun-Lab-NBB/ataraxis-transport-layer-pc
Project-URL: Documentation, https://ataraxis-transport-layer-pc-api-docs.netlify.app/
Author: Ivan Kondratyev, Katlynn Ryu
Maintainer-email: Ivan Kondratyev <ik278@cornell.edu>
License-Expression: Apache-2.0
License-File: LICENSE
Keywords: UART,USB,ataraxis,communication,serial
Classifier: Development Status :: 5 - Production/Stable
Classifier: Intended Audience :: Developers
Classifier: Operating System :: MacOS :: MacOS X
Classifier: Operating System :: Microsoft :: Windows
Classifier: Operating System :: POSIX :: Linux
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Programming Language :: Python :: 3.14
Classifier: Topic :: Communications
Classifier: Typing :: Typed
Requires-Python: <3.15,>=3.12
Requires-Dist: ataraxis-base-utilities<8,>=7
Requires-Dist: ataraxis-time<8,>=7
Requires-Dist: numba<1,>=0.63
Requires-Dist: numpy<3,>=2
Requires-Dist: pyserial<4,>=3
Description-Content-Type: text/markdown

# ataraxis-transport-layer-pc

Provides methods for establishing and maintaining bidirectional communication with Arduino and Teensy
microcontrollers over USB and UART serial interfaces.

![PyPI - Version](https://img.shields.io/pypi/v/ataraxis-transport-layer-pc)
![PyPI - Python Version](https://img.shields.io/pypi/pyversions/ataraxis-transport-layer-pc)
[![uv](https://tinyurl.com/uvbadge)](https://github.com/astral-sh/uv)
[![Ruff](https://tinyurl.com/ruffbadge)](https://github.com/astral-sh/ruff)
![type-checked: mypy](https://img.shields.io/badge/type--checked-mypy-blue?style=flat-square&logo=python)
![PyPI - License](https://img.shields.io/pypi/l/ataraxis-transport-layer-pc)
![PyPI - Status](https://img.shields.io/pypi/status/ataraxis-transport-layer-pc)
![PyPI - Wheel](https://img.shields.io/pypi/wheel/ataraxis-transport-layer-pc)

___

## Detailed Description

This is the Python implementation of the ataraxis-transport-layer (AXTL) library, designed to run on
host-computers (PCs). It provides methods for bidirectionally communicating with microcontrollers running the
[ataraxis-transport-layer-mc](https://github.com/Sun-Lab-NBB/ataraxis-transport-layer-mc) companion library written in
C++. The library abstracts all steps necessary to safely send and receive data over the USB and UART communication
interfaces. It is specifically designed to support time-critical applications, such as scientific experiments, and can
achieve microsecond communication speeds for modern microcontroller-PC hardware combinations. This library is part of
the [Ataraxis](https://github.com/Sun-Lab-NBB/ataraxis) framework for AI-assisted scientific hardware control.

___

## Features

- Supports Windows, Linux, and macOS.
- Uses Consistent Overhead Byte Stuffing (COBS) to encode payloads during transmission.
- Supports standard and reflected Cyclic Redundancy Check (CRC) 8-, 16- and 32-bit polynomials for data integrity.
- Allows fine-tuning all library components to support a wide range of application contexts.
- Uses Just-in-Time (JIT) compilation and NumPy to optimize runtime performance in time-critical applications.
- Has a [companion](https://github.com/Sun-Lab-NBB/ataraxis-transport-layer-mc) microcontroller library written in C++.
- Apache 2.0 License.

___

## Table of Contents

- [Dependencies](#dependencies)
- [Installation](#installation)
- [Usage](#usage)
  - [TransportLayer](#transportlayer)
  - [CLI Commands](#cli-commands)
- [API Documentation](#api-documentation)
- [Developers](#developers)
- [Versioning](#versioning)
- [Authors](#authors)
- [License](#license)
- [Acknowledgments](#acknowledgments)

___

## Dependencies

For users, all library dependencies are installed automatically by all supported installation methods. For developers,
see the [Developers](#developers) section for information on installing additional development dependencies.

___

## Installation

### Source

***Note,*** installation from source is ***highly discouraged*** for anyone who is not an active project developer.

1. Download this repository to the local machine using the preferred method, such as git-cloning. Use one of the
   [stable releases](https://github.com/Sun-Lab-NBB/ataraxis-transport-layer-pc/tags) that include precompiled binary
   and source code distribution (sdist) wheels.
2. If the downloaded distribution is stored as a compressed archive, unpack it using the appropriate decompression
   tool.
3. `cd` to the root directory of the prepared project distribution.
4. Run `pip install .` to install the project and its dependencies.

### pip

Use the following command to install the library and all of its dependencies via [pip](https://pip.pypa.io/en/stable/):
`pip install ataraxis-transport-layer-pc`

___

## Usage

### TransportLayer
The TransportLayer class provides the API for bidirectional communication over USB and UART serial interfaces. It
ensures proper encoding and decoding of data packets using the Consistent Overhead Byte Stuffing (COBS)
scheme and ensures transmitted packet integrity through the use of the Cyclic Redundancy Check (CRC) checksums.

#### Packet Anatomy
The TransportLayer class sends and receives data in the form of packets. Each packet adheres to the following general
layout:

`[START] [PAYLOAD SIZE] [COBS OVERHEAD] [PAYLOAD (1 to 254 bytes)] [DELIMITER] [CRC CHECKSUM (1 to 4 bytes)]`

To optimize runtime efficiency, the class generates two buffers at initialization time that store the incoming and
outgoing data packets. Additionally, the class generates a static lookup table to speed up the CRC checksum calculations
at runtime.

***Note,*** the CRC parameters are specified in the standard non-reflected, MSB-aligned form used by published CRC
parameter catalogues. Reflected variants, such as CRC-16/USB or CRC-32/ISO-HDLC, are selected with the separate
reflection flag rather than by passing an already bit-reversed polynomial.

***Note,*** TransportLayer's write_data() and read_data() methods ***exclusively*** work with the **PAYLOAD** region of
each data buffer. End users can safely ignore all packet-related information and focus on working with transmitted and
received serialized payloads, as it is impossible to access and manipulate packet metadata via the public API.

#### JIT Compilation
The class uses numba under-the-hood to compile many data processing steps to efficient C-code the first time these
methods are called. Since compilation is expensive, the first call to each numba-compiled method is typically very slow,
but all further calls are considerably faster. For optimal performance, all TransportLayer methods should be called at
least once before entering the time-critical portion of the runtime so that the library has time to precompile the code.

#### Initialization Delay
Some microcontrollers, such as Arduino AVR boards, reset upon establishing connection over the UART interface. If
TransportLayer attempts to transmit the data to a microcontroller undergoing the reset, the data may not reach the
microcontroller at all or become corrupted. When using a microcontroller with the UART interface, further code
execution should be delayed by ~2-5 seconds after initializing the TransportLayer class to allow the microcontroller to
finish its reset sequence.

#### Baudrates
For microcontrollers using the UART interface, it is essential to set the baudrate to a value supported by the
microcontroller's hardware. Usually, microcontroller manufacturers provide a list of supported baudrates for each
microcontroller. Additionally, the baudrate values used in the microcontroller and PC versions of the library have to
match. If any of these conditions are not satisfied, the connection can become unstable, leading to the corruption of
exchanged data packets.

#### Quickstart
This minimal example demonstrates how to use this library to send and receive data. It is designed to be used together
with the quickstart example of the [companion](https://github.com/Sun-Lab-NBB/ataraxis-transport-layer-mc#quickstart)
library. See the [rx_tx_loop.py](./examples/rx_tx_loop.py) for the .py implementation of this example:
```
from dataclasses import field, dataclass

import numpy as np
from ataraxis_time import PrecisionTimer
from ataraxis_base_utilities import LogLevel, console

from ataraxis_transport_layer_pc import TransportLayer

# Activates the console to print messages to the terminal during runtime.
if not console.enabled:
    console.enable()

# Instantiates a new TransportLayer object. Most class initialization arguments are set to use optimal default values
# for most microcontrollers and assume that the companion library uses the default parameters. Consult the ReadMe and
# the API documentation to learn about fine-tuning the TransportLayer's parameters to better match the intended
# use-case.
transport_layer = TransportLayer(port="/dev/ttyACM1", baudrate=115200, microcontroller_serial_buffer_size=256)

# Note, buffer size 256 is set for an Arduino Due board. Most Arduino boards have buffers capped at 64 or 256
# bytes. During production runtimes, it is critically important to set the buffer size to the actual size used by the
# interfaced microcontroller.

# Similarly, the baudrate used here is not optimal for all UART microcontrollers. For the communication to be stable,
# the baudrate must be set to an optimal value for the specific microcontroller participating in the communication
# cycle. Use the https://wormfood.net/avrbaudcalc.php tool to find the best baudrate for your AVR board or consult the
# manufacturer's documentation.

# Pre-creates the objects used for the demonstration below.
test_scalar = np.uint32(123456789)
test_array = np.zeros(4, dtype=np.uint8)


# While Python does not have C++-like structures, it has dataclasses that fulfill a similar role. This dataclass
# must not be frozen, because read_data() overwrites its fields in place during the reception procedure.
@dataclass(slots=True)
class TestStruct:
    """Groups the test values used to demonstrate dataclass serialization."""

    test_flag: np.bool_ = field(default_factory=lambda: np.bool_(True))
    """Demonstrates serialization of a numpy boolean value."""
    test_float: np.float32 = field(default_factory=lambda: np.float32(6.66))
    """Demonstrates serialization of a numpy 32-bit floating-point value."""

    def __repr__(self) -> str:
        """Returns a string representation of the TestStruct instance."""
        return f"TestStruct(test_flag={self.test_flag}, test_float={round(float(self.test_float), ndigits=2)})"


test_struct = TestStruct()

# Some Arduino boards reset after receiving a connection request. To make this example universal, sleeps for 2 seconds
# to ensure the microcontroller is ready to receive data.
timer = PrecisionTimer(precision="s")
timer.delay(delay=2, allow_sleep=True, block=False)

console.echo(message="Transmitting the data to the microcontroller...")

# Executes one transmission and one data reception cycle. During production runtime, this code would typically run in
# a function or loop.

# Writes objects to the TransportLayer's transmission buffer, staging them to be sent with the next send_data() command.
# Note, the objects are written in the order they are read by the microcontroller.
transport_layer.write_data(test_scalar)
transport_layer.write_data(test_array)
transport_layer.write_data(test_struct)

# Packages and sends the contents of the transmission buffer that were written above to the Microcontroller.
transport_layer.send_data()

console.echo(message="Data transmission: Complete.", level=LogLevel.SUCCESS)

# Waits for the microcontroller to receive the data and respond by sending its data back to the PC.
console.echo(message="Waiting for the microcontroller to respond...")

# If no data is available, the loop blocks until the microcontroller's response becomes available.
while not transport_layer.available:
    continue

# If the data is available, carries out the reception procedure (reads the received byte-stream, parses the
# payload, and makes it available for reading).
data_received = transport_layer.receive_data()

# If the reception was successful, reads the data, assumed to contain serialized test objects. Note, this
# example is intended to be used together with the example script from the ataraxis-transport-layer-mc library.
if data_received:
    console.echo(message="Data reception: Complete.", level=LogLevel.SUCCESS)

    # Overwrites the memory of the objects that were sent to the microcontroller with the response data.
    test_scalar = transport_layer.read_data(test_scalar)
    test_array = transport_layer.read_data(test_array)
    test_struct = transport_layer.read_data(test_struct)

    # The microcontroller replaces the scalar with a new fixed value before sending it back.
    assert test_scalar == np.uint32(987654321)

    # The rest of the data is transmitted without any modifications.
    assert np.array_equal(test_array, np.array([0, 0, 0, 0], dtype=np.uint8))
    assert test_struct.test_flag == np.bool_(True)
    assert test_struct.test_float == np.float32(6.66)

# Prints the received data values to the terminal for visual inspection.
console.echo(message="Data reading: Complete.", level=LogLevel.SUCCESS)
console.echo(message="Received data values:")
console.echo(message=f"test_scalar = {test_scalar}")
console.echo(message=f"test_array = {test_array}")
console.echo(message=f"test_struct = {test_struct}")
```

#### Sending Data
There are two key methods associated with sending data to the microcontroller:
- The `write_data()` method serializes the input object and writes the resultant byte sequence to the
  transmission buffer's payload region. Each call appends the data to the end of the payload already stored in the
  transmission buffer.
- The `send_data()` method encodes the payload stored in the transmission buffer into a packet using COBS, calculates
  and adds the CRC checksum to the encoded packet, and transmits the packet to the microcontroller. At least one byte
  of data should be written to the transmission buffer before calling this method.

The example below showcases the sequence of steps necessary to send the data to the microcontroller and assumes
the 'transport_layer' instance was initialized following the steps in the [Quickstart](#quickstart) example:
```
# Generates the test array to simulate the payload.
test_array = np.array(object=[1, 2, 3, 0, 0, 6, 0, 8, 0, 0], dtype=np.uint8)

# Writes the data into the instance's transmission buffer. The method raises an error if it is unable to write the
# data.
transport_layer.write_data(test_array)

# Constructs and hands the packet to the communication interface to be transmitted to the microcontroller.
transport_layer.send_data()
```

***Note,*** the transmission buffer is reset when the data is transmitted or via the call to the
`reset_transmission_buffer()` method. Resetting the transmission buffer discards all data stored in the buffer.

#### Receiving Data
There are three key methods associated with receiving data from the microcontroller:
- The `available` property checks if the serial interface has received enough bytes to justify parsing the data.
- The `receive_data()` method reads the encoded packet from the byte-stream stored in Serial interface buffer, verifies
  its integrity with the CRC checksum, and decodes the payload from the packet using COBS. If the packet was
  successfully received and unpacked, this method returns True.
- The `read_data()` method uses the input object as a prototype for the data extracted from the received payload. To do
  so, the method reads and consumes the number of bytes necessary to 'fill' the object with data from the payload and
  returns a new object of the same type storing the read value(s). Dataclass inputs additionally have their fields
  overwritten in place. The consumed bytes are discarded, meaning it is only possible to read the same data **once**.

The example below showcases the sequence of steps necessary to receive data from the microcontroller and assumes
the 'transport_layer' instance was initialized following the steps in the [Quickstart](#quickstart) example:
```
# Generates the test array to which the received data will be written.
test_array = np.array([1, 2, 3, 0, 0, 6, 0, 8, 0, 0], dtype=np.uint8)

# Blocks until the data is received from the microcontroller.
while not transport_layer.available:
    continue

# Parses the received data. Note, this method internally accesses the 'available' property, so it is safe to call
# receive_data() instead of 'available' in the 'while' loop above without changing how this example behaves.
receive_status = transport_layer.receive_data()  # Returns True if the packet was received and decoded.

# Recreates and returns the new test_array instance using the data received from the microcontroller. The method raises
# an error if it is unable to read the data.
updated_array = transport_layer.read_data(test_array)
```

***Note,*** each call to the `receive_data()` method resets the instance's reception buffer, discarding any potentially
unprocessed data.

### CLI Commands

This library exposes the `axtl-ports` CLI command, available from any environment that has the library installed. The
command internally calls the `print_available_ports()` standalone function and prints all USB ports accessible through
the pySerial interface alongside available ID information. The returned port address can be provided to the
TransportLayer class as the `port` argument to establish serial communication through the port.

___

## API Documentation

See the [API documentation](https://ataraxis-transport-layer-pc-api-docs.netlify.app/) for the detailed description of
the methods and classes exposed by components of this library.

___

## Developers

This section provides installation, dependency, and build-system instructions for the developers that want to modify
the source code of this library.

### Installing the Project

***Note,*** this installation method requires **mamba version 2.3.2 or above**. Currently, all automation pipelines
require that mamba is installed through the [miniforge3](https://github.com/conda-forge/miniforge) installer.

1. Download this repository to the local machine using the preferred method, such as git-cloning.
2. If the downloaded distribution is stored as a compressed archive, unpack it using the appropriate decompression
   tool.
3. `cd` to the root directory of the prepared project distribution.
4. Install the core development dependencies into the ***base*** mamba environment via the
   `mamba install tox uv tox-uv` command.
5. Use the `tox -e create` command to create the project-specific development environment followed by `tox -e install`
   command to install the project into that environment as a library.

### Additional Dependencies

In addition to installing the project and all user dependencies, install the following dependencies:

1. [Python](https://www.python.org/downloads/) distributions, one for each version supported by the developed project.
   Currently, this library supports the three latest stable versions. It is recommended to use a tool like
   [pyenv](https://github.com/pyenv/pyenv) to install and manage the required versions.

### Development Automation

This project uses `tox` for development automation. The following tox environments are available:

| Environment          | Description                                                  |
|----------------------|--------------------------------------------------------------|
| `lint`               | Runs ruff formatting, ruff linting, and mypy type checking   |
| `stubs`              | Generates py.typed marker and .pyi stub files                |
| `{py312,...}-test`   | Runs the test suite via pytest for each supported Python     |
| `coverage`           | Aggregates test coverage and applies the 100% coverage gate  |
| `docs`               | Builds the API documentation via Sphinx                      |
| `build`              | Builds sdist and wheel distributions                         |
| `upload`             | Uploads distributions to PyPI via twine                      |
| `deploy`             | Uploads the built documentation to the Netlify site          |
| `install`            | Builds and installs the project into its mamba environment   |
| `uninstall`          | Uninstalls the project from its mamba environment            |
| `create`             | Creates the project's mamba development environment          |
| `remove`             | Removes the project's mamba development environment          |
| `provision`          | Recreates the mamba environment from scratch                 |
| `export`             | Exports the mamba environment as a .yml file                 |
| `import`             | Creates or updates the mamba environment from a .yml file    |

Run any environment using `tox -e ENVIRONMENT`. For example, `tox -e lint`.

***Note,*** all pull requests for this project have to successfully complete the `tox` task before being merged. To
expedite the task's runtime, use the `tox --parallel` command to run some tasks in parallel.

### AI-Assisted Development

Claude Code skills and other AI development assets for this project are distributed through the
[ataraxis](https://github.com/Sun-Lab-NBB/ataraxis) marketplace as part of the **automation** plugin. Install the
plugin from the marketplace to make all associated skills and development tools available to compatible AI coding
agents.

### Automation Troubleshooting

Many packages used in `tox` automation pipelines (uv, mypy, ruff) and `tox` itself may experience runtime failures. In
most cases, this is related to their caching behavior. If an unintelligible error is encountered with any of the
automation components, deleting the corresponding cache directories (`.tox`, `.ruff_cache`, `.mypy_cache`, etc.)
manually or via a CLI command typically resolves the issue.

___

## Versioning

This project uses [semantic versioning](https://semver.org/). See the
[tags on this repository](https://github.com/Sun-Lab-NBB/ataraxis-transport-layer-pc/tags) for the available
project releases.

___

## Authors

- Ivan Kondratyev ([Inkaros](https://github.com/Inkaros))
- Katlynn Ryu ([katlynn-ryu](https://github.com/KatlynnRyu))

___

## License

This project is licensed under the Apache 2.0 License: see the [LICENSE](LICENSE) file for details.

___

## Acknowledgments

- All Sun lab [members](https://neuroai.github.io/sunlab/people) for providing the inspiration and comments during the
  development of this library.
- [PowerBroker2](https://github.com/PowerBroker2) and his
  [pySerialTransfer](https://github.com/PowerBroker2/pySerialTransfer) for inspiring this library and serving as an
  example and benchmark. Check pySerialTransfer project as a good alternative to this library with a non-overlapping
  set of features.
- The creators of all other dependencies and projects listed in the [pyproject.toml](pyproject.toml) file.

___
