Venus API question

Hi All,
Anyone using the Venus API to talk to their system remotely? I can hit the API endpoints using localhost and the PC’s IP address (from that PC, ie locally) - but I can’t access them from another PC. I checked the firewall rules and I can ping the PC’s IP address from another computer. I don’t have authentication turned on.

Any advice?

Thanks
Rob

1 Like

Turns out you have to be not-dumb.

I was on a different network… The ping replies threw me off, but the networks were segregated. When I am on the right network, everything works as expected.

As an aside - Gemini (and I’m sure many of the other new models) is pretty amazing. I uploaded the API doc and asked for a simple monitor. Save the html below as a .html:

Venus Device Monitor /* Using Inter font from Google Fonts */ @import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap'); body { font-family: 'Inter', sans-serif; background-color: #f3f4f6; /* A light gray background */ } /* Custom scrollbar for the trace log */ #trace-log::-webkit-scrollbar { width: 8px; } #trace-log::-webkit-scrollbar-track { background: #e5e7eb; border-radius: 10px; } #trace-log::-webkit-scrollbar-thumb { background: #9ca3af; border-radius: 10px; } #trace-log::-webkit-scrollbar-thumb:hover { background: #6b7280; }
<div class="container mx-auto p-4 md:p-8 max-w-4xl">
    <!-- Header -->
    <header class="mb-8">
        <h1 class="text-3xl font-bold text-gray-900">Venus Device Monitor</h1>
        <div class="flex items-center mt-2">
            <p class="text-gray-600 mr-2">Connection Status:</p>
            <span id="connection-status" class="px-3 py-1 text-sm font-semibold text-white bg-yellow-500 rounded-full">
                Connecting...
            </span>
        </div>
    </header>

    <!-- Main Content -->
    <main class="grid grid-cols-1 gap-8">
        <!-- Device Status Card -->
        <div class="bg-white p-6 rounded-xl shadow-md">
            <h2 class="text-xl font-semibold mb-4">Device Status</h2>
            <div class="flex items-center space-x-4">
                <div id="status-indicator-light" class="w-4 h-4 rounded-full bg-gray-400 animate-pulse"></div>
                <p id="device-status" class="text-lg font-medium text-gray-500">Waiting for status updates...</p>
            </div>
             <p id="status-message" class="text-sm text-gray-500 mt-2 ml-8"></p>
        </div>

        <!-- Real-time Trace Log Card -->
        <div class="bg-white p-6 rounded-xl shadow-md">
            <div class="flex justify-between items-center mb-4">
                <h2 class="text-xl font-semibold">Real-time Trace Log</h2>
                <button id="clear-log-btn" class="px-4 py-2 text-sm font-medium text-white bg-red-600 rounded-lg hover:bg-red-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-red-500">Clear Log</button>
            </div>
            <div id="trace-log" class="h-96 bg-gray-900 text-white font-mono text-sm p-4 rounded-lg overflow-y-auto">
                <!-- Trace messages will be appended here -->
                <div class="text-gray-400">Awaiting trace messages from the SignalR hub...</div>
            </div>
        </div>
    </main>

    <!-- Footer -->
    <footer class="text-center mt-8 text-sm text-gray-500">
        <p>Built for the Venus Web API. Connects to http://localhost:51745/signalR.</p>
    </footer>
</div>

