Appendix F - gclib.py - Galil Motor Controller Interface

import platform #for distinguishing 'Windows', 'Linux', 'Darwin'
from ctypes import *
import os
 
if platform.system() == 'Windows':
    if '64 bit' in platform.python_compiler():
        WinDLL(os.environ["GCLIB_ROOT"] + '/dll/x64/libcrypto-1_1-x64.dll')
        WinDLL(os.environ["GCLIB_ROOT"] + '/dll/x64/libssl-1_1-x64.dll')
        _gclib = WinDLL(os.environ["GCLIB_ROOT"] + '/dll/x64/gclib.dll')
        _gclibo = WinDLL(os.environ["GCLIB_ROOT"] + '/dll/x64/gclibo.dll')
    else:
        WinDLL(os.environ["GCLIB_ROOT"] + '/dll/x86/libcrypto-1_1.dll')
        WinDLL(os.environ["GCLIB_ROOT"] + '/dll/x86/libssl-1_1.dll')
        _gclib = WinDLL(os.environ["GCLIB_ROOT"] + '/dll/x86/gclib.dll')
        _gclibo = WinDLL(os.environ["GCLIB_ROOT"] + '/dll/x86/gclibo.dll')
        #Reassign symbol name, Python doesn't like @ in function names
        #gclib calls
        setattr(_gclib, 'GArrayDownload', getattr(_gclib, '_GArrayDownload@20'))
        setattr(_gclib, 'GArrayUpload', getattr(_gclib, '_GArrayUpload@28'))
        setattr(_gclib, 'GClose', getattr(_gclib, '_GClose@4'))
        setattr(_gclib, 'GCommand', getattr(_gclib, '_GCommand@20'))
        setattr(_gclib, 'GFirmwareDownload', getattr(_gclib, '_GFirmwareDownload@8'))
        setattr(_gclib, 'GInterrupt', getattr(_gclib, '_GInterrupt@8'))
        setattr(_gclib, 'GMessage', getattr(_gclib, '_GMessage@12'))
        setattr(_gclib, 'GOpen', getattr(_gclib, '_GOpen@8'))
        setattr(_gclib, 'GProgramDownload', getattr(_gclib, '_GProgramDownload@12'))
        setattr(_gclib, 'GProgramUpload', getattr(_gclib, '_GProgramUpload@12'))
        #gclibo calls (open source component/convenience functions)
        setattr(_gclibo, 'GAddresses', getattr(_gclibo, '_GAddresses@8'))
        setattr(_gclibo, 'GArrayDownloadFile', getattr(_gclibo, '_GArrayDownloadFile@8'))
        setattr(_gclibo, 'GArrayUploadFile', getattr(_gclibo, '_GArrayUploadFile@12'))
        setattr(_gclibo, 'GAssign', getattr(_gclibo, '_GAssign@8'))
        setattr(_gclibo, 'GError', getattr(_gclibo, '_GError@12'))
        setattr(_gclibo, 'GInfo', getattr(_gclibo, '_GInfo@12'))
        setattr(_gclibo, 'GIpRequests', getattr(_gclibo, '_GIpRequests@8'))
        setattr(_gclibo, 'GMotionComplete', getattr(_gclibo, '_GMotionComplete@8'))
        setattr(_gclibo, 'GProgramDownloadFile', getattr(_gclibo, '_GProgramDownloadFile@12'))
        setattr(_gclibo, 'GSleep', getattr(_gclibo, '_GSleep@4'))
        setattr(_gclibo, 'GProgramUploadFile', getattr(_gclibo, '_GProgramUploadFile@8'))
        setattr(_gclibo, 'GTimeout', getattr(_gclibo, '_GTimeout@8'))
        setattr(_gclibo, 'GVersion', getattr(_gclibo, '_GVersion@8'))
        setattr(_gclibo, 'GSetupDownloadFile', getattr(_gclibo, '_GSetupDownloadFile@20'))
        setattr(_gclibo, 'GServerStatus', getattr(_gclibo, '_GServerStatus@8'))
        setattr(_gclibo, 'GSetServer', getattr(_gclibo, '_GSetServer@4'))
        setattr(_gclibo, 'GListServers', getattr(_gclibo, '_GListServers@8'))
        setattr(_gclibo, 'GPublishServer', getattr(_gclibo, '_GPublishServer@12'))
        setattr(_gclibo, 'GRemoteConnections', getattr(_gclibo, '_GRemoteConnections@8'))
 
