Error Handling

Various errors may occur when communicating with the IM7.

There is a version of the communication class that works with exceptions ZahnerLinkExc, and a version that works without exceptions ZahnerLink, requiring you to check the return value manually.

The other examples do not consider error handling, as it is usually the user’s responsibility to decide how to handle and resolve errors. This also depends on the application.

import os
import sys
import threading
import subprocess
import time
import zahner_link as zl

def print_error_object(error_object: zl.ErrorObject):
    print(f"ErrorObject: {error_object}")
    print(f"ErrorObject==True: {error_object==True}")
    print(f"ErrorObject code enum: {error_object.get_error_code_enum()}")
    print(f"ErrorObject message: {error_object.get_message_formatted()}")

def print_job_status(job: zl.AbstractMeasurementJob):
    print(f"job successfull: {job.was_successful()}")
    print(f"job status: {job.get_last_job_status()}")
    print(f"job error message: {job.get_last_job_error_message()}")
    info = job.get_last_job_info()
    print(f"JobInfo object id: {info.get_job_id()}")
    print(f"JobInfo object status: {info.get_status()}")
    print(f"JobInfo object status detail: {info.get_status_detail()}")
    print(f"JobInfo object error message: {info.get_error_message()}")
    print(f"JobInfo started: {info.get_start_date()}")

HOST = "10.10.253.150"
PORT = "1994"

Runtime Error

Runtime errors may occur due to incorrect operation of the IM7.

Exception Library

First, the exception-based library is used to explain the error output when a job fails.

The two helper functions print_error_object and print_job_status are used for this purpose.

link = zl.ZahnerLinkExc(HOST, PORT)
error: zl.ErrorObject = link.connect()

if not error:
    print("connected successfully")
else:
    print(f"failed to connect, status: {error.get_error_code_enum()}, message: {error.get_message_formatted()}")

switch_on_job = zl.control.SwitchOnJob(
    potentiostat="MAIN:1:POT",
    coupling=zl.PotentiostatCoupling.POTENTIOSTATIC,
    bias=1.0,
    voltage_range_index=0,
    compliance_range_index=0,
)
switch_off_job = zl.control.SwitchOffJob(potentiostat="MAIN:1:POT")

try:
    print("\nfirst switch on")
    error_object_success: zl.ErrorObject = link.do_job(switch_on_job)
    print_error_object(error_object_success)

    print("\nsecond switch on")
    error_object_fail: zl.ErrorObject = link.do_job(switch_on_job)
    print_error_object(error_object_fail)

except zl.ZahnerLinkException as e:
    print(f"\ncaught exception: {e}")
    error_object: zl.ErrorObject = e.error
    print_error_object(error_object)
    print_job_status(switch_on_job)

link.do_job(switch_off_job)
link.disconnect()
connected successfully

first switch on
ErrorObject: no error occoured
ErrorObject==True: False
ErrorObject code enum: ErrorCodeEnum.NONE
ErrorObject message: no error occoured

second switch on

caught exception: an unexpected exception occured: SystemException: type: 8; message: Potentiostat '37128:POT' must be switched off, before switched on.
ErrorObject: an unexpected exception occured: SystemException: type: 8; message: Potentiostat '37128:POT' must be switched off, before switched on.
ErrorObject==True: True
ErrorObject code enum: ErrorCodeEnum.UNEXPECTED_EXCEPTION
ErrorObject message: an unexpected exception occured: SystemException: type: 8; message: Potentiostat '37128:POT' must be switched off, before switched on.
job successfull: False
job status: JobStatusEnum.FAILED
job error message: an unexpected exception occured: SystemException: type: 8; message: Potentiostat '37128:POT' must be switched off, before switched on.
JobInfo object id: fbc13cec-0954-49fa-93fb-cb0d3901b2e1
JobInfo object status: JobStatusEnum.FAILED
JobInfo object status detail: JobStatusDetailEnum.RUNTIME_ERROR
JobInfo object error message: an unexpected exception occured: SystemException: type: 8; message: Potentiostat '37128:POT' must be switched off, before switched on.
JobInfo started: 2026-03-23T10:33:51.612501Z