<script>
    // --- DOM Element References ---
    const connectionStatusEl = document.getElementById('connection-status');
    const deviceStatusEl = document.getElementById('device-status');
    const statusMessageEl = document.getElementById('status-message');
    const statusIndicatorLightEl = document.getElementById('status-indicator-light');
    const traceLogEl = document.getElementById('trace-log');
    const clearLogBtn = document.getElementById('clear-log-btn');

    // --- Configuration ---
    const signalRUrl = "http://localhost:51745/signalR";

    // --- Status Mappings (Based on Programmer's Manual 4.3.3.2) ---
    const executorEventMap = {
        0: { text: "Undefined", color: "bg-gray-500" },
        1: { text: "Initialized", color: "bg-blue-500" },
        2: { text: "Initialization Failed", color: "bg-red-600" },
        3: { text: "Terminated", color: "bg-green-600" },
        4: { text: "Aborted", color: "bg-red-600" },
        5: { text: "Paused", color: "bg-yellow-500" },
        6: { text: "Running", color: "bg-green-500" },
        7: { text: "Unloaded", color: "bg-gray-500" }
    };

    // --- Helper Functions ---
    function updateConnectionStatus(status, color) {
        connectionStatusEl.textContent = status;
        connectionStatusEl.className = `px-3 py-1 text-sm font-semibold text-white ${color} rounded-full`;
    }

    function updateDeviceStatus(event, message) {
        const statusInfo = executorEventMap[event] || executorEventMap[0];
        
        deviceStatusEl.textContent = statusInfo.text;
        deviceStatusEl.classList.remove('text-gray-500');
        
        statusMessageEl.textContent = message || "No additional message.";
        
        // Update the status indicator light
        statusIndicatorLightEl.className = `w-4 h-4 rounded-full ${statusInfo.color}`;
        if (statusInfo.text === 'Running') {
            statusIndicatorLightEl.classList.add('animate-ping', 'opacity-75');
        } else {
             statusIndicatorLightEl.classList.remove('animate-ping', 'opacity-75', 'animate-pulse');
        }
    }

    function addTraceMessage(message) {
        // Clear the initial message if it exists
        if (traceLogEl.childElementCount === 1 && traceLogEl.firstChild.textContent.startsWith('Awaiting')) {
            traceLogEl.innerHTML = '';
        }

        const traceEntry = document.createElement('div');
        const timestamp = new Date().toLocaleTimeString();
        traceEntry.innerHTML = `<span class="text-gray-500 mr-2">${timestamp}</span><span class="text-green-400">&gt;</span> ${message}`;
        traceLogEl.appendChild(traceEntry);

        // Auto-scroll to the bottom
        traceLogEl.scrollTop = traceLogEl.scrollHeight;
    }
    
    clearLogBtn.addEventListener('click', () => {
         traceLogEl.innerHTML = '<div class="text-gray-400">Log cleared. Awaiting new trace messages...</div>';
    });

    // --- SignalR Connection Setup ---
    const connection = new signalR.HubConnectionBuilder()
        .withUrl(signalRUrl, {
            // As per the manual, these options might be necessary
            skipNegotiation: true,
            transport: signalR.HttpTransportType.WebSockets
        })
        .withAutomaticReconnect()
        .build();

    // --- SignalR Event Handlers ---

    // Handle "NewRuntimeStatus" push notification
    connection.on("NewRuntimeStatus", (data) => {
        console.log("NewRuntimeStatus:", data);
        if (data && typeof data.ExecutorEvent !== 'undefined') {
            updateDeviceStatus(data.ExecutorEvent, data.Message);
        }
    });

    // Handle "NewRuntimeTrace" push notification
    connection.on("NewRuntimeTrace", (data) => {
        console.log("NewRuntimeTrace:", data);
        addTraceMessage(data);
    });
    
    // Handle connection lifecycle events
    connection.onreconnecting(error => {
        console.warn(`Connection lost. Attempting to reconnect...`);
        updateConnectionStatus("Reconnecting", "bg-yellow-500");
    });

    connection.onreconnected(connectionId => {
        console.log(`Connection re-established. Connected with ID: ${connectionId}`);
        updateConnectionStatus("Connected", "bg-green-600");
    });

    connection.onclose(error => {
        console.error(`Connection closed due to error: "${error}". Try refreshing the page.`);
        updateConnectionStatus("Disconnected", "bg-red-600");
         statusIndicatorLightEl.className = 'w-4 h-4 rounded-full bg-red-600';
         statusIndicatorLightEl.classList.remove('animate-ping', 'opacity-75', 'animate-pulse');
    });


    // --- Start Connection ---
    async function start() {
        try {
            await connection.start();
            console.log("SignalR Connected.");
            updateConnectionStatus("Connected", "bg-green-600");
        } catch (err) {
            console.error("SignalR Connection Error: ", err);
            updateConnectionStatus("Failed", "bg-red-600");
            addTraceMessage(`ERROR: Could not connect to the SignalR hub at ${signalRUrl}. Ensure the Venus WebAPI Host service is running.`);
        }
    }

    start();

</script>
5 Likes

Hey! I’m interested.
From what I’ve seen the Venus API is purely COM based yeah?
What API endpoints are you hitting?
Did you build your own agent on the instrument PC?

Thanks

1 Like

Hi Nate - the official Venus API is REST and then some SignalR stuff for alerts/realtime status. SignalR is a microsoft web chat tool - but it’s not difficult to implement. There’s swagger documentation at:

  1. Open C:\Program Files (x86)\Hamilton\Bin\WebAPI\WebAPI.Host.appsettings.json. 2. Locate the setting swaggerUIEnabled and replace “false” with “true”.
  2. Save the file and close. The files are in a subdirectory in
    Program Files (x86) and are administrator-access only.
  3. Open the Services system application and select “Hamilton VENUS WebAPI Host”. 5. Restart the Web API host.
  4. Go to http://localhost:51745/swagger/index.html
    for the API reference documentation (Swagger UI).

PyLabRobot uses the COM ports and firmware command set to call discrete hardware actions.

The HTML I posted (gemini created) is just a simple monitor that watches a vantage and shows the trace. I’ve modified it further to watch over our fleet of vantages, it was pretty easy to make a dashboard that showed the status, last few runs, trace files, etc.

I haven’t used it to run anything dynamic - the basic API implementation just lets you start existing methods. You can get clever with variables and parameterization, but we haven’t done that.

1 Like

Oh, sorry - did you mean component object model COM?

It is a huge windows application, and developed over a long time. I haven’t ever tried to dig into it at that level and as far as I know there was never any published interface / documentation to know where to start. But maybe some of the Hamilton folks can comment on that.

Hey Robert,
You’re right it’s REST. I was confusing Venus with Tecan Fluents API.

I too have implemented something similar to what you are describing. Are you capable of monitoring multiple vantages from one interface / device? Like a central pc that is calling out to all of your vantages?

If so, I’m wondering how you exposed that endpoint / port (http://localhost:51745) to the network for the central pc to hit. I ask this because localhost is a loopback address and it’s not accessible from the outside network.

I hope this makes sense.

Thanks!

Yeah - I was able to connect to a bunch of ours. Just put their IP addresses or hostnames instead of localhost. Message me your email address and I’ll send you the dashboard file I made as an example.

1 Like