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 okcontinues 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 okbecomesimport frontpanel. - Naming. PascalCase methods become
snake_case(SetWireInValue→set_wire_in_value), and the mixed-caseDataPortnormalizes toDataport(GetFPGADataPortClassic→get_fpga_dataport_classic). Established acronyms stay uppercase —FPGA,AXI,I2C. - Object model. Instead of one
okCFrontPanelobject carrying everything, the device is decomposed: opening is done by aDeviceManager, configuration lives on anFPGAConfiguration, and wires/triggers/pipes/registers live on the classic data port. - Resource management. The opened device is a context manager — a
withblock closes it for you instead of an explicitClose(). - Errors. Operations raise a typed
frontpanel.FrontPanelErroron failure instead of returning anErrorCodeyou must check. - Buffers. Pipe reads return a fresh
bytesobject sized to what was read, instead of filling abytearrayyou pre-allocate.
Quick Reference: Old → New
| Task | Binding API (ok) | Idiomatic API (frontpanel) |
|---|---|---|
| Import | import ok | import frontpanel |
| Open first device | xem = ok.FrontPanelDevices().Open() | device = frontpanel.DeviceManager().open_device() |
| Close device | xem.Close() | with ... as device: (automatic) |
| Read device info | info = ok.okTDeviceInfo(); xem.GetDeviceInfo(info) | info = device.get_device_info() |
| — field access | info.serialNumber, info.productName | info.serial_number, info.product_name |
| Configure FPGA | xem.ConfigureFPGA("d.bit") | device.get_fpga_configuration().load_configuration_from_file("d.bit") |
| FrontPanel enabled? | xem.IsFrontPanelEnabled() | device.is_frontpanel_enabled() |
| Get classic data port | xem.GetFPGADataPortClassic() | device.get_fpga_dataport_classic() |
| Set / send wire in | SetWireInValue(...), UpdateWireIns() | set_wire_in_value(...), update_wire_ins() |
| Read wire out | UpdateWireOuts(), GetWireOutValue(0x20) | update_wire_outs(), get_wire_out_value(0x20) |
| Trigger in / out | ActivateTriggerIn(...), IsTriggered(...) | activate_trigger_in(...), is_triggered(...) |
| Write pipe | WriteToPipeIn(0x80, data) | write_to_pipe_in(0x80, data) |
| Read pipe | ReadFromPipeOut(0xA0, buf) (fills buf) | read_from_pipe_out(0xA0, length) (returns bytes) |
| Registers | ReadRegister(a), WriteRegister(a, v) | read_register(a), write_register(a, v) |
| Error handling | if ... != 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
- Idiomatic API mirrors the FrontPanel Platform (TypeScript) API — synchronous and
snake_casehere, asynchronous (Promises) andcamelCasethere. - Binding API mirrors the C++ API reference name-for-name.
- See also Using FrontPanel from Python for installation and a side-by-side introduction to both APIs.