Implementation Examples

This section provides concise examples for various programming languages. Fundamentally, any language that supports WebSockets or HTTP can be used to communicate with the device.

The ID command (/id) is used here to demonstrate how to retrieve identification information from a connected Zahner IM7 Workstation. The following examples show how to send this command using raw WebSocket connections across different languages.

Each example produces the following JSON response from the IM7 device:

Listing 3 Output of the ID command
data received:
{
    "type": "REPLY",
    "status": "SUCCESS",
    "request_id": "3bcbaaf4-c27e-4bad-ae23-de30a1a0d8fb",
    "workstation": {
        "protocol_version": 2,
        "serial_number": "73000000",
        "model_name": "IM7x",
        "firmware_version": "p2",
        "cpu_card_uuid": "106096c6-203d-47a1-86e8-d2e671c93b9e",
        "system_mac": "00C06AFFFFFE",
        "system_name": "My Zahner Workstation"
    }
}

All languages use at least the ID command for which the response was listed as an example. If additional commands have been implemented as examples, their responses are displayed separately as needed.

Python WebSocket

ID Command

This example demonstrates raw WebSocket communication in Python to illustrate the general procedure. For easier access to the IM7 API, we recommend using the provided Python library.

Listing 4 Python RAW WebSocket ID
import json
from websocket import create_connection

id_command = {
    "request_id": "3bcbaaf4-c27e-4bad-ae23-de30a1a0d8fb",
    "do": "/id",
}

socket_url = "ws://localhost:1994"
web_socket = create_connection(socket_url)

web_socket.send(json.dumps(id_command))

result = web_socket.recv()
result_data = json.loads(result)

print(f"data received:\n{json.dumps(result_data, indent=2)}")

web_socket.close()

Stop Command

It is also possible to stop jobs via WebSocket by sending the /stop command. The job that was stopped then has the status: “status_detail”: “STOPPED_BY_USER”.

Listing 5 Python RAW WebSocket Stop
import json
from websocket import create_connection

id_command = {
    "request_id": "3bcbaaf4-c27e-4bad-ae23-de30a1a0d8fb",
    "do": "/stop",
}

socket_url = "ws://localhost:1994"
web_socket = create_connection(socket_url)

web_socket.send(json.dumps(id_command))

result = web_socket.recv()
result_data = json.loads(result)

print(f"data received:\n{json.dumps(result_data, indent=2)}")

web_socket.close()

DC Data Retrieval and Decoding

DC measurement data can get quite large. To keep the transfer reliable and compatible across different systems, the device sends the numeric values as a Base64-encoded string.

In plain terms: Base64 is just a way to package “raw bytes” into text so it can be transported safely. To actually work with the numbers (for example to plot them), the client needs to unpack that text back into bytes and interpret those bytes as measurement values.

The Python example below walks through the process step by step. Even if you are not a programmer, you can read it as a checklist of what happens:

  • Request the data for a specific job (the get_data_command).

  • Read the response events: one message provides the column information (names/units), another contains the actual data payload.

  • Decode the Base64 payload back into bytes.

  • Convert the bytes into numeric values (64-bit floating point numbers little endian).

  • Split the long list of numbers into separate columns (“tracks”) using the column count from the header.

Listing 6 Python RAW WebSocket Data Retrieve and Decode
import json
import time
import base64
import struct
from websocket import create_connection

socket_url = "ws://localhost:1994"
web_socket = create_connection(socket_url)
job_id = "bf76433f-39aa-4915-9318-8784aaca2e55"

get_data_command = {
    "do": "/job/data",
    "job_id": f"{job_id}",
    "request_id": "get-data-uuid-example"
}
web_socket.send(json.dumps(get_data_command))
result = web_socket.recv()
result_data = json.loads(result)
print(f"data received:\n{json.dumps(result_data, indent=2)}")

column_count = 0
base64_data = None
dimensions = []

for entry in result_data.get("data", []):
    if entry.get("type") == "EVENT":
        event = entry.get("event", {})
        if event.get("type") == "LIVE_DATA_HEADER":
            dimensions = event["header"]["columns"]["dimensions"]
            column_count = len(dimensions)
            print(f"Found header with {column_count} columns")
        elif event.get("type") == "LIVE_DATA_ROWS":
            base64_data = event["data"]

