Skip to main content

OqlOS — Operation Query Language runtime for hardware testing

Project description

OqlOS — Operation Query Language Runtime

AI Cost Tracking

PyPI Version Python License AI Cost Human Time Model

  • 🤖 LLM usage: $3.3000 (22 commits)
  • 👤 Human dev: ~$2010 (20.1h @ $100/h, 30min dedup)

Generated on 2026-04-30 using openrouter/qwen/qwen3-coder-next


Version Python License

OqlOS is the core runtime for executing OQL (Operation Query Language) hardware testing scenarios. It provides the execution engine, hardware abstraction layer, and API server for running automated hardware tests.

Installation

# Install from source with development dependencies
pip install -e ".[dev]"

# Basic installation
pip install -e .

Requirements

  • Python 3.10+
  • FastAPI, Uvicorn (for API server)
  • Modbus support (for hardware communication)

Quick Start

Start the API Server

# Start with real hardware
oqlos-server --port 8200

# Run with mock hardware (development/testing)
OQLOS_HARDWARE_MODE=mock oqlos-server --port 8200

Run a Scenario (OQL v3 — flat syntax)

from oqlos.core.interpreter import CqlInterpreter

source = """
SCENARIO: Test
DEVICE_TYPE: BA

GOAL:
  SET NAME 'Check'
  SET pompa-1 5.0 l/min
  WAIT 500ms
  GET AI01
  IF AI01 0.5 .. 0.8 V
  CORRECT 'Voltage OK'
  ERROR 'Voltage out of range'
  SAVE high-voltage
"""

interp = CqlInterpreter(mode="dry-run")
result = interp.run(source, "test.oql")
print(result.ok)  # True if successful

OQL v3 is a flat, quote-free syntax with 12 base commands (SET, GET, WAIT, SAVE, CHECK, MIN, MAX, SAMPLE, LOG, ERROR, CALL, INCLUDE). See docs/oql-spec.md for the full specification and oqlos/scenarios/OQL-CHEATSHEET.md for a quick reference. The interpreter still parses legacy v1/v2 scripts with quoted identifiers for backward compatibility.

Package Structure

oqlos/
├── core/
│   ├── interpreter.py   # CqlInterpreter — main execution engine
│   ├── oql_parser.py    # OQL v3 flat parser (12 base commands)
│   ├── _oql_adapter.py  # v3 AST → legacy CqlDocument bridge (+ INCLUDE/MACRO)
│   ├── cql_parser.py    # Legacy v1/v2 parser (dispatches to v3 on detection)
│   └── …
├── models/              # Data models (dsl_models, scenario, execution, peripheral)
├── hardware/            # Hardware abstraction (Modbus, HTTP adapters, …)
├── api/                 # FastAPI REST server and routes
├── executor/            # Scenario execution helpers
├── scenarios/           # Scenario files (.oql) — all in v3 flat syntax
│   ├── lib/             # Macro libraries (hardware.oql, peripherals.oql)
│   └── examples/        # Didactic examples
└── shared/              # Utilities (logger, config, version)

Core Components

CqlInterpreter

The main execution engine for OQL scenarios:

from oqlos.core.interpreter import CqlInterpreter

# Modes: "dry-run", "execute", "validate"
interp = CqlInterpreter(
    mode="dry-run",
    firmware_url="http://localhost:8202",
    quiet=False
)

result = interp.run(source_code, filename)
# result.ok: bool — execution success
# result.events: list — execution trace
# result.variables: dict — captured variables

Parser

Auto-detecting parser pipeline:

  1. parse_cql(source, filename) first checks the source with is_flat_oql().
  2. If the source uses v3 flat grammar (GOAL: + SET NAME, no quotes, INCLUDE "..."), it dispatches to parse_flat_oql() which returns a legacy CqlDocument via oqlos/core/_oql_adapter.py (INCLUDE + MACRO/CALL expansion happens here).
  3. Otherwise the legacy state-machine parser handles it.
from oqlos.core.cql_parser import parse_cql
from oqlos.core.oql_parser import parse_oql

doc = parse_cql(source, "test.oql")      # either path
raw = parse_oql(source, "test.oql")      # just the v3 AST (OqlDoc)

API Endpoints

