Python Migration Guide

This guide covers moving an existing FrontPanel 6 Python application from the Binding API (import ok) to the Idiomatic API (import frontpanel).

Both APIs ship with the FrontPanel SDK and target the same devices. This is a change of style, not of version — if you are upgrading a FrontPanel 5.x application to 6.x, do that first with the Python FP6 migration guide.

The Binding API is not going away. import ok continues to work exactly as before, with no deprecation. The Idiomatic API is an additive layer built on top of it, so you can migrate one module at a time and run both side by side in the same program.

Overview

The Idiomatic API keeps the same underlying operations but repackages them the way Python developers expect:

  • Package. import ok becomes import frontpanel.
  • Naming. PascalCase methods become snake_case (SetWireInValueset_wire_in_value), and the mixed-case DataPort normalizes to Dataport (GetFPGADataPortClassicget_fpga_dataport_classic). Established acronyms stay uppercase — FPGA, AXI, I2C.
  • Object model. Instead of one okCFrontPanel object carrying everything, the device is decomposed: opening is done by a DeviceManager, configuration lives on an FPGAConfiguration, and wires/triggers/pipes/registers live on the classic data port.
  • Resource management. The opened device is a context manager — a with block closes it for you instead of an explicit Close().
  • Errors. Operations raise a typed frontpanel.FrontPanelError on failure instead of returning an ErrorCode you must check.
  • Buffers. Pipe reads return a fresh bytes object sized to what was read, instead of filling a bytearray you pre-allocate.

Quick Reference: Old → New

TaskBinding API (ok)Idiomatic API (frontpanel)
Importimport okimport frontpanel
Open first devicexem = ok.FrontPanelDevices().Open()device = frontpanel.DeviceManager().open_device()
Close devicexem.Close()with ... as device: (automatic)
Read device infoinfo = ok.okTDeviceInfo(); xem.GetDeviceInfo(info)info = device.get_device_info()
— field accessinfo.serialNumber, info.productNameinfo.serial_number, info.product_name
Configure FPGAxem.ConfigureFPGA("d.bit")device.get_fpga_configuration().load_configuration_from_file("d.bit")
FrontPanel enabled?xem.IsFrontPanelEnabled()device.is_frontpanel_enabled()
Get classic data portxem.GetFPGADataPortClassic()device.get_fpga_dataport_classic()
Set / send wire inSetWireInValue(...), UpdateWireIns()set_wire_in_value(...), update_wire_ins()
Read wire outUpdateWireOuts(), GetWireOutValue(0x20)update_wire_outs(), get_wire_out_value(0x20)
Trigger in / outActivateTriggerIn(...), IsTriggered(...)activate_trigger_in(...), is_triggered(...)
Write pipeWriteToPipeIn(0x80, data)write_to_pipe_in(0x80, data)
Read pipeReadFromPipeOut(0xA0, buf) (fills buf)read_from_pipe_out(0xA0, length) (returns bytes)
RegistersReadRegister(a), WriteRegister(a, v)read_register(a), write_register(a, v)
Error handlingif ... != ok.ErrorCode.NoError:try: ... except frontpanel.FrontPanelError:

Migration Steps

Step 1: Change the import and how you open a device

Device enumeration and opening move to DeviceManager. The opened Device is a context manager, so a with block replaces the explicit Close().

# Binding API:
import ok

xem = ok.FrontPanelDevices().Open()   # first available device
# ... use xem ...
xem.Close()

# Idiomatic API:
import frontpanel

manager = frontpanel.DeviceManager()
with manager.open_device() as device:   # closes automatically on exit
    ...                                  # use deviceCode language: PHP (php)

open_device() opens the first available device when called with no argument; pass a serial number to target a specific device (manager.open_device("1234567890")). DeviceManager also offers get_device_count() and get_serial_numbers() for enumeration.

Step 2: Read device info from the returned object

The Binding API fills an okTDeviceInfo struct you pass in. The Idiomatic API returns an immutable DeviceInfo with snake_case attributes.

# Binding API:
info = ok.okTDeviceInfo()
xem.GetDeviceInfo(info)
print(info.serialNumber, info.productName)

# Idiomatic API:
info = device.get_device_info()
print(info.serial_number, info.product_name)Code language: PHP (php)

Step 3: Configure the FPGA through FPGAConfiguration

Configuration moves off the device object onto a dedicated FPGAConfiguration, and failure is reported by an exception rather than a return code.

# Binding API:
if xem.ConfigureFPGA("my_design.bit") != ok.ErrorCode.NoError:
    raise SystemExit("FPGA configuration failed")

# Idiomatic API:
config = device.get_fpga_configuration()
try:
    config.load_configuration_from_file("my_design.bit")
except frontpanel.FrontPanelError as err:
    raise SystemExit(f"FPGA configuration failed: {err}")Code language: PHP (php)