print(f"Extracted data: {base64_data}")

if base64_data and column_count > 0:
    decoded_bytes = base64.b64decode(base64_data)
    double_count = len(decoded_bytes) // 8
    doubles = struct.unpack(f"{double_count}d", decoded_bytes)

    row_count = double_count // column_count
    print(f"Decoded {double_count} doubles, {row_count} rows")

    columns_data = []
    for i in range(column_count):
        start = i * row_count
        end = start + row_count
        col_data = doubles[start:end]
        columns_data.append(col_data)
        dimension_name = dimensions[i] if i < len(dimensions) else "unknown"
        print(f"Column {i} ('{dimension_name}') data: {col_data}")

web_socket.close()
Listing 7 Python RAW WebSocket Data Retrieve and Decode Output
data received:
{
"type": "REPLY",
"status": "SUCCESS",
"data": [
    {
    "type": "EVENT",
    "event": {
        "type": "LIVE_DATA_HEADER",
        "header": {
        "measurement_type": "OCV/OCP Scan",
        "measurement_type_short": "ocv",
        "columns": {
            "dimensions": [
            "time",
            "voltage",
            "current",
            "debug"
            ],
            "units": [
            "s",
            "V",
            "A",
            "bits"
            ],
            "urns": [
            "time",
            "2557874741:POT:U~43043:PAD_U",
            "2557874741:POT:I~43043:PAD_I",
            "debug"
            ]
        }
        },
        "job_info": {
        "job_id": "bf76433f-39aa-4915-9318-8784aaca2e55",
        "status": "FINISHED",
        "status_detail": "RUN_TO_COMPLETION",
        "job_meta": {},
        "created": "2026-01-27T08:43:12.075085Z",
        "type": "ocv",
        "parameters": {
            "duration": 2,
            "output_data_rate": 2
        },
        "started": "2026-01-27T08:43:12.075085Z",
        "ended": "2026-01-27T08:43:14.164460Z",
        "error": {
            "number": 0,
            "code": "NONE",
            "message": "no error occoured",
            "message_parameters": []
        }
        }
    }
    },
    {
    "type": "EVENT",
    "event": {
        "type": "LIVE_DATA_ROWS",
        "data": "AAAAAAAA4D8AAAAAAADwPwAAAAAAAPg/AAAAAAAAAECollrQ6asIv8hG2F/rZQi/AMwSQH5lB7+AS6OXE80Gv2SuNwQy2FC96pnQSNgBUb1msIyqIK1SvUQMpCCFg1O9AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA="
    }
    },
    {
    "type": "EVENT",
    "event": {
        "type": "LIVE_DATA_FINISHED",
        "job_info": {
        "job_id": "bf76433f-39aa-4915-9318-8784aaca2e55",
        "status": "FINISHED",
        "status_detail": "RUN_TO_COMPLETION",
        "job_meta": {},
        "created": "2026-01-27T08:43:12.075085Z",
        "type": "ocv",
        "parameters": {
            "duration": 2,
            "output_data_rate": 2
        },
        "started": "2026-01-27T08:43:12.075085Z",
        "ended": "2026-01-27T08:43:14.164460Z",
        "error": {
            "number": 0,
            "code": "NONE",
            "message": "no error occoured",
            "message_parameters": []
        }
        }
    }
    }
],
"request_id": "get-data-uuid-example"
}
Found header with 4 columns
Extracted data: AAAAAAAA4D8AAAAAAADwPwAAAAAAAPg/AAAAAAAAAECollrQ6asIv8hG2F/rZQi/AMwSQH5lB7+AS6OXE80Gv2SuNwQy2FC96pnQSNgBUb1msIyqIK1SvUQMpCCFg1O9AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=
Decoded 16 doubles, 4 rows
Column 0 ('time') data: (0.5, 1.0, 1.5, 2.0)
Column 1 ('voltage') data: (-4.7057221349162015e-05, -4.653572612424228e-05, -4.462520156191946e-05, -4.348960915625307e-05)
Column 2 ('current') data: (-2.393749297032741e-13, -2.4168694052009246e-13, -2.6540589739164965e-13, -2.773070678257643e-13)
Column 3 ('shunt') data: (0.0, 0.0, 0.0, 0.0)

