Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
45 changes: 45 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
name: CI

on:
push:
branches: [main, master]
pull_request:
branches: [main, master]

permissions:
contents: read

jobs:
test:
runs-on: ubuntu-latest
strategy:
matrix:
python-version: ["3.10", "3.11", "3.12"]

steps:
- uses: actions/checkout@v4

- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python-version }}

- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install -e .[dev]

- name: Lint with ruff
run: ruff check .

- name: Type check with mypy
run: mypy af_server_client --ignore-missing-imports

- name: Test with pytest
run: pytest --cov=af_server_client --cov-report=xml -v

- name: Upload coverage
uses: codecov/codecov-action@v3
with:
file: ./coverage.xml
fail_ci_if_error: false
50 changes: 50 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,2 +1,52 @@
/.venv/
/.idea/

# Python
__pycache__/
*.py[cod]
*$py.class
*.so
.Python
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
wheels/
*.egg-info/
.installed.cfg
*.egg

# Testing
.tox/
.nox/
.coverage
.coverage.*
htmlcov/
.pytest_cache/
nosetests.xml
coverage.xml
*.cover
*.py,cover
.hypothesis/

# IDE
.vscode/

# OS
.DS_Store
Thumbs.db

# Benchmark results
benchmark_results/
*.json
!af_server_config.toml

# PsychoPy experiments (user-created)
experiments/
297 changes: 297 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,297 @@
# AF-Server Client

Low-latency TCP client for LabVIEW server communication with AF-Serializer protocol.

## Features

- **Low-latency TCP communication** with optimizations:
- Nagle's algorithm disabled (TCP_NODELAY)
- QoS with DSCP EF for low latency
- Minimal buffer sizes
- Asyncio + uvloop for improved performance

- **Request-response architecture** with UUID correlation

- **Interactive Textual TUI console** with:
- Status panel showing connection and latency metrics
- Rich log for command output
- Command input with history
- Real-time progress for benchmarks

- **RTT benchmarking system** with statistics and export

- **PsychoPy integration** for laboratory experiments

## Installation

### From Source

```bash
git clone https://github.com/Faragoz/AF-Server.git
cd AF-Server
pip install -e .
```

### With Development Dependencies

```bash
pip install -e ".[dev]"
```

### With PsychoPy Support

```bash
pip install -e ".[psychopy]"
```

## Quick Start

### Launch Console

```bash
af-server-client
```

### In Console

```
> connect 127.0.0.1 2001
> status
> benchmark
> export json results.json
> help
```

### Command-Line Options

```bash
# Specify config file
af-server-client -c myconfig.toml

# Override host and port
af-server-client -H 192.168.1.100 -p 3000

# Auto-connect on startup
af-server-client --connect

# Run benchmark and exit
af-server-client --benchmark --iterations 100 --export results.json
```

## Configuration

Create `af_server_config.toml` in your working directory:

```toml
[network]
host = "127.0.0.1"
port = 2001
timeout = 5.0
buffer_size = 256
enable_nodelay = true
enable_qos = true
heartbeat_interval = 30.0

[console]
log_level = "DEBUG"
history_size = 100
max_log_lines = 1000
theme = "monokai"

[benchmark]
default_iterations = 10
payload_sizes = [0, 128, 256, 512, 1024, 2048, 4096, 8192]
export_format = "json"
export_directory = "./benchmark_results"

[psychopy]
experiments_dir = "./experiments"
default_experiment = "feedback_loop.py"
subject_gui_width = 800
subject_gui_height = 600
```

## Console Commands

| Command | Description |
|---------|-------------|
| `connect [host] [port]` | Connect to server (uses config defaults if no args) |
| `disconnect` | Disconnect from server |
| `status` | Show connection status |
| `send <command>` | Send a command to server |
| `benchmark [iterations] [sizes...]` | Run RTT benchmark |
| `launch-psychopy [experiment]` | Launch PsychoPy experiment |
| `config show` | Display current configuration |
| `config set <key> <value>` | Update configuration |
| `config reload` | Reload configuration from file |
| `export <format> <filename>` | Export benchmark data (json/csv/excel) |
| `clear` | Clear console log |
| `help [command]` | Show help |
| `quit` / `exit` | Exit the application |

### Keyboard Shortcuts

| Key | Action |
|-----|--------|
| `Ctrl+C` | Quit |
| `Ctrl+L` | Clear log |
| `Ctrl+D` | Disconnect |
| `↑` / `↓` | Command history |

