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