JavaScript WebSocket

Listing 8 JavaScript WebSocket
const socket_url = 'ws://localhost:1994';
const socket = new WebSocket(socket_url);

const id_command = {
    "request_id": "3bcbaaf4-c27e-4bad-ae23-de30a1a0d8fb",
    "do": "/id",
};

socket.onopen = function(event) {
    // Send the command as a JSON string
    socket.send(JSON.stringify(id_command));
};

socket.onmessage = function(event) {
    // Parse the received JSON string
    const result_data = JSON.parse(event.data);

    // Print formatted JSON
    console.log(`data received:\n${JSON.stringify(result_data, null, 2)}`);

    socket.close();
};

PowerShell WebSocket

Listing 9 PowerShell WebSocket
$socketUrl = "ws://localhost:1994"
$ws = New-Object System.Net.WebSockets.ClientWebSocket
$cts = New-Object System.Threading.CancellationTokenSource

# Connect to the WebSocket
$ws.ConnectAsync($socketUrl, $cts.Token).Wait()

# Create the JSON command
$idCommand = @{
    request_id = "3bcbaaf4-c27e-4bad-ae23-de30a1a0d8fb"
    do         = "/id"
}
$jsonString = $idCommand | ConvertTo-Json -Compress
$buffer = [System.Text.Encoding]::UTF8.GetBytes($jsonString)

# Send the command
$ws.SendAsync([ArraySegment[byte]]$buffer, [System.Net.WebSockets.WebSocketMessageType]::Text, $true, $cts.Token).Wait()

# Receive the response
$buffer = New-Object byte[] 4096
$result = $ws.ReceiveAsync([ArraySegment[byte]]$buffer, $cts.Token)
$result.Wait()

# Decode and print the response
$responseString = [System.Text.Encoding]::UTF8.GetString($buffer, 0, $result.Result.Count)
$responseData = $responseString | ConvertFrom-Json

Write-Host "data received:"
$responseData | ConvertTo-Json -Depth 5

# Close the connection
$ws.CloseAsync([System.Net.WebSockets.WebSocketCloseStatus]::NormalClosure, "Closing", $cts.Token).Wait()

Bash WebSocket using websocat and jq

Listing 10 Bash WebSocket ID
SOCKET_URL="ws://localhost:1994"
ID_COMMAND='{"request_id": "3bcbaaf4-c27e-4bad-ae23-de30a1a0d8fb", "do": "/id"}'

RESPONSE=$(echo $ID_COMMAND | websocat -1 $SOCKET_URL)

echo "data received:"
echo $RESPONSE | jq .

Bash HTTP using curl and jq

The IM7 also supports HTTP requests but we recommend using WebSockets for full functionality.

ID Command

Listing 11 Bash HTTP ID
URL="http://localhost:1994/id"

RESPONSE=$(curl -s $URL)

echo "data received:"
echo $RESPONSE | jq .

Switch On

As an addition for example the Switch On command via HTTP with the required parameters.

Listing 12 Bash HTTP Switch On
URL="http://host.docker.internal:1994/job/start"

PAYLOAD='{
    "request_id": "3bcbaaf4-c27e-4bad-ae23-de30a1a0d8fb",
    "job": {
        "type": "switch_on",
        "parameters": {
            "potentiostat": "MAIN:1:POT",
            "coupling": "POTENTIOSTATIC",
            "bias": 0,
            "voltage_range_index": 0,
            "compliance_range_index": 0
        }
    }
}'

RESPONSE=$(curl -s -X POST -H "Content-Type: application/json" -d "$PAYLOAD" $URL)

echo "data received:"
echo $RESPONSE | jq .

The output shown below represents only the initial response, indicating that the job has been successfully submitted but has not yet completed. As detailed in the main WebSocket documentation, the job is finished only when a message with “type”:”JOB_DONE” is received. Alternatively, the job status in the job list can be polled until completion.