No exception library

As you can see from the following output, the exception was not triggered.

You therefore have to manually check for success in your program flow.

link = zl.ZahnerLink(HOST, PORT)
error: zl.ErrorObject = link.connect()

if not error:
    print("connected successfully")
else:
    print(f"failed to connect, status: {error.get_error_code_enum()}, message: {error.get_message_formatted()}")

switch_on_job = zl.control.SwitchOnJob(
    potentiostat="MAIN:1:POT",
    coupling=zl.PotentiostatCoupling.POTENTIOSTATIC,
    bias=1.0,
    voltage_range_index=0,
    compliance_range_index=0,
)
switch_off_job = zl.control.SwitchOffJob(potentiostat="MAIN:1:POT")

try:
    print("\nfirst switch on")
    error_object_success: zl.ErrorObject = link.do_job(switch_on_job)
    print_error_object(error_object_success)

    print("\nsecond switch on")
    error_object_fail: zl.ErrorObject = link.do_job(switch_on_job)
    print_error_object(error_object_fail)

except zl.ZahnerLinkException as e:
    print(f"\ncaught exception: {e}")
    error_object: zl.ErrorObject = e.error
    print_error_object(error_object)    
    print_job_status(switch_on_job)

link.do_job(switch_off_job)
link.disconnect()
connected successfully

first switch on
ErrorObject: no error occoured
ErrorObject==True: False
ErrorObject code enum: ErrorCodeEnum.NONE
ErrorObject message: no error occoured

second switch on
ErrorObject: an unexpected exception occured: SystemException: type: 8; message: Potentiostat '37128:POT' must be switched off, before switched on.
ErrorObject==True: True
ErrorObject code enum: ErrorCodeEnum.UNEXPECTED_EXCEPTION
ErrorObject message: an unexpected exception occured: SystemException: type: 8; message: Potentiostat '37128:POT' must be switched off, before switched on.

Another example is when jobs are executed without a connection being established.

link = zl.ZahnerLink("definitely.wrong.hostname", PORT)
error: zl.ErrorObject = link.connect()

if not error:
    print("connected successfully")
else:
    print(f"failed to connect, status: {error.get_error_code_enum()}, message: {error.get_message_formatted()}")

switch_on_job = zl.control.SwitchOnJob(
    potentiostat="MAIN:1:POT",
    coupling=zl.PotentiostatCoupling.POTENTIOSTATIC,
    bias=1.0,
    voltage_range_index=0,
    compliance_range_index=0,
)
switch_off_job = zl.control.SwitchOffJob(potentiostat="MAIN:1:POT")

try:
    print("\nfirst switch on")
    error_object_success: zl.ErrorObject = link.do_job(switch_on_job)
    print_error_object(error_object_success)

    print("\nsecond switch on")
    error_object_fail: zl.ErrorObject = link.do_job(switch_on_job)
    print_error_object(error_object_fail)

except zl.ZahnerLinkException as e:
    print(f"\ncaught exception: {e}")
    error_object: zl.ErrorObject = e.error
    print_error_object(error_object)

link.do_job(switch_off_job)
link.disconnect()
failed to connect, status: ErrorCodeEnum.CONNECTION_DNS_ERROR, message: dns error: definitely.wrong.hostname:1994

first switch on
ErrorObject: no network connection has been established yet
ErrorObject==True: True
ErrorObject code enum: ErrorCodeEnum.CONNECTION_NOT_ESTABLISHED
ErrorObject message: no network connection has been established yet

second switch on
ErrorObject: no network connection has been established yet
ErrorObject==True: True
ErrorObject code enum: ErrorCodeEnum.CONNECTION_NOT_ESTABLISHED
ErrorObject message: no network connection has been established yet

Parameter Error

If the parameters of a job are incorrect, the library also returns an error message.

Only the exception-based library is shown here.

link = zl.ZahnerLinkExc(HOST, PORT)
error: zl.ErrorObject = link.connect()

if not error:
    print("connected successfully")
else:
    print(f"failed to connect, status: {error.get_error_code_enum()}, message: {error.get_message_formatted()}")