elif platform.system() == 'Linux':
    cdll.LoadLibrary("libgclib.so.2")
    _gclib = CDLL("libgclib.so.2")
    cdll.LoadLibrary("libgclibo.so.2")
    _gclibo = CDLL("libgclibo.so.2")
 
elif platform.system() == 'Darwin': #OSX
    _gclib_path = '/Applications/gclib/dylib/gclib.0.dylib'
    _gclibo_path = '/Applications/gclib/dylib/gclibo.0.dylib'
    cdll.LoadLibrary(_gclib_path)
    _gclib = CDLL(_gclib_path)
    cdll.LoadLibrary(_gclibo_path)
    _gclibo = CDLL(_gclibo_path)
 
 
 
# Python "typedefs"
_GReturn = c_int #type for a return code
_GCon = c_void_p #type for a Galil connection handle
_GCon_ptr = POINTER(_GCon) #used for argtypes declaration
_GSize = c_ulong #type for a Galil size variable
_GSize_ptr = POINTER(_GSize) #used for argtypes declaration
_GCStringIn = c_char_p #char*. In C it's const.
_GCStringOut = c_char_p #char*
_GOption = c_int #type for option variables, e.g.    GArrayDownload
_GStatus = c_ubyte #type for interrupt status bytes
_GStatus_ptr = POINTER(_GStatus) #used for argtypes declaration
 
#Define arguments and result type (if not C int type)
#gclib calls
_gclib.GArrayDownload.argtypes = [_GCon, _GCStringIn, _GOption, _GOption, _GCStringIn]
_gclib.GArrayUpload.argtypes = [_GCon, _GCStringIn, _GOption, _GOption, _GOption, _GCStringOut, _GSize]
_gclib.GClose.argtypes = [_GCon]
_gclib.GCommand.argtypes = [_GCon, _GCStringIn, _GCStringOut, _GSize, _GSize_ptr]
_gclib.GFirmwareDownload.argtypes = [_GCon, _GCStringIn]
_gclib.GInterrupt.argtypes = [_GCon, _GStatus_ptr]
_gclib.GMessage.argtypes = [_GCon, _GCStringOut, _GSize]
_gclib.GOpen.argtypes = [_GCStringIn, _GCon_ptr]
_gclib.GProgramDownload.argtypes = [_GCon, _GCStringIn, _GCStringIn]
_gclib.GProgramUpload.argtypes = [_GCon, _GCStringOut, _GSize]
#gclibo calls (open source component/convenience functions)
_gclibo.GAddresses.argtypes = [_GCStringOut, _GSize]
_gclibo.GArrayDownloadFile.argtypes = [_GCon, _GCStringIn]
_gclibo.GArrayUploadFile.argtypes = [_GCon, _GCStringIn, _GCStringIn]
_gclibo.GAssign.argtypes = [_GCStringIn, _GCStringIn]
_gclibo.GError.argtypes = [_GReturn, _GCStringOut, _GSize]
_gclibo.GError.restype    = None
_gclibo.GIpRequests.argtypes = [_GCStringOut, _GSize]
_gclibo.GMotionComplete.argtypes = [_GCon, _GCStringIn]
_gclibo.GProgramDownloadFile.argtypes = [_GCon, _GCStringIn, _GCStringIn]
_gclibo.GSleep.argtypes = [c_uint]
_gclibo.GSleep.restype    = None
_gclibo.GProgramUploadFile.argtypes = [_GCon, _GCStringIn]
_gclibo.GTimeout.argtypes = [_GCon, c_int]
_gclibo.GVersion.argtypes = [_GCStringOut, _GSize]
_gclibo.GServerStatus.argtypes = [_GCStringOut, _GSize]
_gclibo.GSetServer.argtypes = [_GCStringIn]
_gclibo.GListServers.argtypes = [_GCStringOut, _GSize]
_gclibo.GPublishServer.argtypes = [_GCStringIn, _GOption, _GOption]
_gclibo.GRemoteConnections.argtypes = [_GCStringOut, _GSize]
_gclibo.GSetupDownloadFile.argtypes = [_GCon, _GCStringIn, _GOption, _GCStringOut, _GSize]
 
