Quick start guide
This section demonstrates how to use various functionalities provided by the nanoOCM Python API to interact with nanoOCM devices.
Connecting to a Device
# Import the api module, which provides the OCM class to interact with nanoOCM devices
from photonpath.nanoocm import api
# Get available serial ports (optional)
ports = api.OCM.get_serial_ports()
print("Available serial ports:", ports)
# Create an OCM object specifying the connection port and the desired baud rate
ocm = api.OCM(com_port="COM1", baud_rate=api.SerialBaudRates.Baud_115200)
# Open the connection
ocm.connect()
# Check if the connection is open
if ocm.is_connected():
print("Connected to device")
else:
print("Failed to connect to device")
# Close the connection when done
ocm.disconnect()
Reading a Register
from photonpath.nanoocm import api
# Connect to the device
ocm = api.OCM(com_port="COM1", baud_rate=api.SerialBaudRates.Baud_115200)
ocm.connect()
# Read the device serial number from the SerNo register
serial_number = ocm.read_reg_ser_no()
print("Device serial number:", serial_number)
# Read the current baud-rate and the available baud-rates from the IOCap register
current_baud, max_baud = ocm.read_reg_io_cap()
print("Current baud rate:", current_baud)
print("Max supported baud rate:", max_baud)
# Close the connection when done
ocm.disconnect()
Writing to a Register
from photonpath.nanoocm import api
# Connect to the device
ocm = api.OCM(com_port="COM1", baud_rate=api.SerialBaudRates.Baud_115200)
ocm.connect()
# Set the device baud rate to 9600
# Note: The device will be disconnected and reconnected at the new baud rate
ocm.write_reg_io_cap(api.SerialBaudRates.Baud_9600, auto_switch_baud=True)
# Close the connection when done
ocm.disconnect()
Storing non-volatile configuration
from photonpath.nanoocm import api
# All configuration registers of the nanoOCM marked as "non-volatile" can be stored to the non-volatile memory of the device
# The non-volatile memory is used to store the configuration of the device across power cycles
# The content of the registers is not stored automatically and the host must explicitly store the configuration to the non-volatile memory
# This is done by writing the GenCfg register
# When the GenCfg register is written, the nanoOCM will store the content of all non-volatile registers to the non-volatile memory
# Registers that are marked as "non-volatile" include: IOCap, FCaseTemp, WCaseTemp, ALMT, BootScanMode, BootGridIdx, ScanRate, InputLosses, and CrosstalkSuppression
# Other registers automatically store their content to the non-volatile memory when they are written: GridMem
# Connect to the device
ocm = api.OCM(com_port="COM1", baud_rate=api.SerialBaudRates.Baud_115200)
ocm.connect()
# Set the device baud rate to 9600
# Note: The device will be disconnected and reconnected at the new baud rate
ocm.write_reg_io_cap(api.SerialBaudRates.Baud_9600, auto_switch_baud=True)
# Store the current configuration to the non-volatile memory
# Note: the api will automatically wait for the long (pending) write operation to complete
ocm.write_reg_gen_cfg(wait_pending=True)
# Close the connection when done
ocm.disconnect()
Rebooting the device
from photonpath.nanoocm import api
# Connect to the device
ocm = api.OCM(com_port="COM1", baud_rate=api.SerialBaudRates.Baud_115200)
ocm.connect()
# Reboot the device
# Note: the api will automatically reconnect to the device after reboot
ocm.write_reg_reboot(auto_reconnect=True)
# Close the connection when done
ocm.disconnect()
Uploading a channel plan configuration to the device: 100 GHz fixed-grid example
from photonpath.nanoocm import api
# Initialize a new channel plan configuration
# Note: The channel plan configuration is a list of channel center frequency indexes, channel bandwidth indexes, and Loss Of Signal (LOS) thresholds
# List of channel center frequency indexes on the standard frequency grid with 6.25 GHz spacing.
# n=0 corresponds to 193.1 THz.
n = []
# List of channel bandwidth indexes on the standard frequency grid with 12.5 GHz spacing.
# m=1 corresponds to a 12.5 GHz channel bandwidth.
m = []
# List of channel Loss Of Signal (LOS) thresholds in dBm.
# If a channel's power level falls below the LOS threshold, the nanoOCM will report a LOS alarm.
los = []
# Create a fixed-grid 100 GHz channel plan over the entire C-band
# For 100 GHz grid, there are 48 channels in the C-band (191.3 THz to 196 THz)
# Channel spacing is 16 points on the standard frequency grid (6.25 GHz)
channel_spacing = 16
# The first channel is 18 channels before the center channel at 193.1 THz
first_channel = -18
# The last channel is 29 channels after the center channel at 193.1 THz
last_channel = 29
# Channel bandwidth is 8 points on the standard frequency grid (12.5 GHz)
channel_bandwidth = 8
# LOS threshold is set at -45 dBm for all channels
los_threshold = -45
# Fill the channel plan configuration lists
for idx in range(first_channel, last_channel + 1):
n.append(idx * channel_spacing)
m.append(channel_bandwidth)
los.append(los_threshold)
# Connect to the device
ocm = api.OCM(com_port="COM1", baud_rate=api.SerialBaudRates.Baud_115200)
ocm.connect()
# Select the device memory index to store the channel plan configuration
# Note: The nanoOCM device has 16 memory slots to store different channel plan configurations: 0-15
ocm.write_reg_grid_mem_idx(0) # Store to location 0
# Upload the channel plan configuration to the device
# The nanoOCM will automatically store the new configuration to the selected non-volatile memory index
ocm.write_reg_grid_mem(n, m, los)
# Read back the channel plan configuration from the device (optional)
ocm.write_reg_grid_mem_idx(0) # Select the memory index to read from. Not necessary if the index is already selected.
dev_n, dev_m, dev_los = ocm.read_reg_grid_mem()
print("Device channel plan configuration:", dev_n, dev_m, dev_los)
# Close the connection when done
ocm.disconnect()
Uploading a channel plan configuration to the device: 50 GHz fixed-grid example
from photonpath.nanoocm import api
# Initialize a new channel plan configuration
# Note: The channel plan configuration is a list of channel center frequency indexes, channel bandwidth indexes, and Loss Of Signal (LOS) thresholds
n = []
m = []
los = []
# Create a fixed-grid 50 GHz channel plan over the entire C-band
# For 50 GHz grid, there are 95 channels in the C-band (191.3 THz to 196 THz)
# Channel spacing is 8 points on the standard frequency grid (6.25 GHz)
channel_spacing = 8
# The first channel is 36 channels before the center channel at 193.1 THz
first_channel = -36
# The last channel is 58 channels after the center channel at 193.1 THz
last_channel = 58
# Channel bandwidth is 4 points on the standard frequency grid (12.5 GHz)
channel_bandwidth = 4
# LOS threshold is set at -45 dBm for all channels
los_threshold = -45
# Fill the channel plan configuration lists
for idx in range(first_channel, last_channel + 1):
n.append(idx * channel_spacing)
m.append(channel_bandwidth)
los.append(los_threshold)
# Connect to the device
ocm = api.OCM(com_port="COM1", baud_rate=api.SerialBaudRates.Baud_115200)
ocm.connect()
# Select the device memory index to store the channel plan configuration
# Note: The nanoOCM device has 16 memory slots to store different channel plan configurations: 0-15
ocm.write_reg_grid_mem_idx(1) # Store to location 1
# Upload the channel plan configuration to the device
# The nanoOCM will automatically store the new configuration to the selected non-volatile memory index
ocm.write_reg_grid_mem(n, m, los)
# Read back the channel plan configuration from the device (optional)
ocm.write_reg_grid_mem_idx(1) # Select the memory index to read from. Not necessary if the index is already selected.
dev_n, dev_m, dev_los = ocm.read_reg_grid_mem()
print("Device channel plan configuration:", dev_n, dev_m, dev_los)
# Close the connection when done
ocm.disconnect()
Performing a channel scan on a single input port
from photonpath.nanoocm import api
# Optional: Import matplotlib for plotting the scan results. Requires matplotlib to be installed.
import matplotlib.pyplot as plt
# The nanoOCM has four mode of operation, called Scan Modes:
# - Stand-by mode: the nanoOCM is in low-power mode and only the serial interface is active.
# - Idle mode: the nanoOCM is ready to perform channel scans.
# - One-shot mode: the nanoOCM performs a single channel scan and returns to idle mode after the scan is complete.
# - Continuous mode: the nanoOCM performs channel scans continuously until the mode is changed.
# Input ports management:
# - If the device has multiple input ports, a different channel plan configuration can be assigned to each input port.
# - Each input port can be enabled or disabled for scanning independently of the other input ports.
# - When a channel scan is performed, the nanoOCM will scan all enabled input ports.
# - The last acquired data for each input port is always available at the ScanData and ScanFrequencies registers, even if the input port is currently disabled.
# - The InputPortsMode register can be used to modify the behaviour of One-shot and Continuous mode when multiple ports are enabled (see below).
# - By default, all input ports are enabled and assigned to the same channel plan configuration at memory index 0.
# Connect to the device
ocm = api.OCM(com_port="COM1", baud_rate=api.SerialBaudRates.Baud_115200)
ocm.connect()
# Switch nanoOCM from stand-by mode (default at power-up) to idle mode.
# It might take up to 10 seconds for the nanoOCM to switch to idle mode.
ocm.write_reg_scan_mode(api.ScanModes.Idle)
# Select the channel plan configuration to use for the scan for the first input port (index 0)
ocm.write_reg_active_grid_idx(input_port_idx=0, active_grid_idx=0) # Select channel plan at memory index 0
# Select the desired CrosstalkSuppression mode (enabled/disabled - True/False).
# The command is optional. The last memorized value is used if the command is not sent. Default factory setting is: enabled (True).
# When enabled, the nanoOCM will automatically detect and suppress channels with too much cross-talk from adjacent or non-adjacent channels.
# Limits for adjacent and non-adjacent channel cross-talk are defined in the device datasheet specifications.
ocm.write_reg_crosstalk_suppression(enable=True)
# Start a single channel scan
# The api will automatically wait for the scan to complete by polling the ScanMode register
ocm.write_reg_scan_mode(api.ScanModes.OneShot)
# Read the scan results for the first input port (index 0)
channel_powers = ocm.read_reg_scan_data(input_port_idx=0)
channel_center_frequencies = ocm.read_reg_scan_frequencies(input_port_idx=0)
# Print the scan results
for idx, (power, center_frequency) in enumerate(zip(channel_powers, channel_center_frequencies)):
print(f"Channel {idx}: {power} dBm - {center_frequency} GHz")
# Plot the scan results (optional)
plt.plot(channel_powers)
plt.xlabel("Channel index")
plt.ylabel("Power [dBm]")
plt.title("Channel scan results")
plt.show()
# Close the connection when done
ocm.disconnect()
Performing a channel scan on multiple input ports
from photonpath.nanoocm import api
# Optional: Import matplotlib for plotting the scan results. Requires matplotlib to be installed.
import matplotlib.pyplot as plt
# The nanoOCM has four mode of operation, called Scan Modes:
# - Stand-by mode: the nanoOCM is in low-power mode and only the serial interface is active.
# - Idle mode: the nanoOCM is ready to perform channel scans.
# - One-shot mode: the nanoOCM performs a single channel scan and returns to idle mode after the scan is complete.
# - Continuous mode: the nanoOCM performs channel scans continuously until the mode is changed.
# Input ports management:
# - If the device has multiple input ports, a different channel plan configuration can be assigned to each input port.
# - Each input port can be enabled or disabled for scanning independently of the other input ports.
# - When a channel scan is performed, the nanoOCM will scan all enabled input ports.
# - The last acquired data for each input port is always available at the ScanData and ScanFrequencies registers, even if the input port is currently disabled.
# - The InputPortsMode register can be used to modify the behaviour of One-shot and Continuous mode when multiple ports are enabled (see below).
# - By default, all input ports are enabled and assigned to the same channel plan configuration at memory index 0.
# Connect to the device
ocm = api.OCM(com_port="COM1", baud_rate=api.SerialBaudRates.Baud_115200)
ocm.connect()
# Switch nanoOCM from stand-by mode (default at power-up) to idle mode.
# It might take up to 10 seconds for the nanoOCM to switch to idle mode.
ocm.write_reg_scan_mode(api.ScanModes.Idle)
# Select the channel plan configuration to use for the scan for the first and second input ports (index 0, 1).
ocm.write_reg_active_grid_idx(input_port_idx=0, active_grid_idx=0) # Select channel plan at memory index 0.
ocm.write_reg_active_grid_idx(input_port_idx=1, active_grid_idx=0) # Select channel plan at memory index 0.
# Enable the input ports (needed only when changing the configuration, not at every scan).
# Example for a 4-ports device: enable ports 0 and 1, disable ports 2 and 3.
# The number of available ports can be queried with ocm.read_reg_input_ports_count().
ocm.write_reg_input_ports_mask([True, True, False, False])
# Set the InputPortsMode:
# - All: the nanoOCM will scan all enabled input ports at every scan (default).
# - Stepped: the nanoOCM will scan one enabled input port at a time.
# InputPortsMode has effect both in One-shot and Continuous scan modes:
# - One-Shot + All: the nanoOCM will scan all enabled input ports at every one-shot scan.
# - One-Shot + Stepped: the nanoOCM will scan one enabled input port at a time at every one-shot scan. The cycle starts back from the first enabled port after all enabled ports have been scanned.
# - Continuous + All: the nanoOCM will scan all enabled input ports continuously. The delay set in ScanInterval is applied only between full scans of all enabled ports, not between individual port scans.
# - Continuous + Stepped: the nanoOCM will scan one enabled input port at a time continuously. The delay set in ScanInterval is applied between each individual port scan.
ocm.write_reg_input_ports_mode(api.InputPortsModes.All) # Scan all enabled ports at every scan.
# Select the desired CrosstalkSuppression mode (enabled/disabled - True/False).
# The command is optional. The last memorized value is used if the command is not sent. Default factory setting is: enabled (True).
# When enabled, the nanoOCM will automatically detect and suppress channels with too much cross-talk from adjacent or non-adjacent channels.
# Limits for adjacent and non-adjacent channel cross-talk are defined in the device datasheet specifications.
ocm.write_reg_crosstalk_suppression(enable=True)
# Start a single channel scan
# The api will automatically wait for the scan to complete by polling the ScanMode register
ocm.write_reg_scan_mode(api.ScanModes.OneShot)
# Read the scan results for the first and second input ports (index 0, 1)
channel_powers = {}
channel_center_frequencies = {}
for port_idx in [0, 1]:
channel_powers[port_idx] = ocm.read_reg_scan_data(input_port_idx=port_idx)
channel_center_frequencies[port_idx] = ocm.read_reg_scan_frequencies(input_port_idx=port_idx)
# Print the scan results
for port_idx in [0, 1]:
for idx, (power, center_frequency) in enumerate(zip(channel_powers[port_idx], channel_center_frequencies[port_idx])):
print(f"Port {port_idx} - Channel {idx}: {power} dBm - {center_frequency} GHz")
# Plot the scan results (optional)
for port_idx in [0, 1]:
plt.figure(port_idx)
plt.plot(channel_powers[port_idx])
plt.xlabel("Channel index")
plt.ylabel("Power [dBm]")
plt.title(f"Port {port_idx+1} - Channel scan results")
plt.show()
# Close the connection when done
ocm.disconnect()
Handling execution errors and exceptions
from photonpath.nanoocm import api
try:
# Connect to the device
ocm = api.OCM(com_port="COM1", baud_rate=api.SerialBaudRates.Baud_115200)
ocm.connect()
# Attempt to read a register that is not supported by the device
ocm.read_register(0xFF)
except api.SerialConnectionError as e:
# Handle connection errors: device not found, port not available, etc.
print("Failed to read register:", e)
except api.SerialCommunicationError as e:
# Handle communication errors: host-to-module error reported by the device or module-to-host error detected by the API
# These errors are typically caused by an unstable connection
# The API will automatically attempt to recover from these errors by re-sending the command multiple times before raising an exception
print("Failed to read register:", e)
except api.SerialResponseError as e:
# Handle response errors: good, but unexpected response received from the device
print("Failed to read register:", e)
except api.SerialCommandError as e:
# Handle command errors: the device reported an error in response to the execution of the command
# This error is typically caused by an invalid command or invalid command parameters
# The API automatically polls the device NOP register to check the error cause and stores it in the error_code attribute of the exception
print("Failed to read register:", e)
except api.SerialPendingTimeoutError as e:
# Handle pending operation timeout errors: the device did not complete a long (pending) operation within the specified timeout
# The API will wait for the operation to complete before raising an exception
print("Failed to read register:", e)
# Close the connection when done
ocm.disconnect()
Reading the device status
from photonpath.nanoocm import api
# Connect to the device
ocm = api.OCM(com_port="COM1", baud_rate=api.SerialBaudRates.Baud_115200)
ocm.connect()
# Read the device NOP register to check if the device is responding
# The NOP register returns three values: last command execution error, module ready flag, pending flags
# The NOP register is automatically used by the API to check the device status after an execution error or a pending operation are reported by the device
last_error, module_ready, pending_flags = ocm.read_reg_nop()
print("Device status:", last_error, module_ready, pending_flags)
# Read the device warning status register
# The warning status register contains flags for various warning conditions such as high temperature, low input power, etc.
status_w = ocm.read_reg_status_w()
print("Device warning status flags:", status_w)
# Read the device fatal status register
# The fatal status register contains flags for various fatal conditions such as high temperature, bad temperature stabilization, etc.
status_f = ocm.read_reg_status_f()
print("Device fatal status flags:", status_f)
# Clear warning and fatal status register latched flags
# The latched flags are set by the device when a warning condition occurs and can be cleared by the host to reset the monitoring of the condition
ocm.write_reg_status_w()
ocm.write_reg_status_f()
# Read the module case temperature in °C
case_temperature = ocm.read_reg_case_temp()
print(f"Module case temperature: {case_temperature} °C")
# Close the connection when done
ocm.disconnect()
Changing the ALARM triggers
from photonpath.nanoocm import api
# Connect to the device
ocm = api.OCM(com_port="COM1", baud_rate=api.SerialBaudRates.Baud_115200)
ocm.connect()
# The nanoOCM can detect various fatal or warning conditions and report them to the host using the StatusW and StatusF registers
# The nanoOCM can also trigger an ALARM signal on the ALARM pin when some fatal or warning conditions are detected
# The ALMT register (Alarm Triggers) is used to configure the conditions that trigger the ALARM signal
# Read the current ALARM triggers
alarm_triggers = ocm.read_reg_almt()
print("Current ALARM triggers:", alarm_triggers)
# Set the ALARM triggers to report only an out of range case temperature (both warning and fatal conditions)
ocm.write_reg_almt(AlarmTriggers(FCaseTemp=True, WCaseTemp=True))
# Change the case temperature ranges for fatal and warning conditions
ocm.write_reg_w_case_temp(-10, 50) # Set the warning case temperature range to -10°C to 50°C
ocm.write_reg_f_case_temp(-20, 60) # Set the fatal case temperature range to -20°C to 60°C
# Close the connection when done
ocm.disconnect()