Skip to content

Commit

Permalink
Added preliminary Linux support
Browse files Browse the repository at this point in the history
Updated README
  • Loading branch information
ravngr committed Oct 27, 2023
1 parent 4bdcccb commit db4f11c
Show file tree
Hide file tree
Showing 4 changed files with 43 additions and 25 deletions.
2 changes: 1 addition & 1 deletion CITATION.cff
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,6 @@ authors:
given-names: Mahnaz
orcid: https://orcid.org/0000-0002-3520-1553
title: "pylinkam"
version: 1.2.2
version: 1.2.3
doi: 10.5281/zenodo.6758012
date-released: 2022-06-27
13 changes: 6 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,10 @@ We use a Linkam HFS600E-PB4 stage with T96 controller for gas sensing experiment
## Installation
Note that the Linkam SDK binary files (`LinkamSDK_release.dll` or `LinkamSDK_debug.dll`) and the required license file (typically `Linkam.lsk`) are **not** distributed as part of this module.

By default, the module will look for the Linkam SDK dll using the `$PATH` environment variable, appending the module directory before searching.
By default, the module will look for the Linkam SDK binary using the `$PATH` environment variable via the `ctypes` module and will automatically append the module directory before searching.

0. If necessary, rename `LinkamSDK.dll` (recent versions) to `LinkamSDK_release.dll`
1. Place `LinkamSDK_release.dll` (or `LinkamSDK_debug.dll`) and `Linkam.lsk` files inside the `pylinkam` module folder (the one that contains `__init__.py`). Alternately, at runtime you can specify a path to access these files.
2. Run `demo.py` to check for any issues. This will set the stage temperature to 25°C temporarily.
1. Place `LinkamSDK.dll` (debug or release) and `Linkam.lsk` files inside the `pylinkam` module folder (the one that contains `__init__.py`). Alternately, at runtime you can specify a binary name (remove the `.dll` extension on Windows) and path to access these files.
2. Run `demo.py` to check for any connection/path issues. This will set the stage temperature to 25°C temporarily.

## Usage
Initialise the SDK by creating an instance of `pylinkam.sdk.SDKWrapper` providing optional paths for SDK binary files and the license file. Once initialised, use the `connect()` method to create a context manager for the connection to a device.
Expand All @@ -43,18 +42,18 @@ This library has been developed for the following Linkam instruments/addons, a c
- [x] RH95 Humidity Controller
- [ ] LNP96 Cooling Option (should work)

Note that connecting multiple devices to a single host is also untested.
Note that connecting multiple devices to a single host is untested, though the connect functions per the Linkam API *can* accept a serial number.

## Tested API Versions and Platforms

- [x] `v3.0.5.5` on Windows 10
- [x] `v3.0.15.35` on Windows 10

In theory the SDK binary files for Linux should have identical mappings, but this hasn't been tested.
In theory the SDK binary files for Linux should have identical mappings, but this hasn't been tested. Versions `< 1.2.3` used `WinDLL` to load the Linkam SDK binary, however more recent versions will automatically detect the platform and use `CDLL` when not on Windows. We don't have hardware connected to a Linux machine to test, so please report any issues.

## Acknowledgments