FPGAConfiguration also provides load_configuration_from_memory(data), configure_from_flash(index), and clear_configuration().

Step 4: Move wire/trigger/pipe/register calls onto the classic data port

Get the classic data port from the device, then translate each call to snake_case. The parameters are unchanged.

# Binding API:
xem.SetWireInValue(0x00, 0x01)
xem.UpdateWireIns()
xem.ActivateTriggerIn(0x40, 0)
xem.UpdateWireOuts()
status = xem.GetWireOutValue(0x20)

# Idiomatic API:
classic = device.get_fpga_dataport_classic()
classic.set_wire_in_value(0x00, 0x01)
classic.update_wire_ins()
classic.activate_trigger_in(0x40, 0)
classic.update_wire_outs()
status = classic.get_wire_out_value(0x20)Code language: PHP (php)

Triggers and registers follow the same mechanical rename:

# Binding API:
classic_triggered = xem.IsTriggered(0x60, 0x01)
value = xem.ReadRegister(0x100)
xem.WriteRegister(0x100, 0xABCD)

# Idiomatic API:
classic_triggered = classic.is_triggered(0x60, 0x01)
value = classic.read_register(0x100)
classic.write_register(0x100, 0xABCD)Code language: PHP (php)

The Idiomatic API additionally offers dict-based bulk register access — read_registers([0x100, 0x104]) returns {address: value}, and write_registers({0x100: 0xABCD}) writes a mapping.

Step 5: Adopt the bytes convention for pipes

Pipe writes are unchanged — pass any bytes/bytearray. Pipe reads change shape: instead of pre-allocating a buffer and passing it in, you pass the number of bytes to read and receive a fresh bytes object sized to what was actually transferred.

# Binding API:
buf = bytearray(1024)
xem.ReadFromPipeOut(0xA0, buf)          # fills buf, returns a count
# ... buf now holds the data ...

# Idiomatic API:
data = classic.read_from_pipe_out(0xA0, 1024)   # returns bytes, len == bytes readCode language: PHP (php)

Block pipes work the same way, with the block size as the middle argument:

# Binding API:
buf = bytearray(1024)
xem.ReadFromBlockPipeOut(0xA0, 256, buf)
xem.WriteToBlockPipeIn(0x80, 256, data)

# Idiomatic API:
data = classic.read_from_block_pipe_out(0xA0, 256, 1024)   # returns bytes
classic.write_to_block_pipe_in(0x80, 256, data)            # returns count writtenCode language: PHP (php)

Step 6: Replace return-code checks with exception handling

Wherever the Binding API returned an ErrorCode you compared against ok.ErrorCode.NoError, the Idiomatic API raises frontpanel.FrontPanelError (which carries the same ErrorCode as .code). AXI transfers raise the more specific AXITransferError / AXIOperationError, both subclasses of FrontPanelError, so a single except frontpanel.FrontPanelError still catches everything.

# Binding API:
rc = xem.ConfigureFPGA("design.bit")
if rc != ok.ErrorCode.NoError:
    print("configure failed:", rc)
    return

# Idiomatic API:
try:
    device.get_fpga_configuration().load_configuration_from_file("design.bit")
except frontpanel.FrontPanelError as err:
    print("configure failed:", err.code)
    returnCode language: PHP (php)

Complete Example

The same program written both ways.

# Binding API:
import ok

xem = ok.FrontPanelDevices().Open()

info = ok.okTDeviceInfo()
xem.GetDeviceInfo(info)
print("Serial: ", info.serialNumber)
print("Product:", info.productName)

if xem.ConfigureFPGA("my_design.bit") != ok.ErrorCode.NoError:
    raise SystemExit("configuration failed")

classic = xem.GetFPGADataPortClassic()
classic.SetWireInValue(0x00, 0x01)
classic.UpdateWireIns()
classic.ActivateTriggerIn(0x40, 0)

xem.Close()Code language: PHP (php)
# Idiomatic API:
import frontpanel

manager = frontpanel.DeviceManager()

with manager.open_device() as device:
    info = device.get_device_info()
    print("Serial: ", info.serial_number)
    print("Product:", info.product_name)

    try:
        device.get_fpga_configuration().load_configuration_from_file("my_design.bit")
    except frontpanel.FrontPanelError as err:
        raise SystemExit(f"configuration failed: {err}")

    classic = device.get_fpga_dataport_classic()
    classic.set_wire_in_value(0x00, 0x01)
    classic.update_wire_ins()
    classic.activate_trigger_in(0x40, 0)Code language: PHP (php)

Migrating Incrementally

Because both APIs live on the same underlying SDK, you do not have to convert everything at once:

1. Add import frontpanel alongside your existing import ok. 2. Convert a self-contained module — device open, configuration, or one data path — to the Idiomatic API and test it against hardware. 3. Repeat module by module. Leave the rest on ok for as long as you like; there is no deprecation deadline.

Reference