Listing 13 Output of the Switch On command (first reply not finished job)
data received:
{
    "type": "REPLY",
    "status": "SUCCESS",
    "request_id": "3bcbaaf4-c27e-4bad-ae23-de30a1a0d8fb",
    "job_info": {
        "job_id": "27a6688c-6ba7-48ae-ab2c-f58d378b68ff",
        "status": "PENDING",
        "status_detail": "NOT_FINISHED",
        "job_meta": {},
        "created": "2025-12-17T10:14:05.807353Z",
        "type": "switch_on",
        "parameters": {
        "potentiostat": "MAIN:1:POT",
        "coupling": "POTENTIOSTATIC",
        "bias": 0,
        "voltage_range_index": 0,
        "compliance_range_index": 0
        },
        "started": "2025-12-17T10:14:05.807353Z",
        "error": {
        "number": 0,
        "code": "NONE",
        "message": "no error occoured",
        "message_parameters": []
        }
    }
}

Job List

Use the following HTTP GET command to retrieve the complete list of jobs and their statuses.

Listing 14 Bash HTTP Job List
URL="http://host.docker.internal:1994/job/list"

RESPONSE=$(curl -s $URL)

echo "data received:"
echo $RESPONSE | jq .
Listing 15 Output of the job list command
data received:
{
    "type": "REPLY",
    "status": "SUCCESS",
    "jobs": [
        {
        "job_id": "0a6b9787-1328-43aa-b5c5-6011f7b5a769",
        "status": "FINISHED",
        "status_detail": "RUN_TO_COMPLETION",
        "job_meta": {},
        "created": "2025-12-17T10:41:21.467971Z",
        "type": "switch_on",
        "parameters": {
            "potentiostat": "MAIN:1:POT",
            "coupling": "POTENTIOSTATIC",
            "bias": 0,
            "voltage_range_index": 0,
            "compliance_range_index": 0
        },
        "started": "2025-12-17T10:41:21.467971Z",
        "ended": "2025-12-17T10:41:21.858092Z",
        "error": {
            "number": 0,
            "code": "NONE",
            "message": "no error occoured",
            "message_parameters": []
        }
        }
    ]
}

Stop Command

The following example demonstrates how to stop a running job using the /stop HTTP command. The job that was stopped has the following status: “status_detail”: “STOPPED_BY_USER”.

Listing 16 Bash HTTP Stop
URL="http://host.docker.internal:1994/stop"

RESPONSE=$(curl -s $URL)

echo "data received:"
echo $RESPONSE | jq .

C# WebSocket

Listing 17 C# WebSocket example
using System;
using System.IO;
using System.Net.WebSockets;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Text.Json;

class Program
{
    static async Task Main(string[] args)
    {
        using (ClientWebSocket ws = new ClientWebSocket())
        {
            Uri serverUri = new Uri("ws://localhost:1994");
            CancellationTokenSource cts = new CancellationTokenSource();

            await ws.ConnectAsync(serverUri, cts.Token);

            var idCommand = new
            {
                request_id = "3bcbaaf4-c27e-4bad-ae23-de30a1a0d8fb",
                @do = "/id"
            };

            string jsonString = JsonSerializer.Serialize(idCommand);
            byte[] bytesToSend = Encoding.UTF8.GetBytes(jsonString);

            await ws.SendAsync(new ArraySegment<byte>(bytesToSend), WebSocketMessageType.Text, true, cts.Token);

            using (var ms = new MemoryStream())
            {
                var buffer = new byte[1024];
                WebSocketReceiveResult result;
                do
                {
                    result = await ws.ReceiveAsync(new ArraySegment<byte>(buffer), cts.Token);
                    ms.Write(buffer, 0, result.Count);
                } while (!result.EndOfMessage);

                string responseString = Encoding.UTF8.GetString(ms.ToArray());

                using (JsonDocument doc = JsonDocument.Parse(responseString))
                {
                    var options = new JsonSerializerOptions { WriteIndented = true };
                    Console.WriteLine("data received:\n" + JsonSerializer.Serialize(doc.RootElement, options));
                }
            }

            await ws.CloseAsync(WebSocketCloseStatus.NormalClosure, "Closing", cts.Token);
        }
    }
}