When running oqlos-server:

Endpoint Method Description
/api/hardware/peripherals GET List connected hardware
/api/scenarios GET List available scenarios
/api/scenarios/{id}/run POST Execute a scenario
/health GET Health check

OQL Scenario Format (v3 Flat Syntax)

OQL scenarios describe hardware tests with a minimal set of 12 base commands: SET, GET, WAIT, SAVE, CHECK, MIN, MAX, SAMPLE, LOG, ERROR, CALL, INCLUDE — plus block headers GOAL, CONFIG and MACRO. Full specification: docs/oql-spec.md.

SCENARIO: PSS 7000 Mask Test
DEVICE_TYPE: BA
DEVICE_MODEL: PSS 7000
MANUFACTURER: Dräger

INCLUDE "lib/peripherals.oql"

CONFIG reset:
  CALL init-all

GOAL:
  SET NAME 'Visual inspection'
  SET valve-nc 1
  WAIT 2s
  GET AI01
  IF AI01 0.60 .. 0.67 V
  CORRECT 'NC voltage in range'
  ERROR 'NC voltage out of range'
  SAVE nc-voltage-reading

Key rules:

  • Identifiers are bare — no surrounding quotes (pump-main, not 'pump-main'). For names with spaces use brackets: SET [pompa głównego obiegu] 5 l/min.
  • GOAL name set via SET NAME — use GOAL: followed by SET NAME 'nazwa' inside the block. Legacy GOAL name: still works for backward compatibility.
  • No IF/ELSE/ENDIF — use IF min .. max unit with CORRECT/ERROR messages for range assertions, or split into multiple GOAL blocks for sequencing.
  • Unicode is welcomeciśnienie-NC, °C, %RH, μV, m³/h

CONFIG Blocks

CONFIG blocks are semantically identical to GOAL but marked [CONFIG] in logs — convention for initialization and cleanup:

SCENARIO: System Startup
DEVICE_TYPE: BA

INCLUDE "lib/peripherals.oql"

CONFIG safety-initialization:
  CALL init-pump
  CALL init-valves-main
  WAIT 500ms

CONFIG pump-calibration:
  # 10 l/min corresponds to 100% PWM by default
  SET PUMP_FLOW_FULL_SCALE_LPM 10.0

GOAL:
  SET NAME 'Voltage test'
  SET valve-nc 1
  WAIT 1s
  GET AI01
  SAVE voltage-test

Macros and INCLUDE

Reusable sequences live in oqlos/scenarios/lib/ and are pulled in with INCLUDE. Positional arguments use $1, $2, … placeholders:

INCLUDE "lib/hardware.oql"

MACRO pump-ramp:
  SET pump-main $1 l/min
  WAIT $2
  SET pump-main 0

GOAL:
  SET NAME 'Smoke'
  CALL pump-ramp 5 2s
  CALL hw-valves-smoke
  CALL hw-sensors-baseline

Running Scenarios

# Dry-run (validate and simulate)
oqlctl scenarios/config-peripherals.oql --mode dry-run

# Execute on real hardware
oqlctl scenarios/config-peripherals.oql --mode execute

# Execute with custom firmware URL
oqlctl scenarios/config-peripherals.oql \
  --firmware-url http://localhost:8202 \
  --mode execute

# Fastest single-command hardware execution (v3 syntax)
oqlctl cmd "SET pompa-1 0"

# Single command without touching hardware
oqlctl cmd "SET pompa-1 0" --mode dry-run

# Validate every .oql in a directory tree
oqlctl --validate-dir oqlos/scenarios

Use cmd when you want to send a single OQL line to the firmware; use a file path when the action requires multiple steps.

CLI Output Example

📋 CQL: Konfiguracja Peryferii
🔧 Device: BA / PSS 7000
🎯 GOAL: [CONFIG] init-pompa
  ⚙️ SET [pump-main] = [0]
  ⚙️ SET [pompa-1] = [0]
  ⏳ WAIT 0.5s
  ✅ [passed] [CONFIG] init-pompa
🎯 GOAL: [CONFIG] init-zawory-nc
  ⚙️ SET [valve-nc] = [0]
  ...
✅ Konfiguracja Peryferii: 10/10 passed