switch_on_job = zl.control.SwitchOnJob(
    potentiostat="MAIN:1:POT",
    coupling=zl.PotentiostatCoupling.POTENTIOSTATIC,
    bias=1.0,
    voltage_range_index=0,
    compliance_range_index=0,
)
switch_off_job = zl.control.SwitchOffJob(potentiostat="MAIN:1:POT")

try:
    error_object_success: zl.ErrorObject = link.do_job(switch_on_job)
    print_error_object(error_object_success)
    
    eis_dict_table_job = zl.meas.EisFrequencyTableJob(
        {
            "bias": 0.0,
            "spectrum": [
                {
                    "frequency": freq,
                    "amplitude": -2, # Negative amplitude to provoke an error
                    "pre_duration": 0.1,
                    "pre_waves": 1,
                    "meas_duration": 0.5,
                    "meas_waves": 3,
                }
                for freq in [1e3,10e3]
            ],
        }
    )
    link.do_job(eis_dict_table_job)

except zl.ZahnerLinkException as e:
    print(f"\ncaught exception: {e}")
    error_object: zl.ErrorObject = e.error
    print_error_object(error_object)
    print_job_status(eis_dict_table_job)

link.do_job(switch_off_job)
link.disconnect()
connected successfully
ErrorObject: no error occoured
ErrorObject==True: False
ErrorObject code enum: ErrorCodeEnum.NONE
ErrorObject message: no error occoured

caught exception: parameter 'amplitude' is too low: '-2' < '0' must be false
ErrorObject: parameter 'amplitude' is too low: '-2' < '0' must be false
ErrorObject==True: True
ErrorObject code enum: ErrorCodeEnum.PARAMETER_VALUE_TOO_LOW
ErrorObject message: parameter 'amplitude' is too low: '-2' < '0' must be false
job successfull: False
job status: JobStatusEnum.FAILED
job error message: parameter 'amplitude' is too low: '-2' < '0' must be false
JobInfo object id: void
JobInfo object status: JobStatusEnum.FAILED
JobInfo object status detail: JobStatusDetailEnum.FAILED_TO_CREATE
JobInfo object error message: parameter 'amplitude' is too low: '-2' < '0' must be false
JobInfo started: 

Connection Loss

If the connection is lost, you can re-establish the connection to the device and check the job list to see if any active jobs are still running.

In this example, the connection disruption is triggered by the software using PowerShell.

DELAY_BEFORE_DISCONNECT = 5  # seconds before the connection is killed
ADAPTER_NAME = "Ethernet 2"

def disable_adapter():
    """Disables the network adapter via netsh (requires admin, UAC prompt)."""
    cmd = f'netsh interface set interface "{ADAPTER_NAME}" admin=disable'
    print(f"[NET] Disabling '{ADAPTER_NAME}' ...")
    result = subprocess.run(
        ["powershell", "-Command", f"Start-Process cmd -ArgumentList '/c {cmd}' -Verb RunAs -Wait"],
        capture_output=True, text=True,
    )
    print(f"[NET] rc={result.returncode} stdout={result.stdout.strip()!r} stderr={result.stderr.strip()!r}")

def enable_adapter():
    """Re-enables the network adapter via netsh (requires admin, UAC prompt)."""
    cmd = f'netsh interface set interface "{ADAPTER_NAME}" admin=enable'
    print(f"[NET] Enabling '{ADAPTER_NAME}' ...")
    result = subprocess.run(
        ["powershell", "-Command", f"Start-Process cmd -ArgumentList '/c {cmd}' -Verb RunAs -Wait"],
        capture_output=True, text=True,
    )
    print(f"[NET] rc={result.returncode} stdout={result.stdout.strip()!r} stderr={result.stderr.strip()!r}")

def disconnect_after_delay(delay: float):
    """Waits `delay` seconds and then disables the network adapter."""
    print(f"[DISCONNECT-THREAD] Waiting {delay}s before killing the connection ...")
    time.sleep(delay)
    print(f"[DISCONNECT-THREAD] Disabling adapter NOW!")
    disable_adapter()


