- Python 97.5%
- Dockerfile 2.5%
|
|
||
|---|---|---|
| .forgejo/workflows | ||
| fosd | ||
| misc/containerlab | ||
| tests | ||
| .gitignore | ||
| config.example.yaml | ||
| LICENSE | ||
| pyproject.toml | ||
| README.md | ||
fosd - Fiber Optic Switch Daemon
A lightweight HTTP API server for controlling fiber optic switches (FOS) from various manufacturers. The daemon provides a unified REST API interface for managing multiple fiber optic switches through a plugin-based driver architecture.
Features
- RESTful HTTP API - Simple JSON-based API for switch control
- Multi-switch support - Manage multiple switches simultaneously
- Plugin driver architecture - Extensible driver system for different switch models
- YAML configuration - Easy-to-manage configuration files
- Asynchronous operation - Built on aiohttp for high performance
Supported Hardware
Currently Supported Switches
-
Weinert EOL Switches (UART interface)
- Auto-detection of input/output configuration (e.g., 1x2, 1x4, etc.)
- Firmware version reporting
- 1-based channel indexing
- Compatible ID:
weinert-eol,uart
-
Agiltron FFSW Switches (USB/Serial)
- Support for MEMS-based switches
- 0-based channel indexing
- Compatible ID:
agiltron-ffsw
-
Virtual Switch (Testing/Development)
- Simulated switch for testing
- 0-based channel indexing (0 to N-1)
- Compatible ID:
virtual
-
Containerlab Bridge (Linux Bridge/ebtables Simulation)
- Simulates 1:N fiber optical switch using Linux bridges
- Designed for containerlab network topologies
- Uses ebtables for Layer 2 switching control
- 0-based channel indexing (0 to N-1)
- Compatible IDs:
containerlab-bridge,clab-bridge,linux-bridge-fos
Planned Support
- Weinert EOL Switches (I2C interface) -
weinert-eol,i2c
Installation
Prerequisites
- Python 3.10 or higher
- uv - Fast Python package installer
Install uv
# macOS and Linux
curl -LsSf https://astral.sh/uv/install.sh | sh
# Windows
powershell -c "irm https://astral.sh/uv/install.sh | iex"
# Or with pip
pip install uv
Install Dependencies
uv sync
This will install all required packages from pyproject.toml:
aiohttp- Async HTTP serverpyserial- Serial port communicationpyyaml- YAML configuration parsing
Running the Daemon
# With default config location (/etc/fosd/config.yaml)
uv run fosd
# With custom config file
uv run fosd --config /path/to/config.yaml
# With debug logging
uv run fosd --loglevel DEBUG
Command Line Options
--config, -c- Path to configuration file (default:/etc/fosd/config.yaml)--loglevel- Logging level: DEBUG, INFO, WARN, ERROR (default: INFO)
Configuration
Configuration is done via a YAML file. See config.example.yaml for a template.
Example Configuration
http:
host: 0.0.0.0
port: 9000
switches:
switch_1:
compatible: weinert-eol,uart
port: /dev/tty.usbserial-211240
baud: 57600
switch_2:
compatible: virtual
channels: 4
lab_switch:
compatible: agiltron-ffsw
port: /dev/serial/by-id/usb-FOSI-12345
# Containerlab bridge simulation (for use in containerlab topologies)
fiber_sim:
compatible: containerlab-bridge
input_interface: eth1
output_interfaces:
- eth2
- eth3
- eth4
- eth5
Configuration Options
HTTP Server Settings
http.host- Bind address (e.g.,0.0.0.0for all interfaces,127.0.0.1for localhost)http.port- Port number (default: 9000)
Switch Configuration
Each switch is defined under the switches section with a unique identifier.
Common parameters:
compatible- Driver identifier (see Supported Hardware section)port- Serial port path (for UART/USB devices)baud- Baud rate for serial communication (e.g., 57600, 115200)channels- Number of output channels (for virtual switches)SN- Serial number (for Agiltron switches)input_interface- Input interface name (for containerlab-bridge)output_interfaces- List of output interface names (for containerlab-bridge)bridge_name- Linux bridge name (for containerlab-bridge, default:br-fos)
API Documentation
The daemon exposes a REST API on the configured host and port.
API Version
GET /v1
Get API version information.
Response:
{
"version": "1.0.0"
}
List All Switches
GET /v1/switches
Get a list of all configured switches.
Response:
{
"switches": ["switch_1", "switch_2", "lab_switch"]
}
Get Switch Information
GET /v1/switches/{id}
Get detailed information about a specific switch.
Parameters:
{id}- Switch identifier from configuration
Response Example (Weinert EOL):
{
"model": "1x4",
"firmware": "v2.1",
"inputs": "1",
"outputs": "4"
}
Response Example (Virtual):
{
"name": "Virtual Fiber-Optical Switch",
"inputs": 1,
"outputs": 4
}
Get Current Output Channel
GET /v1/switches/{id}/output
Get the currently active output channel.
Parameters:
{id}- Switch identifier from configuration
Response:
{
"channel": 2
}
Note: Channel numbering depends on the driver. Virtual switches use 0-based indexing (0 to N-1), while hardware switches may use 1-based indexing (1 to N). Check the driver documentation for your specific hardware.
Set Output Channel
PUT /v1/switches/{id}/output
Switch to a different output channel.
Parameters:
{id}- Switch identifier from configuration
Request Body:
{
"channel": 2
}
Note: For virtual switches with 4 channels, valid values are 0-3. For hardware switches, consult the driver documentation.
Response:
200 OK- Channel switched successfully400 Bad Request- Invalid request500 Internal Server Error- Failed to switch channel
Usage Examples
Using curl
# Get API version
curl http://localhost:9000/v1
# List all switches
curl http://localhost:9000/v1/switches
# Get switch information
curl http://localhost:9000/v1/switches/switch_1
# Get current output channel
curl http://localhost:9000/v1/switches/switch_1/output
# Set output channel to 2 (for virtual switch, valid: 0-3 if 4 channels)
curl -X PUT http://localhost:9000/v1/switches/switch_1/output \
-H "Content-Type: application/json" \
-d '{"channel": 2}'
Using Python
import requests
BASE_URL = "http://localhost:9000/v1"
# List switches
response = requests.get(f"{BASE_URL}/switches")
switches = response.json()['switches']
print(f"Available switches: {switches}")
# Get current channel
response = requests.get(f"{BASE_URL}/switches/switch_1/output")
current = response.json()['channel']
print(f"Current channel: {current}")
# Switch to channel 1 (for virtual switch with 4 channels, valid: 0-3)
response = requests.put(
f"{BASE_URL}/switches/switch_1/output",
json={"channel": 1}
)
if response.status_code == 200:
print("Channel switched successfully")
Architecture
Components
__main__.py- Entry point, configuration loading, and daemon startuplistener.py- HTTP server and REST API endpoints (HTTPListener)backend.py- Business logic and request handling (FOSBackend)drivers/- Hardware driver implementationscommon.py- Base driver class and registryweinert_eol.py- Weinert EOL switch driversagiltron_ffsw.py- Agiltron FFSW switch drivervirtual.py- Virtual switch for testing
Driver Development
To add support for a new switch model:
- Create a new driver file in
drivers/ - Inherit from
FOSDriverbase class - Implement required methods:
probe(device_id)- Check if driver handles the device ID__init__(config)- Initialize the switch with configget_info()- Return switch informationget_channel()- Get current channel (1-based)set_channel(ch)- Set output channel (1-based)
- Register the driver in
drivers/common.py
Example:
from .common import FOSDriver
class MySwitch(FOSDriver):
@classmethod
def probe(cls, device_id: str) -> bool:
return device_id == "myvendor-mymodel"
def __init__(self, config):
# Initialize hardware connection
pass
def get_info(self) -> dict:
return {"model": "MyModel", "outputs": 4}
def get_channel(self) -> int:
# Query hardware for current channel
# Return 0-based or 1-based depending on your hardware
return 1
def set_channel(self, ch: int):
# Validate channel range (example for 0-based with 4 channels)
if ch < 0 or ch >= 4:
raise ValueError(f"Channel {ch} out of range")
# Send command to hardware
pass
Troubleshooting
Serial Port Permissions
On Linux, you may need to add your user to the dialout group:
sudo usermod -a -G dialout $USER
# Log out and log back in for changes to take effect
Finding Serial Ports
# Linux
ls /dev/tty* | grep -i usb
# macOS
ls /dev/tty.* | grep -i usb
# List by ID (Linux)
ls /dev/serial/by-id/
License
This project is provided under the European Union Public License 1.2 (EUPL-1.2) license.