Developed at [Swinburne University of Technology](https://swin.edu.au). If used in an academic project, please consider citing this work as it helps attract funding and track research outputs:
Developed at [Swinburne University of Technology](https://swin.edu.au). If used in an academic or research project, please consider citing this work as it helps attract funding and track research outputs:

```
C. J. Harrison and M. Shafiei. pylinkam. (2022). [Online]. doi: https://doi.org/10.5281/zenodo.6758012
Expand Down
2 changes: 1 addition & 1 deletion pylinkam/__init__.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
__app_name__ = 'pylinkam'
__version__ = '1.2.2'
__version__ = '1.2.3'

__author__ = 'Chris Harrison'
__credits__ = [__author__]
Expand Down
51 changes: 35 additions & 16 deletions pylinkam/sdk.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,8 +37,14 @@ class ControllerConnectError(Exception):
class SDKWrapper:
""" Wrapper for Linkam SDK. """

# Assume SDK files are in the library root, can be overriden at instantiation
_DEFAULT_SDK_ROOT_PATH = os.path.dirname(os.path.abspath(__file__))

_DEFAULT_BIN_NAME_LINUX = 'libLinkamSDK.so'

# Strip the .dll suffix, this is re-added automatically
_DEFAULT_BIN_NAME_WINDOWS = 'LinkamSDK'

class Connection:
""" Wrapper for connection to Linkam controller. """

Expand Down Expand Up @@ -331,22 +337,29 @@ def set_value(self, value_type: interface.StageValueType, n: typing.Any) -> bool
comm_handle=self._handle
))

def __init__(self, sdk_root_path: typing.Optional[str] = None, sdk_log_path: typing.Optional[str] = None,
sdk_license_path: typing.Optional[str] = None, debug: bool = False):
def __init__(self, sdk_root_path: typing.Optional[str] = None, sdk_bin_name: typing.Optional[str] = None,
sdk_log_path: typing.Optional[str] = None, sdk_license_path: typing.Optional[str] = None):
""" Initialise the SDK, loading the required binary files.
:param sdk_root_path: search path for SDK binary files, defaults to module directory
:param sdk_bin_name: SDK binary name (on Windows remove .dll extension), defaults to platform-dependant name
:param sdk_log_path: path for SDK logging, defaults to SDK directory
:param sdk_license_path: path for SDL license file, defaults to SDK directory
:param debug: if True use debug DLL, else use release version
"""
self._sdk_root_path = sdk_root_path or self._DEFAULT_SDK_ROOT_PATH

self._sdk: typing.Optional[ctypes.WinDLL] = None
self._sdk: typing.Optional[ctypes.CDLL] = None
self._sdk_lock = threading.RLock()

# Setup DLL name and paths
self._sdk_dll_name = f"LinkamSDK_{'debug' if debug else 'release'}.dll"
if sdk_bin_name is None:
if os.name == 'nt':
self._sdk_bin_name = self._DEFAULT_BIN_NAME_WINDOWS
else:
self._sdk_bin_name = self._DEFAULT_BIN_NAME_LINUX
else:
self._sdk_bin_name = sdk_bin_name

self._sdk_log_path = sdk_log_path or os.path.join(self.sdk_root_path, 'Linkam.log')
self._sdk_license_path = sdk_license_path or os.path.join(self.sdk_root_path, 'Linkam.lsk')

Expand All @@ -362,14 +375,18 @@ def close(self) -> None:
self._sdk = None

@property
def sdk(self) -> ctypes.WinDLL:
def sdk(self) -> ctypes.CDLL:
if self._sdk is None:
# Load SDK DLL
if os.name == 'nt':
loader: typing.Type[ctypes.CDLL] = ctypes.WinDLL
else:
loader = ctypes.CDLL

try:
sdk = ctypes.WinDLL(self.sdk_dll_name)
sdk = loader(self.sdk_bin_name)
except FileNotFoundError:
# Try absolute path
sdk = ctypes.WinDLL(os.path.join(self.sdk_root_path, self.sdk_dll_name))
# Re-attempt as an absolute path
sdk = loader(os.path.join(self.sdk_root_path, self.sdk_bin_name))

if sdk is None:
raise SDKError('Linkam SDK was not loaded')
Expand Down Expand Up @@ -418,8 +435,8 @@ def sdk(self) -> ctypes.WinDLL:
return self._sdk

@property
def sdk_dll_name(self) -> str:
return self._sdk_dll_name
def sdk_bin_name(self) -> str:
return self._sdk_bin_name

@property
def sdk_license_path(self) -> str:
Expand Down Expand Up @@ -572,8 +589,8 @@ def _connect_serial(self, port: str) -> Connection:
# Configure serial connection
comm_info = interface.CommsInfo()

port = ctypes.create_string_buffer(port.encode())
self.sdk.linkamInitialiseSerialCommsInfo(ctypes.pointer(comm_info), port)
port_enc = ctypes.create_string_buffer(port.encode())
self.sdk.linkamInitialiseSerialCommsInfo(ctypes.pointer(comm_info), port_enc)

return self._connect_common(comm_info)

Expand All @@ -587,9 +604,11 @@ def _connect_usb(self, serial_number: typing.Optional[str] = None) -> Connection
comm_info = interface.CommsInfo()

if serial_number is not None:
serial_number = ctypes.create_string_buffer(serial_number.encode())
serial_number_enc = ctypes.create_string_buffer(serial_number.encode())
else:
serial_number_enc = None

self.sdk.linkamInitialiseUSBCommsInfo(ctypes.pointer(comm_info), serial_number)
self.sdk.linkamInitialiseUSBCommsInfo(ctypes.pointer(comm_info), serial_number_enc)

return self._connect_common(comm_info)

Expand Down

0 comments on commit db4f11c

Please sign in to comment.