link = zl.ZahnerLinkExc(HOST, PORT)
status = link.connect()

if status == zl.ZahnerLinkServiceStatusEnum.SUCCESS_NO_ERROR:
    print("connected successfully")
else:
    print(f"failed to connect, status: {status}")

switch_on_job = zl.control.SwitchOnJob(
    potentiostat="MAIN:1:POT",
    coupling=zl.PotentiostatCoupling.POTENTIOSTATIC,
    bias=1.0,
    voltage_range_index=0,
    compliance_range_index=0,
)
switch_off_job = zl.control.SwitchOffJob(potentiostat="MAIN:1:POT")

eis_dict_table_job = zl.meas.EisFrequencyTableJob(
    {
        "bias": 0.0,
        "spectrum": [
            {
                "frequency": freq,
                "amplitude": 0.1,
                "pre_duration": 0.1,
                "pre_waves": 1,
                "meas_duration": 0.5,
                "meas_waves": 3,
            }
            for freq in [1e3, 10e3, 1, 200e-3]
        ],
    }
)

try:
    error_object_success: zl.ErrorObject = link.do_job(switch_on_job)
    print_error_object(error_object_success)

    # Start disconnect thread — disables adapter after DELAY_BEFORE_DISCONNECT seconds
    disconnect_thread = threading.Thread(
        target=disconnect_after_delay,
        args=(DELAY_BEFORE_DISCONNECT,),
        daemon=True,
    )
    disconnect_thread.start()

    print("\nStarting EIS measurement (connection will be killed mid-measurement) ...")
    link.do_job(eis_dict_table_job)
    print("\nMeasurement unexpectedly completed (connection was not killed fast enough).")

except zl.ZahnerLinkException as e:
    print(f"\ncaught exception: {e}")
    error_object: zl.ErrorObject = e.error
    print_error_object(error_object)
    print_job_status(eis_dict_table_job)

finally:
    # Always re-enable the adapter
    print("\n[CLEANUP] Re-enabling network adapter ...")
    enable_adapter()

# now we are disconnected
print("\nReconnect Now")
link.connect()

running_jobs = 0
job_list = link.get_job_info_list()
for job_info in job_list:
    print(f"id: {job_info.get_job_id()}")
    print(f"\tstatus: {job_info.get_status()}")
    if job_info.get_status() == zl.JobStatusEnum.RUNNING:
        running_jobs += 1
    print(f"\terror message: {job_info.get_error_message()}")
    print(f"\tstarted: {job_info.get_start_date()}")
    print(f"\tended: {job_info.get_end_date()}")
print(f"Number of jobs still running: {running_jobs}")

print("\nWait until EIS is finished")
time.sleep(15)

running_jobs = 0
job_list = link.get_job_info_list()
for job_info in job_list:
    print(f"id: {job_info.get_job_id()}")
    print(f"\tstatus: {job_info.get_status()}")
    if job_info.get_status() == zl.JobStatusEnum.RUNNING:
        running_jobs += 1
    print(f"\terror message: {job_info.get_error_message()}")
    print(f"\tstarted: {job_info.get_start_date()}")
    print(f"\tended: {job_info.get_end_date()}")
print(f"Number of jobs still running: {running_jobs}")

# Switch off the potentiostat
try:
    print("\nSwitch off")
    link.do_job(switch_off_job)
    print_job_status(switch_off_job)
except zl.ZahnerLinkException as e:
    print(f"\nSwitch-off after disconnect failed: {e}")
connected successfully
ErrorObject: no error occoured
ErrorObject==True: False
ErrorObject code enum: ErrorCodeEnum.NONE
ErrorObject message: no error occoured
[DISCONNECT-THREAD] Waiting 5s before killing the connection ...

Starting EIS measurement (connection will be killed mid-measurement) ...
[DISCONNECT-THREAD] Disabling adapter NOW!
[NET] Disabling 'Ethernet 2' ...
[NET] rc=0 stdout='' stderr=''

