Metadata-Version: 2.4
Name: fluid-reality
Version: 0.1.6
Summary: Python SDK for Fluid Reality hardware.
Project-URL: Homepage, https://github.com/Fluid-Reality/sdk
Project-URL: Repository, https://github.com/Fluid-Reality/sdk
Project-URL: Issues, https://github.com/Fluid-Reality/sdk/issues
Author: Fluid Reality
License: MIT License
        
        Copyright (c) 2026 Fluid Reality
        
        Permission is hereby granted, free of charge, to any person obtaining a copy
        of this software and associated documentation files (the "Software"), to deal
        in the Software without restriction, including without limitation the rights
        to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
        copies of the Software, and to permit persons to whom the Software is
        furnished to do so, subject to the following conditions:
        
        The above copyright notice and this permission notice shall be included in all
        copies or substantial portions of the Software.
        
        THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
        IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
        FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
        AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
        LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
        OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
        SOFTWARE.
License-File: LICENSE
Keywords: actuators,fluid-reality,haptics,hardware,serial
Classifier: Development Status :: 3 - Alpha
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: MIT License
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3 :: Only
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Topic :: Scientific/Engineering
Classifier: Topic :: Software Development :: Libraries :: Python Modules
Requires-Python: >=3.10
Requires-Dist: pyserial>=3.5
Provides-Extra: test
Requires-Dist: pytest>=8; extra == 'test'
Description-Content-Type: text/markdown

# Fluid Reality SDK

Python SDK for Fluid Reality Lansing Development Kit hardware.

The package name on PyPI is `fluid-reality`; the Python import package is
`fluid_reality`.

## Install

Use Python 3.10 or newer.

```bash
python -m pip install --upgrade pip
python -m pip install fluid-reality
```

## Find the Serial Port

Connect the Lansing board over USB, then list the serial devices visible to
Python:

```bash
python -m serial.tools.list_ports
```

Use the device name shown by that command when creating `Lansing(...)`.
The exact name depends on the operating system:

- Windows usually reports names such as `COM4` or `COM16`.
- macOS usually reports names under `/dev/cu.*`, for example a USB modem port.
- Linux usually reports names under `/dev/tty*`, for example a USB ACM or USB
  serial device.

If more than one device is listed, unplug the board, run the command again,
then plug it back in and look for the new entry.

### Simulator port aliases

Existing applications can expose a Lansing TCP simulator under a selectable
port name without code changes. Set `FLUID_REALITY_VIRTUAL_PORTS` before starting
the application:

```powershell
$env:FLUID_REALITY_VIRTUAL_PORTS="COM66=tcp://127.0.0.1:8765"
```

Only `Lansing("COM66")` uses the mapped TCP endpoint. Selecting another COM
port opens that physical serial port normally. Separate multiple mappings with
semicolons. See
[apps/lansing_simulator/README.md](apps/lansing_simulator/README.md) for the
simulator command and platform-specific examples.

List physical serial ports together with configured aliases:

```python
from fluid_reality import list_ports

print(list_ports())
# Example: ["COM1", "COM2", "COM66"]
```

Every returned value can be passed directly to `Lansing(...)`.

To inspect exact TX/RX traffic or expose a physical board to another computer, run
the terminal-only [Device Bridge](apps/device_bridge/README.md). It maps
an SDK virtual alias to a physical serial port and can print or save binary-safe
hexadecimal and ASCII traces.

### Developing simulated-device listeners

The SDK exposes a raw byte-stream listener for device simulators. It performs
no text decoding or message framing, so protocols can switch freely between
line commands and binary streaming:

```python
from fluid_reality import TcpDeviceListener

with TcpDeviceListener("127.0.0.1", 8765) as listener:
    while True:
        with listener.accept() as connection:
            while chunk := connection.read_bytes(4096):
                response = protocol.feed(chunk)
                if response:
                    connection.write_bytes(response)
```

`write_bytes()` uses `sendall()` semantics. Applications should keep protocol
buffering, command terminators, binary packet boundaries, and mode transitions
inside their protocol engine.

## Touch Validation Example

This example powers the board, detects actuator `0`, initializes it if needed,
then asks the user to touch the actuator while it pulses once:

- full on for 250 ms
- off for 250 ms while the board discharges it in the opposite direction

Save this as `touch_validation.py`.

```python
import sys
import time

from fluid_reality import ActuatorState, Lansing


def main() -> None:
    if len(sys.argv) != 3:
        print("Usage: python touch_validation.py <serial-port> <actuator>")
        print("Find the port with: python -m serial.tools.list_ports")
        raise SystemExit(2)

    port = sys.argv[1]
    actuator = int(sys.argv[2])

    with Lansing(port) as board:
        print("Connected.")

        board.power_supply(True)
        voltage = board.voltage()
        print(f"Power supply voltage: {voltage:.2f} V")

        board.connect_power(True)
        print(f"Idle current: {board.current():.2f} mA")

        state = board.detect(actuator)
        print(f"Actuator {actuator} state after detection: {state.value}")

        if state is ActuatorState.ERROR:
            print("Actuator needs initialization. This can take about two minutes.")
            state = board.initialize(actuator)
            print(f"Actuator {actuator} state after initialization: {state.value}")

        if state is not ActuatorState.READY:
            raise RuntimeError(
                f"Actuator {actuator} is {state.value}; it is not ready to drive."
            )

        input(f"Touch actuator {actuator}, then press Enter to run the touch validation.")

        print(f"Actuator {actuator} full on for 250 ms.")
        board.set_actuator(actuator, 255)
        time.sleep(0.250)

        print(f"Actuator {actuator} off for 250 ms while it discharges.")
        board.set_actuator(actuator, 0)
        time.sleep(0.250)

        board.all_actuators_off()
        print(f"Done. You should have felt actuator {actuator} during the pulse.")


if __name__ == "__main__":
    main()
```