#Set up some constants
_enc = "ASCII" #byte encoding for going between python strings and c strings.
_buf_size = 500000 #size of response buffer. Big enough to fit entire 4000 program via UL/LS, or 24000 elements of array data.
_error_buf = create_string_buffer(128)    #buffer for retrieving error code descriptions.
 
def _rc(return_code):
    """Checks return codes from gclib and raises a python error if result is exceptional."""
    if return_code != 0:
        _gclibo.GError(return_code, _error_buf, 128) #Get the library's error description
        raise GclibError(str(_error_buf.value.decode(_enc)))
    return
 
class GclibError(Exception):
    """@ingroup python
    Error class for non-zero gclib return codes.
    """
    pass
 
class py:
    """
    Represents a single Python connection to a Galil Controller or PLC.
    """
 
    def __init__(self):
        """Constructor for the Connection class. Initializes gclib's handle and read buffer."""
        self._gcon = _GCon(0) #handle to connection
        self._buf = create_string_buffer(_buf_size)
        self._timeout = 5000
        return
 
    def __del__(self):
        """Destructor for the Connection class. Ensures close gets called to release Galil resource (Sockets, Kernel Driver, Com Port, etc)."""
        self.GClose()
        return
 
    def _cc(self):
        """Checks if connection is established, throws error if not."""
        if self._gcon.value == None:
            _rc(-1201) #G_CONNECTION_NOT_ESTABLISHED
 
    def GOpen(self, address):
        """@ingroup py_connection
        Opens a connection a galil controller.
        See the gclib docs for address string formatting.
        """
        c_address = _GCStringIn(address.encode(_enc))
        _rc(_gclib.GOpen(c_address, byref(self._gcon)))
        return
 
 
    def GClose(self):
        """@ingroup py_connection
        Closes a connection to a Galil Controller.
        """
        if self._gcon.value != None:
            _rc(_gclib.GClose(self._gcon))
            self._gcon = _GCon(0)
        return
 
 
    def GCommand(self, command):
        """@ingroup py_controller
        Performs a command-and-response transaction on the connection.
        Trims the response.
        """
        self._cc()
        c_command = _GCStringIn(command.encode(_enc))
        _rc(_gclib.GCommand(self._gcon, c_command, self._buf, _buf_size, None))
        response = str(self._buf.value.decode(_enc))
        return response[:-3].strip() # trim trailing /r/n: and leading space
 
 
    def GSleep(self, val):
        """@ingroup python
        Provides a blocking sleep call which can be useful for timing-based chores.
        """
        _gclibo.GSleep(val)
        return
 
 
    def GVersion(self):
        """@ingroup python
        Provides the gclib version number. Please include the output of this function on all support cases.
        """
        _rc(_gclibo.GVersion(self._buf, _buf_size))
        return "py." + str(self._buf.value.decode(_enc))
 
    def GServerStatus(self):
        """@ingroup py_remote
        Provides the local server name and whether it is published to the local network. 
        """
        _rc(_gclibo.GServerStatus(self._buf, _buf_size))
        return str(self._buf.value.decode(_enc))
 
    def GSetServer(self, server_name):
        """@ingroup py_remote
        Set the new active server.
        """
        c_server_name = _GCStringIn(server_name.encode(_enc))
        _rc(_gclibo.GSetServer(c_server_name))
        return
 
    def GListServers(self):
        """@ingroup py_remote
        Provide a list of all available gcaps servers on the local network.
        """
        _rc(_gclibo.GListServers(self._buf, _buf_size))
        return str(self._buf.value.decode(_enc))
 
    def GPublishServer(self, server_name, publish, save):
        """@ingroup py_remote
        Publish local gcaps server to the network. 
        """
        c_server_name = _GCStringIn(server_name.encode(_enc))
        _rc(_gclibo.GPublishServer(c_server_name, publish, save))
        return
 
    def GRemoteConnections(self):
        """@ingroup py_remote
        Shows all remote addresses that are connected to the local server.
        """
        _rc(_gclibo.GRemoteConnections(self._buf, _buf_size))
        return str(self._buf.value.decode(_enc))
 
    def GInfo(self):
        """@ingroup py_connection
        Provides a useful connection string. Please include the output of this function on all support cases.
        """
        _rc(_gclibo.GInfo(self._gcon, self._buf, _buf_size))
        return str(self._buf.value.decode(_enc))
 
 
    def GIpRequests(self):
        """@ingroup py_connection
        Provides a dictionary of all Galil controllers requesting IP addresses via BOOT-P or DHCP.
 
        Returns a dictionary mapping 'model-serial' --> 'mac address'
        e.g. {'DMC4000-783': '00:50:4c:20:03:0f', 'DMC4103-9998': '00:50:4c:38:27:0e'}
 
        Linux/OS X users must be root to use GIpRequests() and have UDP access to bind and listen on port 67.
        """
        _rc(_gclibo.GIpRequests(self._buf, _buf_size)) #get the c string from gclib
        ip_req_dict = {}
        for line in str(self._buf.value.decode(_enc)).splitlines():
            line = line.replace(' ', '') #trim spaces throughout
            if (line == ""): continue
            fields = line.split(',')
            #fields go [model, serial number, mac]
            ip_req_dict[fields[0] + '-' + fields[1]] = fields[2] # e.g. DMC4000-783 maps to its MAC addr.
        return ip_req_dict
 
 
    def GAssign(self, ip, mac):
        """@ingroup py_connection
        Assigns IP address over the Ethernet to a controller at a given MAC address.
        Linux/OS X users must be root to use GAssign() and have UDP access to send on port 68.
        """
        c_ip = _GCStringIn(ip.encode(_enc))
        c_mac = _GCStringIn(mac.encode(_enc))
        _rc(_gclibo.GAssign(c_ip, c_mac))
        return
 
 
    def GAddresses(self):
        """@ingroup py_connection
        Provides a dictionary of all available connection addresses.
 
        Returns a dictionary mapping 'address' -> 'revision reports', where possible
        e.g. {}
        """
        _rc(_gclibo.GAddresses(self._buf, _buf_size))
        addr_dict = {}
        for line in str(self._buf.value.decode(_enc)).splitlines():
            fields = line.split(',')
            if len(fields) >= 2:
                addr_dict[fields[0]] = fields[1]
            else:
                addr_dict[fields[0]] = ''
 
        return addr_dict
 
 
    def GProgramDownload(self, program, preprocessor=""):
        """@ingroup py_memory
        Downloads a program to the controller's program buffer.
        See the gclib docs for preprocessor options.
        """
        self._cc()
        c_prog = _GCStringIn(program.encode(_enc))
        c_pre = _GCStringIn(preprocessor.encode(_enc))
        _rc(_gclib.GProgramDownload(self._gcon, c_prog, c_pre))
        return
 
 
    def GProgramUpload(self):
        """@ingroup py_memory
        Uploads a program from the controller's program buffer.
        """
        self._cc()
        _rc(_gclib.GProgramUpload(self._gcon, self._buf, _buf_size))
        return str(self._buf.value.decode(_enc))
 
 
    def GProgramDownloadFile(self, file_path, preprocessor=""):
        """@ingroup py_memory
        Program download from file.
        See the gclib docs for preprocessor options.
        """
        self._cc()
        c_path = _GCStringIn(file_path.encode(_enc))
        c_pre = _GCStringIn(preprocessor.encode(_enc))
        _rc(_gclibo.GProgramDownloadFile(self._gcon, c_path, c_pre))
        return
 
    def GProgramUploadFile(self, file_path):
        """@ingroup py_memory
        Program upload to file.
        """
        self._cc()
        c_path = _GCStringIn(file_path.encode(_enc))
        _rc(_gclibo.GProgramUploadFile(self._gcon, c_path))
        return
 
    def GArrayDownload(self, name, first, last, array_data):
        """@ingroup py_memory
        Downloads array data to a pre-dimensioned array in the controller's array table.
        array_data should be a list of values (e.g. int or float)
        """
        self._cc()
        c_name = _GCStringIn(name.encode(_enc))
        array_string = ""
        for val in array_data:
            array_string += str(val) + ","
        c_data = _GCStringIn(array_string[:-1].encode(_enc)) #trim trailing command
        _rc(_gclib.GArrayDownload(self._gcon, c_name, first, last, c_data))
        return
 
 
    def GArrayUploadFile(self, file_path, names = []):
        """@ingroup py_memory
        Uploads the entire controller array table or a subset and saves the data as a csv file specified by file_path.
        names is optional and should be a list of array names on the controller.
        """
        self._cc()
        c_path = _GCStringIn(file_path.encode(_enc))
        names_string = ''
        c_names = _GCStringIn(''.encode(_enc)) #in case empty list provided
        for name in names:
            names_string += name + ' '
 
        c_names = _GCStringIn(names_string[:-1].encode(_enc)) #trim trailing space
        _rc(_gclibo.GArrayUploadFile(self._gcon, c_path, c_names))
        return
 
 
    def GArrayDownloadFile(self, file_path):
        """@ingroup py_memory
        Downloads a csv file containing array data at file_path.
        """
        self._cc()
        c_path = _GCStringIn(file_path.encode(_enc))
        _rc(_gclibo.GArrayDownloadFile(self._gcon, c_path))
        return
 
 
    def GArrayUpload(self, name, first, last):
        """@ingroup py_memory
        Uploads array data from the controller's array table.
        """
        self._cc()
        c_name = _GCStringIn(name.encode(_enc))
        _rc(_gclib.GArrayUpload(self._gcon, c_name, first, last, 1, self._buf, _buf_size)) #1 is comma delimiter
        string_list = str(self._buf.value.decode(_enc)).split(',')
        float_list = []
        for s in string_list:
            float_list.append(float(s))
        return float_list
 
 
    def GTimeout(self, timeout):
        """@ingroup py_connection
        Set the library timeout. Set to -1 to use the initial library timeout, as specified in GOpen.
        """
        self._cc()
        _rc(_gclibo.GTimeout(self._gcon, timeout))
        self._timeout = timeout
        return
 
 
    @property
    def timeout(self):
        """@ingroup py_connection
        Convenience property read access to timeout value. If -1, gclib uses the initial library timeout, as specified in GOpen.
        """
        return self._timeout
 
    @timeout.setter
    def timeout(self, timeout):
        """@ingroup py_connection
        Convenience property write access to timeout value. Set to -1 to use the initial library timeout, as specified in GOpen.
        """
        self.GTimeout(timeout)
        return
 
 
    def GFirmwareDownload(self, file_path):
        """@ingroup py_memory
        Upgrade firmware.
        """
        self._cc()
        c_path = _GCStringIn(file_path.encode(_enc))
        _rc(_gclib.GFirmwareDownload(self._gcon, c_path))
        return
 
 
    def GMessage(self):
        """@ingroup py_unsolicited
        Provides access to unsolicited messages from the controller.
        """
        self._cc()
        _rc(_gclib.GMessage(self._gcon, self._buf, _buf_size))
        return str(self._buf.value.decode(_enc))
 
 
    def GMotionComplete(self, axes):
        """@ingroup py_controller
        Blocking call that returns once all axes specified have completed their motion.
        """
        self._cc()
        c_axes = _GCStringIn(axes.encode(_enc))
        _rc(_gclibo.GMotionComplete(self._gcon, c_axes))
        return
 
    def GInterrupt(self):
        """@ingroup py_unsolicited
        Provides access to PCI and UDP interrupts from the controller.
        """
        self._cc()
        status = _GStatus(0)
        _rc(_gclib.GInterrupt(self._gcon, byref(status)))
        return status.value
 
    def GSetupDownloadFile(self, file_path, options):
        """@ingroup py_memory
        Downloads specified sectors from a Galil compressed backup (gcb) file to a controller.
 
        Returns a dictionary with the controller information stored in the gcb file.
        If options is specified as 0, an additional "options" key will be in the dictionary indicating the info sectors available in the gcb
        """
        self._cc()
        c_path = _GCStringIn(file_path.encode(_enc))
 
        rc = _gclibo.GSetupDownloadFile(self._gcon, c_path, options, self._buf, _buf_size)
        if (options != 0):
            _rc(rc)
 
        info_dict = {}
        for line in str(self._buf.value.decode(_enc)).split("\"\n"):
            fields = line.split(',',1)
 
            if (fields[0] == ""): continue
            elif len(fields) >= 2:
                info_dict[fields[0].strip("\"\'")] = fields[1].strip("\"\'")
            else:
                info_dict[fields[0].strip("\"\'")] = ''
 
        if (options == 0):
            info_dict["options"] = rc
 
        return info_dict





Copyright © 2025-2026 Galassia-5 Satellite Programme