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

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:

# In stand-by mode, the nanoOCM is in low-power mode and only the serial interface is active.

# In idle mode, the nanoOCM is ready to perform channel scans.

# In one-shot mode, the nanoOCM performs a single channel scan and returns to idle mode after the scan is complete.

# In continuous mode, the nanoOCM performs channel scans continuously until the mode is changed.



# 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

ocm.write_reg_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

channel_powers = ocm.read_reg_scan_data()

channel_center_frequencies = ocm.read_reg_scan_frequencies()



# 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()

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()