srsRAN Integration Example ========================== This example demonstrates how to integrate **ai_nn_controller** with a real wireless RAN system — `srsRAN `_ — by bridging its InfluxDB metrics server into the ai_nn_controller message bus. It highlights the framework's **multi-domain** capability: the same controller that manages optical amplifiers and ROADMs can simultaneously ingest live 5G RAN KPIs and expose them to AI agents. Overview -------- srsRAN ships with a metrics server that writes UE-level and system-level KPIs to an InfluxDB bucket (the same data source Grafana dashboards use). The integration adds two components: 1. **srsRAN Network Node** (``network_nodes/srsran_node/``) — polls InfluxDB for the latest metrics and pushes them into the ai_nn_controller message bus. 2. **A read-only control application** — subscribes to those measurements and exposes them via the REST API and MCP tools. See :ref:`srsran-read-app`. .. code-block:: text ┌──────────────┐ ┌──────────────┐ ┌──────────────────────────┐ │ srsRAN │ │ │ │ ai_nn_controller Message Bus │ │ gNodeB / │──────►│ InfluxDB │◄──────│ │ │ Metrics │ write │ │ poll │ srsRAN Node (ID=10) │ │ Server │ └──────────────┘ │ │ │ └──────────────┘ │ ▼ PUSH measurements │ │ Broker ──► PUB │ │ │ │ │ ┌─────────▼────────┐ │ │ │ SrsranReadMeasure │ │ │ │ ments App │ │ │ │ (SUB, REST, MCP) │ │ │ └──────────────────┘ │ └──────────────────────────┘ Available Metrics ----------------- The srsRAN node exposes two categories of measurements: **UE-Level Metrics** (from ``ue_info`` InfluxDB measurement): .. list-table:: :header-rows: 1 :widths: 30 70 * - Metric - Description * - ``pci`` - Physical Cell ID * - ``rnti`` - Radio Network Temporary Identifier * - ``dl_bitrate`` - Downlink bitrate * - ``ul_bitrate`` - Uplink bitrate * - ``dl_bler`` - Downlink Block Error Rate * - ``ul_bler`` - Uplink Block Error Rate * - ``dl_mcs`` - Downlink Modulation and Coding Scheme * - ``ul_mcs`` - Uplink Modulation and Coding Scheme * - ``dl_nof_ok`` / ``dl_nof_nok`` - Downlink successful / failed transmissions * - ``ul_nof_ok`` / ``ul_nof_nok`` - Uplink successful / failed transmissions * - ``bsr`` - Buffer Status Report * - ``cqi`` - Channel Quality Indicator * - ``ri`` - Rank Indicator * - ``ul_snr`` - Uplink SNR * - ``pusch_snr_db`` - PUSCH SNR (dB) * - ``pucch_snr_db`` - PUCCH SNR (dB) **System-Level Metrics** (from ``app_resource_usage`` InfluxDB measurement): .. list-table:: :header-rows: 1 :widths: 30 70 * - Metric - Description * - ``cpu_usage_percent`` - CPU usage of the srsRAN process * - ``memory_usage_MB`` - Memory consumption (MB) * - ``power_consumption_Watts`` - Estimated power consumption (W) srsRAN Network Node ------------------- The node (``network_nodes/srsran_node/node.py``) is built on the ``controlled_entity`` framework -- the same base class and decorator pattern used by the dummy nodes. The key difference is the ``setup()`` hook, which initializes an InfluxDB poller thread that queries the srsRAN metrics bucket. This demonstrates how to integrate a real external data source using the ``ControlledEntity`` abstraction. The node developer only implements the southbound logic; all ZMQ plumbing is handled by ``NodeRunner``. .. code-block:: python from controlled_entity import ControlledEntity, node, NodeRunner import threading @node(name="srsRAN") class SrsranNode(ControlledEntity): available_measurements = [ "session_id", "pci", "rnti", "dl_bitrate", "ul_bitrate", "dl_bler", "ul_bler", "cqi", "ul_snr", "cpu_usage_percent", "memory_usage_MB", # ... full list in source ] measurement_interval = 1.0 def setup(self): """Start InfluxDB poller thread -- runs after registration.""" self._latest_metrics = {} self._metrics_lock = threading.Lock() self._influx_url = self.config.get("influxdb_url", "http://influxdb:8086") self._influx_bucket = self.config.get("influxdb_bucket", "srsran") thread = threading.Thread(target=self._poll_influxdb, daemon=True) thread.start() def poll_measurements(self): with self._metrics_lock: current = dict(self._latest_metrics) if current: current["session_id"] = f"session_{self.config['node_id']}_{int(time.time())}" return current return None if __name__ == "__main__": NodeRunner().run() **Configuration** (``node.conf``): .. code-block:: ini ip_address = aic_register register_port = 5558 node_id = 10 pub_port = 5580 # InfluxDB connection (must match srsRAN metrics-server config) influxdb_url = http://influxdb:8086 influxdb_token = influxdb_org = srs influxdb_bucket = srsran poll_interval = 1 All custom keys (``influxdb_url``, ``influxdb_bucket``, etc.) are accessible via ``self.config`` in the node's ``setup()`` method. **How it works:** 1. ``@node(name="srsRAN")`` registers the class with the ``controlled_entity`` framework 2. ``NodeRunner().run()`` handles registration with ``aic_register`` as node ID 10 3. After registration, ``setup()`` initializes the InfluxDB connection and starts a background poller thread 4. The poller queries two InfluxDB measurements: ``ue_info`` and ``app_resource_usage`` 5. ``poll_measurements()`` returns the cached metrics every ``measurement_interval`` seconds 6. ``NodeRunner`` publishes the measurements to the broker via ZMQ PUSH .. code-block:: python # Core InfluxDB query (from node.py) ue_query = f''' from(bucket: "{self.influxdb_bucket}") |> range(start: -30s) |> filter(fn: (r) => r._measurement == "ue_info") |> last() ''' .. _srsran-read-app: srsRAN Read Measurements App ----------------------------- The read-only control application subscribes to all srsRAN node metrics and prints them. This follows exactly the same pattern as any other ai_nn_controller app: .. code-block:: python from ai_nn_controller.decorators.aic_app import aic_app from ai_nn_controller.AicApp import AicApp from ai_nn_controller.AicController import AicController @aic_app(name="SrsranReadMeasurements") class SrsranReadMeasurementsApp(AicApp): aic_app_id = 10 control_loop_update_time = 2 # Subscribe to all srsRAN metrics from node 10 read_measurements = { 10: [ "session_id", "pci", "rnti", "dl_bitrate", "ul_bitrate", "dl_bler", "ul_bler", "dl_mcs", "ul_mcs", "dl_nof_ok", "dl_nof_nok", "ul_nof_ok", "ul_nof_nok", "bsr", "cqi", "ri", "ul_snr", "pusch_snr_db", "pucch_snr_db", "cpu_usage_percent", "memory_usage_MB", "power_consumption_Watts", ], } # Read-only -- no control functions control_functions = {} @classmethod def process(cls, measurements): latest = measurements.get(10, [None])[-1] if measurements.get(10) else None if not latest: print("No srsRAN data yet") return print(f"DL bitrate : {latest.get('dl_bitrate')}") print(f"CQI : {latest.get('cqi')}") print(f"CPU usage : {latest.get('cpu_usage_percent')}%") if __name__ == "__main__": AicController(with_api=True).run() Because this app is registered with the framework, the following are auto-generated: - **REST endpoint**: ``GET /apps/SrsranReadMeasurements/measurements`` - **MCP tool**: ``SrsranReadMeasurements_get_measurements`` This means an AI agent can query live 5G RAN KPIs via MCP alongside optical network metrics, all from the same controller. Docker Deployment ----------------- The srsRAN node is defined in ``network_nodes/srsran_node/`` and requires an active srsRAN deployment with InfluxDB. To add it to your stack, add the following to ``docker-compose.yml``: .. code-block:: yaml srsran_node: container_name: srsran_node build: context: ./network_nodes/srsran_node/ dockerfile: Dockerfile depends_on: - aic_register - node_msg_broker networks: - aic_network - docker_metrics # Access InfluxDB on the srsRAN metrics network command: > sh -c "sleep 5 && python3 node.py" srsran_reader: container_name: srsran_reader build: context: ./ dockerfile: control_applications/srsran_read_measurements/Dockerfile ports: - "8000:8000" networks: - aic_network command: > sh -c "sleep 25 && pip install --no-cache-dir /ai_nn_controller && python3 aic_app.py --verbose" networks: docker_metrics: external: true # Created by the srsRAN docker-compose .. note:: The ``docker_metrics`` network must already exist (created by the srsRAN Docker Compose stack). This allows the srsRAN node container to reach InfluxDB on its internal network. Expected Output --------------- When running, the srsRAN reader app prints measurements every 2 seconds: .. code-block:: text ====================================================================== [srsRAN Measurements] Processing at 1707609600.00 ====================================================================== session_id : session_10_1707609600 --- UE-Level Metrics --- pci : 1.0 rnti : 17921.0 dl_bitrate : 28500000.0 ul_bitrate : 12300000.0 dl_bler : 0.02 ul_bler : 0.01 dl_mcs : 27.0 ul_mcs : 22.0 cqi : 15.0 ri : 2.0 ul_snr : 25.3 pusch_snr_db : 24.8 pucch_snr_db : 23.1 --- System-Level Metrics --- cpu_usage_percent : 45.2 memory_usage_MB : 512.0 power_consumption_Watts: 35.0 ====================================================================== Key Patterns Demonstrated ------------------------- 1. **controlled_entity abstraction**: The srsRAN node is a ``ControlledEntity`` subclass, just like the dummy nodes. The only difference is the ``setup()`` hook that initializes the InfluxDB connection -- all ZMQ plumbing is handled by ``NodeRunner`` 2. **External data-source bridging**: The ``setup()`` pattern can be used for any external telemetry source (Prometheus, SNMP, gRPC streaming, REST APIs, etc.) 3. **Read-only apps**: Not every app needs control functions; pure monitoring apps expose data via REST and MCP without sending commands 4. **Multi-domain integration**: The same controller simultaneously manages optical nodes (amplifiers, ROADMs) and wireless nodes (srsRAN gNodeB), demonstrating cross-domain network intelligence 5. **Docker network bridging**: The ``docker_metrics`` external network lets the srsRAN node reach InfluxDB without exposing it on the host Next Steps ---------- - Add ``@agent_controlled`` operations to let an AI agent trigger RAN optimizations based on live KPIs (e.g., adjust scheduling weights when CQI drops) - Build a cross-domain app that correlates optical link quality with RAN throughput - Connect additional domain nodes (core network, transport) to create a fully converged multi-domain AI controller