Run it with the serial port you found earlier:

```bash
python touch_validation.py <serial-port> <actuator>
```

For example, replace `<serial-port>` with the port name reported on your
machine, such as a Windows `COM...` device, a macOS `/dev/cu...` device, or a
Linux `/dev/tty...` device. To validate actuator 0, pass `0` as the actuator
number.

## Core Concepts

`Lansing(port)` opens the board connection. Use it as a context manager so the
serial port closes cleanly when the script exits.

The power supply and PSU connection to the actuator path are separate:

- `board.power_supply(True)` turns on the high-voltage supply.
- `board.voltage()` reads the measured supply voltage. A powered Lansing kit is
  typically around 215-220 V.
- `board.connect_power(True)` turns on the PSU connection to the actuator path.
- `board.current()` reads the current drawn by the system in milliamps.

Actuators have SDK states:

- `Unknown`: the default state when the board object is created.
- `Ready`: the actuator has been detected and is safe to drive normally.
- `Not connected`: the SDK did not measure a meaningful current change.
- `Error`: the current delta is too high for normal operation. Run
  `board.initialize(actuator)` before trying to use the actuator. Initialization
  runs a staged recovery sequence and then diagnoses the actuator again. If it
  returns `Ready`, the actuator can be used normally. If it still returns
  `Error`, leave the actuator off, check the physical connection, and contact
  Fluid Reality support before continuing.

Before driving an actuator, call `board.detect(actuator)`. Detection checks the
forward-current delta after 250 ms against a 10 mA hard limit. If safe, it keeps
only that actuator continuously forward at maximum output for another 2 seconds
and classifies the resulting delta. `set_actuator()` only works when that
actuator is `Ready`.

Actuators may need initialization after storage, shipping, or long periods
without use. If `detect()` returns `Error`, run `board.initialize(actuator)`.
Initialization drives the actuator through a staged recovery sequence and then
diagnoses it again. If initialization succeeds, the state changes to `Ready`.

## Discharge Behavior

Actuator output and discharge are also separate phases. When an actuator is
turned on with `board.set_actuator(actuator, value)`, it runs forward. When it
is turned off with `board.set_actuator(actuator, 0)`, the board does not simply
stop instantly. It automatically discharges the actuator by running it in the
opposite direction for the same amount of time it was driven forward, up to the
configured discharge limit.

This means an actuator that was active for 250 ms will discharge for about
250 ms after it is turned off. An actuator that was active for longer will also
discharge longer, but the Lansing firmware limits normal forward activation to
at most 5 seconds and limits discharge to at most 2 seconds. During discharge,
the actuator can still feel active or busy even though you already commanded it
off. That is expected behavior.

Wait for discharge to finish before starting the next pulse or interpreting the
actuator as idle. The SDK and firmware use this discharge phase to return the
actuator safely toward neutral.

## API Reference

For the complete customer development API reference, including all public
classes, methods, errors, debug output options, streaming helpers, and code
examples, see [docs/api_reference.md](docs/api_reference.md).

## Dashboard

The repository includes a desktop dashboard for connecting to a Lansing board,
turning the power supply and output connection on or off, viewing voltage and
current, detecting actuator state, initializing actuators, diagnosing actuators,
running recovery, and starting square-wave output.

See [apps/lansing_dashboard/README.md](apps/lansing_dashboard/README.md) for
installation and usage instructions.

## Terminal

The repository includes a command-line terminal for connecting to a Lansing
board, controlling the power supply and PSU connection, viewing telemetry and
configuration, detecting and diagnosing actuators, running initialization and
recovery, controlling actuator output, and operating square-wave tests. It
supports interactive use, semicolon-separated command sequences, and
newline-delimited JSON output for automation.

The terminal directory also includes PowerShell, Linux/macOS shell, and Windows
batch workflows for detecting actuators and initializing them to a target
current delta while monitoring improvement and enforcing bounded stop
conditions.

See [apps/lansing_terminal/README.md](apps/lansing_terminal/README.md) for
installation, the complete command reference, JSON schemas, automation options,
and platform-specific usage instructions.

## Examples

Example scripts are available in [examples](examples):

- [01_basic_actuator_current.py](examples/01_basic_actuator_current.py):
  power the board, connect the output, detect one actuator, pulse it, and read
  current.
- [02_initialize_and_diagnose.py](examples/02_initialize_and_diagnose.py):
  detect an actuator, initialize it when needed, and report diagnosis results.
- [03_stream_sine.py](examples/03_stream_sine.py):
  stream a sine waveform to one actuator.
- [04_debug_logging.py](examples/04_debug_logging.py):
  enable SDK and firmware debug output and save it to a log file.
- [05_status_snapshot.py](examples/05_status_snapshot.py):
  print a full board status snapshot.
- [06_manual_output_bench_test.py](examples/06_manual_output_bench_test.py):
  run direct positive/negative manual-output bench commands.
- [07_error_handling.py](examples/07_error_handling.py):
  show how to catch SDK exceptions and print recovery guidance.
- [08_actuator_pulse_until_key.py](examples/08_actuator_pulse_until_key.py):
  repeatedly pulse one actuator until a key is pressed.