## Architecture

```
af_server_client/
β”œβ”€β”€ core/
β”‚ β”œβ”€β”€ tcp_client.py # Asyncio TCP client with uvloop
β”‚ β”œβ”€β”€ protocol.py # @lvclass command definitions
β”‚ β”œβ”€β”€ response_handler.py # Request-response correlation
β”‚ └── config.py # TOML configuration loader
β”œβ”€β”€ console/
β”‚ β”œβ”€β”€ app.py # Main Textual TUI application
β”‚ β”œβ”€β”€ commands.py # Command executor and registry
β”‚ β”œβ”€β”€ widgets.py # StatusPanel, ProgressPanel
β”‚ └── command_parser.py # Parse and validate commands
β”œβ”€β”€ benchmark/
β”‚ β”œβ”€β”€ rtt_test.py # Round-trip time benchmarking
β”‚ β”œβ”€β”€ metrics.py # Sample, BenchmarkRun classes
β”‚ └── export.py # JSON/CSV/Excel exporters
β”œβ”€β”€ psychopy_bridge/
β”‚ β”œβ”€β”€ launcher.py # Launch PsychoPy experiments
β”‚ β”œβ”€β”€ subject_gui.py # Subject interface (buttons/sliders)
β”‚ └── shared_client.py # Singleton TCP client
└── cli.py # CLI entry point
```

## Protocol Classes

The client uses AF-Serializer for communication:

```python
from af_server_client.core.protocol import Protocol, EchoCommand, Response

# General protocol wrapper
protocol = Protocol()
protocol.data = "request-id"

# Echo command for benchmarking
cmd = EchoCommand()
cmd.payload = "test data"
cmd.timestamp = 1234567890.123

# Server response
response = Response()
response.request_id = "request-id"
response.success = True
response.exec_time = 1500.0 # ΞΌs
response.message = "OK"
```

## Benchmarking

The benchmark system measures round-trip time with various payload sizes:

```python
from af_server_client.benchmark import RTTBenchmark, BenchmarkExporter

# Run benchmark
benchmark = RTTBenchmark(client, config)
result = await benchmark.run(iterations=10, payload_sizes=[0, 128, 512, 1024])

# Access statistics
print(f"Avg Latency: {result.stats['avg_total_latency']:.2f}ms")
print(f"P99 Latency: {result.stats['p99_latency']:.2f}ms")
print(f"Jitter: {result.stats['jitter']:.2f}ms")

# Export results
exporter = BenchmarkExporter(result)
exporter.to_json("benchmark_results.json")
exporter.to_csv("benchmark_results.csv")
exporter.to_excel("benchmark_results.xlsx")
```

### Metrics Collected

- **Total Latency**: Full round-trip time
- **Network Latency**: RTT minus server execution time
- **Execution Time**: Server-reported processing time
- **Jitter**: Standard deviation of latency
- **Percentiles**: P95 and P99 latency

## PsychoPy Integration

For laboratory experiments with CRIO devices:

```python
from af_server_client.psychopy_bridge import SharedClient, ExperimentLauncher

# Initialize shared client
client = SharedClient.initialize(config)
await client.connect()

# Launch experiment
launcher = ExperimentLauncher(config)
await launcher.launch("my_experiment.py")
```

### Subject GUI

```python
from af_server_client.psychopy_bridge.subject_gui import SubjectGUI

gui = SubjectGUI(client, width=800, height=600)
gui.register_callback('start_trial', on_start_trial)
gui.register_callback('intensity_changed', on_intensity_changed)
gui.run()
```

## Development

### Install Development Dependencies

```bash
pip install -e ".[dev]"
```

### Run Tests

```bash
pytest -v
```

### Run Tests with Coverage

```bash
pytest --cov=af_server_client --cov-report=html
```

### Lint Code

```bash
ruff check .
```

### Type Check

```bash
mypy af_server_client
```

## Requirements

- Python >= 3.10
- af-serializer
- textual >= 0.50.0
- uvloop >= 0.19.0 (Unix only)
- rich >= 13.7.0
- pandas >= 2.2.0
- numpy >= 1.26.0
- openpyxl >= 3.1.0

## License

MIT License - See LICENSE file for details.

## Author

Faragoz - aragon.froylan@gmail.com
Loading