Supported Hardware

  • Valves: valve-1 through valve-14, valve-nc, valve-sc, valve-wc (Modbus RTU via /dev/ttyACM1 @ 19200 8N1)
  • Pump: pump-main (DRI0050 PWM motor driver via HTTP :49055)
  • Artificial lung: lung-main (Tic T249 stepper via HTTP :8205)
  • Sensors: AI01 (NC), AI02 (SC), AI03 (WC) (piADC ADS1115 via HTTP :8204; raw ADC voltage)

Hardware Adapters

Adapter Class Protocol Default URL
Motor (pump) _DRI0050MotorAdapter HTTP POST /api/speed http://localhost:49055
Lung (artificial lung) _Tic249LungAdapter HTTP POST /api/lung http://localhost:8205
Valves _ModbusAdapter Modbus RTU (pymodbus) /dev/ttyACM1 serial
Sensors _PiAdcAdapter HTTP GET /api/v1/hardware/sensor/{id} http://localhost:8204

Hardware Identification & Diagnostics

The /api/v1/hardware/identify endpoint returns the adapter registry, live probe status, and a diagnostics block with:

  • USB device inventory
  • Serial port inventory (ttyACM* and ttyUSB*)
  • I2C bus inventory (/dev/i2c-*)
  • Best-effort bridge health snapshot for piadc, motor, lung, and modbus

The current valve calibration flow uses raw piADC voltage windows in the test scenario oqlos/oqlos/scenarios/test-zaworu.oql, while hardware-valves-smoke.oql only verifies basic open/close actuation.

Environment Variables

Variable Default Description
OQLOS_HARDWARE_MODE mock mock or real
MOTOR_URL http://localhost:49055 DRI0050 motor service
LUNG_MOTOR_URL http://localhost:8205 Tic T249 lung service
PIADC_URL http://localhost:8080 piADC sensor service
MODBUS_SERIAL_PORT /dev/ttyACM1 Modbus RTU serial port
MODBUS_BAUD_RATE 19200 Modbus baud rate
PUMP_FLOW_FULL_SCALE_LPM 10 Flow rate that maps to 100% PWM for pompa 1

Docker Deployment

# Development
docker-compose -f docker/docker-compose.dev.yml up

# Production
docker-compose -f docker/docker-compose.prod.yml up -d

Testing

# Run all tests (96 passing)
pytest

# Run with coverage
pytest --cov=oqlos

# Run specific test file
pytest tests/test_interpreter.py -v

# Run OQL scenarios (dry-run)
python -m oqlos.core.interpreter scenarios/test-pompy.oql --mode dry-run

Status: 96 tests passing, 3 scenarios (12/12 goals), CC̄≤15, 0 violations

Documentation

License

Licensed under Apache-2.0.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

oqlos-0.1.2.tar.gz (169.4 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

oqlos-0.1.2-py3-none-any.whl (192.9 kB view details)

Uploaded Python 3

File details

Details for the file oqlos-0.1.2.tar.gz.

File metadata

  • Download URL: oqlos-0.1.2.tar.gz
  • Upload date:
  • Size: 169.4 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.13.7

File hashes

Hashes for oqlos-0.1.2.tar.gz
Algorithm Hash digest
SHA256 aaa3eb5586bd9f614a0bca6b674b20a6587cf775ace52a6d59b02477a5f6513a
MD5 607fbe2bb17e0538d5eae053dae5ba2c
BLAKE2b-256 2d89199a8599a6e8a4d5d718ff293274e9ff1fe3a38566e81a0f1b9c8d521fe3

See more details on using hashes here.

File details

Details for the file oqlos-0.1.2-py3-none-any.whl.

File metadata

  • Download URL: oqlos-0.1.2-py3-none-any.whl
  • Upload date:
  • Size: 192.9 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.13.7

File hashes

Hashes for oqlos-0.1.2-py3-none-any.whl
Algorithm Hash digest
SHA256 a2066878c251a86aaa326b2d14edf1f82a9cda38892020c4a0a4e85b3cf8bf75
MD5 4929a956dcbd6c0ffbf2ed26c3248c6c
BLAKE2b-256 fc0d5d6aa0b1ac5b230158bd10250797e57405ef7d16b6c317d1973f6c45a15c

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page