Skip to main content
RACELOGIC Support Centre

LabSat Lite Discovery Script

This page contains a Python script that can be used to locate LabSat Lite units on a network.

The following is a Python script you can use to find your LabSat Lite unit on the network and identify the IP Address. You can then enter the IP address into a browser to access the LabSat Lite Web Interface. 

#!/usr/bin/env python3

"""

findlabsat.py - Discover LabSat Lite devices on the local network

Sends UDP broadcast on port 43686 and listens for responses on port 43687

"""

 

import socket

import struct

import sys

import argparse

from typing import List, Dict, Any

 

# Network configuration

BROADCAST_PORT = 43686

LISTEN_PORT = 43687

SEARCH_MESSAGE = b"LSRT_SEARCH"

TIMEOUT = 3.0  # seconds to wait for responses

 

# Response structure constants

HOSTNAME_LENGTH = 24

RESPONSE_FORMAT = '<4s4sH24sBBH'  # Header(4), IP(4), Port(2), Name(24), Ver(1), SubVer(1), Build(2)

RESPONSE_SIZE = struct.calcsize(RESPONSE_FORMAT)

 

def get_local_interfaces():

    """Get list of local network interfaces with their IP addresses"""

    interfaces = []

    

    try:

        # Get hostname

        hostname = socket.gethostname()

        

        # Get all addresses for this host

        addr_info = socket.getaddrinfo(hostname, None, socket.AF_INET)

        

        for info in addr_info:

            ip = info[4][0]

            if ip not in [i[1] for i in interfaces]:

                interfaces.append(('Local', ip))

    except:

        pass

    

    # Always add localhost

    if '127.0.0.1' not in [i[1] for i in interfaces]:

        interfaces.append(('Loopback', '127.0.0.1'))

    

    return interfaces

 

def parse_response(data: bytes) -> Dict[str, Any]:

    """Parse the UDP response from a LabSat Lite device"""

    

    # The response includes the search string echo first, skip it

    search_echo = b"LSRT_SEARCH"

    if data.startswith(search_echo):

        data = data[len(search_echo):]

    

    if len(data) < RESPONSE_SIZE:

        raise ValueError(f"Response too short: {len(data)} bytes, expected {RESPONSE_SIZE}")

    

    # Unpack the binary structure

    header, ip_bytes, port, name_bytes, version, subversion, build = struct.unpack(

        RESPONSE_FORMAT, data[:RESPONSE_SIZE]

    )

    

    # Convert IP address bytes to string

    ip_address = '.'.join(str(b) for b in ip_bytes)

    

    # Convert name bytes to string, stripping null terminators

    device_name = name_bytes.rstrip(b'\x00').decode('ascii', errors='replace')

    

    return {

        'ip': ip_address,

        'port': port,

        'name': device_name,

        'version': version,

        'subversion': subversion,

        'build': build

    }

 

def find_labsat_devices(timeout: float = TIMEOUT, bind_addr: str = '0.0.0.0') -> List[Dict[str, Any]]:

    """

    Broadcast search request and collect responses from LabSat Lite devices

    

    Args:

        timeout: How long to wait for responses (seconds)

        bind_addr: Local IP address to bind to (use '0.0.0.0' for all interfaces)

    

    Returns:

        List of dictionaries containing device information

    """

    devices = []

    

    # Create broadcast socket

    broadcast_sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)

    broadcast_sock.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, 1)

    

    # Bind to specific interface if requested

    if bind_addr != '0.0.0.0':

        try:

            broadcast_sock.bind((bind_addr, 0))

            print(f"Bound broadcast socket to {bind_addr}")

        except OSError as e:

            print(f"Warning: Could not bind to {bind_addr}: {e}")

            print("Using default interface instead.")

    

    # Create listening socket

    listen_sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)

    listen_sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)

    listen_sock.bind(('', LISTEN_PORT))

    listen_sock.settimeout(timeout)

    

    try:

        # Send broadcast search request

        print(f"Broadcasting search request on port {BROADCAST_PORT}...")

        broadcast_sock.sendto(SEARCH_MESSAGE, ('<broadcast>', BROADCAST_PORT))

        

        print(f"Listening for responses on port {LISTEN_PORT}...")

        print(f"Waiting {timeout} seconds for devices to respond...\n")

        

        # Collect responses until timeout

        while True:

            try:

                data, addr = listen_sock.recvfrom(1024)

                

                try:

                    device_info = parse_response(data)

                    device_info['source_addr'] = addr[0]  # Add source address for reference

                    devices.append(device_info)

                    

                    print(f"Found device:")

                    print(f"  Name:    {device_info['name']}")

                    print(f"  IP:      {device_info['ip']}")

                    print(f"  Port:    {device_info['port']}")

                    print(f"  Version: {device_info['version']}.{device_info['subversion']}.{device_info['build']}")

                    print(f"  Source:  {device_info['source_addr']}")

                    print()

                    

                except ValueError as e:

                    print(f"Warning: Could not parse response from {addr[0]}: {e}")

                    

            except socket.timeout:

                break

                

    finally:

        broadcast_sock.close()

        listen_sock.close()

    

    return devices

 

def main():

    """Main entry point"""

    parser = argparse.ArgumentParser(

        description='Discover LabSat Lite devices on the local network',

        formatter_class=argparse.RawDescriptionHelpFormatter,

        epilog="""

Examples:

  python findlabsat.py                    # Search on all interfaces

  python findlabsat.py --list             # List available interfaces

  python findlabsat.py --bind 192.168.1.5 # Search on specific interface

  python findlabsat.py --timeout 5        # Wait 5 seconds for responses

        """

    )

    

    parser.add_argument('--bind', '-b', 

                       default='0.0.0.0',

                       help='Local IP address to bind to (default: 0.0.0.0 for all)')

    

    parser.add_argument('--timeout', '-t',

                       type=float,

                       default=TIMEOUT,

                       help=f'Timeout in seconds to wait for responses (default: {TIMEOUT})')

    

    parser.add_argument('--list', '-l',

                       action='store_true',

                       help='List available network interfaces and exit')

    

    args = parser.parse_args()

    

    # List interfaces if requested

    if args.list:

        print("Available network interfaces:")

        print("=" * 50)

        interfaces = get_local_interfaces()

        for name, ip in interfaces:

            print(f"  {ip:15s}  ({name})")

        print()

        print("Use --bind <ip> to broadcast on a specific interface")

        return

    

    print("LabSat Lite Device Discovery")

    print("=" * 50)

    

    if args.bind != '0.0.0.0':

        print(f"Using interface: {args.bind}")

    else:

        print("Using all interfaces (default route)")

    print()

    

    try:

        devices = find_labsat_devices(timeout=args.timeout, bind_addr=args.bind)

        

        if devices:

            print(f"\nDiscovery complete. Found {len(devices)} device(s).")

        else:

            print("\nNo LabSat Lite devices found on the network.")

            print("Make sure devices are powered on and connected to the same network.")

            print("\nTip: Use --list to see available interfaces")

            

    except KeyboardInterrupt:

        print("\n\nSearch interrupted by user.")

        sys.exit(1)

    except Exception as e:

        print(f"\nError during discovery: {e}")

        sys.exit(1)

 

if __name__ == "__main__":

    main()

  • Was this article helpful?