Multi-Node Monitoring Example
This example demonstrates monitoring multiple network nodes and implementing basic control logic using ai_nn_controller. The framework supports any type of network node — optical, wireless, RAN, core network, and more.
Complete Code
"""
multi_node_app.py - Multi-node monitoring with control
Monitors multiple network nodes, sends corrections when needed.
This example uses optical network nodes, but the same pattern applies
to any network domain (wireless, RAN, core, etc.).
"""
import time
import commands # Register commands
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="MultiNodeController")
class MultiNodeController(AicApp):
"""
Controls multiple amplifiers and ROADMs in a network segment.
"""
aic_app_id = 101
control_loop_update_time = 3
# Monitor multiple nodes
read_measurements = {
3: [ # Amp1
"amp1_target_gain",
"amp1_gain_tilt",
],
5: [ # Amp2
"amp2_target_gain",
"amp2_gain_tilt",
],
8: [ # ROADM3
"roadm3_preamp_gain",
"roadm3_booster_gain",
],
}
# Control functions
control_functions = {
3: ["SET_GAIN"],
5: ["SET_GAIN"],
8: ["SET_GAIN", "SET_VOA"],
}
# cell_ids and send_commands are auto-generated by @aic_app
# cell_ids will be: [3, 5, 8]
# Configuration
TARGET_GAIN = 20.0
TOLERANCE = 2.0
# State tracking
last_correction = {}
@classmethod
def process(cls, measurements):
"""Monitor nodes and apply corrections."""
if not measurements:
print("[MultiNode] Waiting for measurements...")
return
current_time = time.time()
# Process each node
for node_id, data_list in measurements.items():
if not data_list:
continue
latest = data_list[-1]
cls._process_node(node_id, latest, current_time)
@classmethod
def _process_node(cls, node_id, data, current_time):
"""Process measurements from a single node."""
# Get the appropriate gain metric
gain_key = cls._get_gain_key(node_id)
if gain_key not in data:
return
current_gain = data[gain_key]
gain_error = current_gain - cls.TARGET_GAIN
print(f"[Node {node_id}] {gain_key}: {current_gain:.2f} dB (error: {gain_error:+.2f})")
# Check if correction needed
if abs(gain_error) > cls.TOLERANCE:
# Rate limit corrections (once per 10 seconds per node)
last_time = cls.last_correction.get(node_id, 0)
if current_time - last_time > 10:
cls._send_correction(node_id, current_gain)
cls.last_correction[node_id] = current_time
@classmethod
def _get_gain_key(cls, node_id):
"""Get the gain metric key for a node."""
mapping = {
3: "amp1_target_gain",
5: "amp2_target_gain",
8: "roadm3_preamp_gain",
}
return mapping.get(node_id, "unknown")
@classmethod
def _send_correction(cls, node_id, current_gain):
"""Send a gain correction command."""
print(f" [CORRECTION] Node {node_id}: {current_gain:.2f} -> {cls.TARGET_GAIN:.2f}")
# Determine amp_type based on node
amp_type = "preamp" if node_id == 8 else "line"
cls.add_command((
"SET_GAIN",
{
"node_id": node_id,
"value": {
"amp_type": amp_type,
"target_gain": cls.TARGET_GAIN
}
}
))
if __name__ == "__main__":
AicController(with_api=True, verbose=True).run()
Commands Module
Create commands.py in the same directory:
"""commands.py - Command definitions for multi-node controller."""
import json
from ai_nn_controller.registry import register_command
def set_gain_handler(node_id: int, value: dict) -> str:
return json.dumps({
"command": "SET_GAIN",
"amp_type": value.get("amp_type", "line"),
"target_gain": value.get("target_gain", 0)
})
SET_GAIN_SCHEMA = {
"description": "Set amplifier target gain",
"properties": {
"node_id": {"type": "integer"},
"amp_type": {"type": "string", "enum": ["line", "preamp", "booster"]},
"target_gain": {"type": "number"}
},
"required": ["node_id", "target_gain"]
}
def set_voa_handler(node_id: int, value: dict) -> str:
return json.dumps({
"command": "SET_VOA",
"attenuation": value.get("attenuation", 0)
})
SET_VOA_SCHEMA = {
"description": "Set variable optical attenuator",
"properties": {
"node_id": {"type": "integer"},
"attenuation": {"type": "number"}
},
"required": ["node_id", "attenuation"]
}
register_command("SET_GAIN", set_gain_handler, SET_GAIN_SCHEMA)
register_command("SET_VOA", set_voa_handler, SET_VOA_SCHEMA)
Running
# Start infrastructure
docker compose up -d
# Run the application
python multi_node_app.py --verbose
# Start via API
curl -X PUT http://localhost:8000/apps/MultiNodeController/state \
-H "Content-Type: application/json" \
-d '{"state": "running"}'
Output Example
[Node 3] amp1_target_gain: 23.50 dB (error: +3.50)
[CORRECTION] Node 3: 23.50 -> 20.00
[Node 5] amp2_target_gain: 19.80 dB (error: -0.20)
[Node 8] roadm3_preamp_gain: 25.00 dB (error: +5.00)
[CORRECTION] Node 8: 25.00 -> 20.00
Key Patterns
Multiple node subscriptions: Monitor several nodes simultaneously
State tracking: Use class attributes to track correction history
Rate limiting: Prevent command flooding with time-based throttling
Helper methods: Organize code with private methods
Node-specific logic: Handle different nodes differently