caught exception: network connection closed by remote peer
ErrorObject: network connection closed by remote peer
ErrorObject==True: True
ErrorObject code enum: ErrorCodeEnum.CONNECTION_WAS_CLOSED
ErrorObject message: network connection closed by remote peer
job successfull: False
job status: JobStatusEnum.FAILED
job error message: network connection closed by remote peer
JobInfo object id: void
JobInfo object status: JobStatusEnum.FAILED
JobInfo object status detail: JobStatusDetailEnum.CONNECTION_LOSS
JobInfo object error message: network connection closed by remote peer
JobInfo started: 

[CLEANUP] Re-enabling network adapter ...
[NET] Enabling 'Ethernet 2' ...
[NET] rc=0 stdout='' stderr=''

Reconnect Now
id: d286e94d-1506-4753-b0c0-50969fdfbd0f
	status: JobStatusEnum.RUNNING
	error message: no error occoured
	started: 2026-03-23T10:34:48.309026Z
	ended: 
id: 91abff8c-3005-4fb2-a317-3e99f58b80e3
	status: JobStatusEnum.FAILED
	error message: an unexpected exception occured: SystemException: type: 8; message: Potentiostat '37128:POT' must be switched off, before switched on.
	started: 2026-03-23T10:34:02.218823Z
	ended: 2026-03-23T10:34:02.220206Z
id: 13d017c7-b4f1-4021-a15b-27b2b6fac73b
	status: JobStatusEnum.FINISHED
	error message: no error occoured
	started: 2026-03-23T10:34:02.222262Z
	ended: 2026-03-23T10:34:02.222454Z
id: da2f2e6d-9ad1-44e0-a8c0-d5d1eb5a0182
	status: JobStatusEnum.FINISHED
	error message: no error occoured
	started: 2026-03-23T10:34:15.544448Z
	ended: 2026-03-23T10:34:18.771306Z
id: 1e479ded-822d-4a5f-a745-9c5ec227ba04
	status: JobStatusEnum.FINISHED
	error message: no error occoured
	started: 2026-03-23T10:34:18.774355Z
	ended: 2026-03-23T10:34:18.774557Z
id: dda6070e-43dc-4c3b-b023-afa6c4d0ded5
	status: JobStatusEnum.FINISHED
	error message: no error occoured
	started: 2026-03-23T10:34:45.080012Z
	ended: 2026-03-23T10:34:48.306849Z
Number of jobs still running: 1

Wait until EIS is finished
id: 13d017c7-b4f1-4021-a15b-27b2b6fac73b
	status: JobStatusEnum.FINISHED
	error message: no error occoured
	started: 2026-03-23T10:34:02.222262Z
	ended: 2026-03-23T10:34:02.222454Z
id: da2f2e6d-9ad1-44e0-a8c0-d5d1eb5a0182
	status: JobStatusEnum.FINISHED
	error message: no error occoured
	started: 2026-03-23T10:34:15.544448Z
	ended: 2026-03-23T10:34:18.771306Z
id: 1e479ded-822d-4a5f-a745-9c5ec227ba04
	status: JobStatusEnum.FINISHED
	error message: no error occoured
	started: 2026-03-23T10:34:18.774355Z
	ended: 2026-03-23T10:34:18.774557Z
id: dda6070e-43dc-4c3b-b023-afa6c4d0ded5
	status: JobStatusEnum.FINISHED
	error message: no error occoured
	started: 2026-03-23T10:34:45.080012Z
	ended: 2026-03-23T10:34:48.306849Z
id: d286e94d-1506-4753-b0c0-50969fdfbd0f
	status: JobStatusEnum.FINISHED
	error message: no error occoured
	started: 2026-03-23T10:34:48.309026Z
	ended: 2026-03-23T10:35:15.505373Z
Number of jobs still running: 0

Switch off
job successfull: True
job status: JobStatusEnum.FINISHED
job error message: no error occoured
JobInfo object id: e20aafa8-48d6-4a0b-98c5-7df505d714c1
JobInfo object status: JobStatusEnum.FINISHED
JobInfo object status detail: JobStatusDetailEnum.RUN_TO_COMPLETION
JobInfo object error message: no error occoured
JobInfo started: 2026-03-23T10:35:17.927282Z