Skip to content

Commit b72a9fc

Browse files
Merge branch 'main' into Development
2 parents 9b151c5 + 04d0ccf commit b72a9fc

File tree

8 files changed

+444
-3
lines changed

8 files changed

+444
-3
lines changed

Python/Thorlabs PMxxx Power Meters/TLPMX_dll/TLPMX.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2352,7 +2352,7 @@ def getPowerAutorange(self, powerAutorangeMode, channel):
23522352
23532353
Return values:
23542354
TLPM_AUTORANGE_POWER_OFF (0): power auto range disabled
2355-
TLPM_AUTORANGE_POWER_ON (0): power auto range enabled
2355+
TLPM_AUTORANGE_POWER_ON (1): power auto range enabled
23562356
23572357
channel(c_uint16) : Number of the sensor channel.
23582358

Python/Thorlabs PMxxx Power Meters/scpi/Readme.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ anyvisa library. Some others use the third-party pyvisa python library based on
77
## Included Examples
88

99
### Simple Example
10-
The example ```PMxxx_SCPI_pyvisa.py``` shows how to to open a powermeter connected with USB using the pyvisa library, make settings and get measurement values.
10+
The example ```PMxxx_SCPI_pyvisa.py``` shows how to to open a powermeter connected with USB using the pyvisa library, make settings and get measurement values. It will not work for ethernet connection, check the anyvisa examples for that case.
1111

1212
### Fast Mode
1313
Demonstrates how to query fast measurement stream of Thorlabs Power Meter.
@@ -24,7 +24,7 @@ The Thorlabs Power Meters supports Scope Mode to measure and store a software or
2424
For closer details refer to [Readme](scopeMode). Available for PM6x, PM103, PM103E and PM5020.
2525

2626
### Open Anyvisa
27-
Minimal template script ```PMxxx_SCPI_OpenAnyvisa.py``` to open a known instrument resource using anvisa library.
27+
Minimal template script ```PMxxx_SCPI_OpenAnyvisa.py``` to open a known instrument resource using anyvisa library.
2828

2929
### Search Anyvisa
3030
Minimal template script ```PMxxx_SCPI_SearchAnyvisa.py``` to run instrument search and open one of the devices found using anvisa library.
Lines changed: 176 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,176 @@
1+
import os
2+
import ctypes
3+
4+
class TLWAVE:
5+
6+
def __init__(self, resourceName = None, IDQuery = True, resetDevice = False):
7+
"""
8+
This function initializes the instrument driver session and performs the following initialization actions:
9+
10+
(1) Opens a session to the Default Resource Manager resource and a session to the specified device using the Resource Name.
11+
(2) Performs an identification query on the instrument.
12+
(3) Resets the instrument to a known state.
13+
(4) Sends initialization commands to the instrument.
14+
(5) Returns an instrument handle which is used to distinguish between different sessions of this instrument driver.
15+
16+
Notes:
17+
(1) Each time this function is invoked a unique session is opened.
18+
19+
Args:
20+
resourceName
21+
IDQuery (bool):This parameter specifies whether an identification query is performed during the initialization process.
22+
23+
VI_TRUE (1): Do query (default).
24+
VI_FALSE (0): Skip query.
25+
26+
27+
resetDevice (bool):This parameter specifies whether the instrument is reset during the initialization process.
28+
29+
VI_TRUE (1): instrument is reset
30+
VI_FALSE (0): no reset (default)
31+
32+
33+
"""
34+
possiblepaths = [os.path.dirname(os.path.abspath(__file__)),
35+
r"C:\Program Files (x86)\Thorlabs\OPM"]
36+
37+
if ctypes.sizeof(ctypes.c_voidp) == 4:
38+
dll_name = "TLWAVE_32.dll"
39+
else:
40+
dll_name = "TLWAVE_64.dll"
41+
42+
self.dll = None
43+
last_err = None
44+
for path in possiblepaths:
45+
fullpath = os.path.join(path,dll_name)
46+
if os.path.isfile(fullpath):
47+
try:
48+
self.dll = ctypes.cdll.LoadLibrary(fullpath)
49+
break
50+
except OSError as e:
51+
print(e)
52+
last_err = e
53+
54+
if self.dll is None:
55+
print(last_err)
56+
raise RuntimeError(f"Could not find {dll_name} in {" or ".join(possiblepaths)}")
57+
58+
self.devSession = ctypes.c_long()
59+
self.devSession.value = 0
60+
if resourceName is not None:
61+
pInvokeResult = self.dll.TLWAVE_init( ctypes.create_string_buffer(resourceName), ctypes.c_bool(IDQuery), ctypes.c_bool(resetDevice), ctypes.byref(self.devSession))
62+
self.__testForError(pInvokeResult)
63+
64+
65+
def __testForError(self, status):
66+
if status < 0:
67+
self.__throwError(status)
68+
return status
69+
70+
def __throwError(self, code):
71+
msg = ctypes.create_string_buffer(1024)
72+
self.dll.TLWAVE_error_message(self.devSession, ctypes.c_int(code), msg)
73+
raise NameError(ctypes.c_char_p(msg.raw).value)
74+
75+
def getRsrcName(self, index):
76+
"""
77+
This function gets the resource name string needed to open a device.
78+
79+
Notes:
80+
(1) The data provided by this function was updated at the last call of <Find Resources>.
81+
82+
Args:
83+
index(uint32) : This parameter accepts the index of the device to get the resource descriptor from.
84+
85+
Notes:
86+
(2) The index is zero based. The maximum index to be used here is one less than the number of devices found by the last call of <Find Resources>.
87+
88+
Returns:
89+
resourceName(string) : This parameter returns the resource descriptor. Use this descriptor to specify the device.
90+
"""
91+
pyresourceName = ctypes.create_string_buffer(1024)
92+
pInvokeResult = self.dll.TLWAVE_getRsrcName(self.devSession, ctypes.c_uint32(index), pyresourceName)
93+
self.__testForError(pInvokeResult)
94+
return ctypes.c_char_p(pyresourceName.raw).value
95+
96+
def findRsrc(self):
97+
"""
98+
This function finds all driver compatible devices attached to the PC and returns the number of found devices.
99+
100+
Note:
101+
(1) The function additionally stores information like system name about the found resources internally. This information can be retrieved with further functions from the class, e.g. <Get Resource Description> and <Get Resource Information>.
102+
103+
104+
Args:
105+
106+
Returns:
107+
resourceCount(uint32) : The number of connected devices that are supported by this driver.
108+
"""
109+
pyresourceCount = ctypes.c_uint32(0)
110+
pInvokeResult = self.dll.TLWAVE_findRsrc(self.devSession, ctypes.byref(pyresourceCount))
111+
self.__testForError(pInvokeResult)
112+
return pyresourceCount.value
113+
114+
def __del__(self):
115+
#destructor
116+
if self.dll is not None:
117+
self.close()
118+
119+
def close(self):
120+
"""
121+
This function closes the instrument driver session.
122+
123+
Note: The instrument must be reinitialized to use it again.
124+
125+
Returns:
126+
int: The return value, 0 is for success
127+
"""
128+
pInvokeResult = self.dll.TLWAVE_close(self.devSession)
129+
return pInvokeResult
130+
131+
def writeRaw(self, command):
132+
"""
133+
This function writes directly to the instrument.
134+
135+
Args:
136+
command(char_p) : Null terminated command string to send to the instrument.
137+
138+
Returns:
139+
"""
140+
pInvokeResult = self.dll.TLWAVE_writeRaw(self.devSession, ctypes.c_char_p(command.encode('utf-8')))
141+
self.__testForError(pInvokeResult)
142+
143+
def readRaw(self, size):
144+
"""
145+
This function reads directly from the instrument.
146+
147+
Args:
148+
149+
size(uint32) : This parameter specifies the buffer size.
150+
151+
Notes:
152+
(1) If received data is less than buffer size, the buffer is additionally terminated with a '' character.
153+
(2) If received data is same as buffer size no '' character is appended. It's the caller's responsibility to make sure a buffer is '' terminated if the caller wants to interpret the buffer as string.
154+
(3) You may pass VI_NULL if you don't need this value.
155+
156+
Returns:
157+
buffer(string) : Byte buffer that receives the data read from the instrument.
158+
returnCount(uint32) : Number of bytes actually transferred and filled into Buffer. This number doesn't count the additional termination '' character. If Return Count == size of the buffer, the content will not be '' terminated.
159+
"""
160+
pybuffer = ctypes.create_string_buffer(1024)
161+
pyreturnCount = ctypes.c_uint32(0)
162+
pInvokeResult = self.dll.TLWAVE_readRaw(self.devSession, pybuffer, ctypes.c_uint32(size), ctypes.byref(pyreturnCount))
163+
self.__testForError(pInvokeResult)
164+
return ctypes.c_char_p(pybuffer.raw).value, pyreturnCount.value
165+
166+
def readRegister(self, registerID:int) -> int:
167+
registervalue = ctypes.c_int16(-1)
168+
pInvokeResult = self.dll.TLWAVE_readRegister(self.devSession, registerID, ctypes.byref(registervalue))
169+
self.__testForError(pInvokeResult)
170+
return registervalue.value
171+
172+
def getID(self) -> str:
173+
self.writeRaw("*IDN?\n")
174+
rawValue,numbytes = self.readRaw(1024)
175+
id = rawValue.decode('utf-8').strip()
176+
return id
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
## Included Example
2+
3+
### Thorlabs WM20X Wavelength Meters
4+
In this folder you will find sample codes showing how to control and acquire from
5+
Thorlabs WM20X Wavelength Meters.
6+
7+
There are 4 examples in this folder:
8+
9+
* [Example 1 - Read the wavelength using TCP](./Thorlabs_WM_example_01.py)
10+
* [Example 2 - Read the wavelength using TLWAVE dll (USB/TCP)](./Thorlabs_WM_example_02.py)
11+
* [Example 3 - Read the wavelength using RS232](./Thorlabs_WM_example_03.py)
12+
* [Example 4 - Set fast mode, then read wavelength and estimated power](./Thorlabs_WM_example_04.py)
13+
14+
15+
### Requirements
16+
* The example with TCP-connection is cross-plattform compatible, and uses only native python.
17+
18+
* The example with USB connection requires windows and installation of [Optical power meter software](https://www.thorlabs.com/software_pages/ViewSoftwarePage.cfm?Code=OPM), which includes the USB-driver as well as the needed TLWAVE dll. The DLL is wrapepd by PyTLWAVE.py which is found in the same folder as the example.
19+
20+
* The example with RS232-connection is cross-plattform compatible, but utilizes pyserial.
21+
22+
### More information
23+
For details about the available SCPI commands please refer to the instrument manual.
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
"""
2+
Thorlabs_VM_example_01
3+
Example Date of Creation: 2025-08-22
4+
Example Date of Last Modification on Github: 2025-08-22
5+
Version of Python: 3.12.4
6+
Version of Platform Firmware: 0.9.12
7+
Version of Interferometer Firmware: 0.58
8+
==================
9+
Example Description: Read the wavelength from Thorlabs Wavelength Meter WM20X using TCP
10+
"""
11+
import time
12+
import socket
13+
import struct
14+
15+
# Set the address to the IP number of your wavelength meter
16+
wavemeteradress = "192.168.55.6"
17+
18+
def build_request_frame(cmd):
19+
# This creates the TCP frames that are sent to the wavelength meter.
20+
# The basic example only supports single frame requests
21+
# Full example of the TCP frames can be found with the PM5020 examples
22+
payload = cmd.encode()
23+
FRAME_START = 0xCA
24+
return struct.pack('<BBH', FRAME_START, 0, len(payload)) + payload
25+
26+
def recv_response(sock):
27+
# Recieves and parses a TCP packet from the wavelength meter.
28+
data = b''
29+
seq = 0
30+
HEADER_SIZE = 4
31+
while True:
32+
# Loop because response might be multi frame
33+
hdr = sock.recv(HEADER_SIZE)
34+
if len(hdr) < HEADER_SIZE:
35+
break
36+
start, cnt, plen = struct.unpack('<BBH', hdr)
37+
if cnt != seq:
38+
break
39+
part = sock.recv(plen)
40+
data += part
41+
if start != 0xCB:
42+
break # no more frames
43+
seq += 1
44+
return data.decode(errors='ignore')
45+
46+
def send_cmd(sock, cmd):
47+
# Build and send a SCPI command to the wavelength meter
48+
sock.sendall(build_request_frame(cmd))
49+
50+
def request_response(sock, cmd):
51+
# Send a SCPI command to the wavelength meter and
52+
# read the answer from it
53+
send_cmd(sock, cmd)
54+
return recv_response(sock)
55+
56+
with socket.create_connection((wavemeteradress, 2000), timeout=5) as s:
57+
while True:
58+
try:
59+
response = request_response(s, "MEAS:WAV?\n")
60+
wavelength = float(response.strip())
61+
print(f"{wavelength} nm(vac)")
62+
except Exception as e:
63+
print(e)
64+
time.sleep(1)
Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
"""
2+
Thorlabs_VM_example_02
3+
Example Date of Creation: 2025-08-22
4+
Example Date of Last Modification on Github: 2025-08-22
5+
Version of Python: 3.13.7
6+
Version of the Thorlabs SDK used: OPM 6.5, TLWAVE_32.dll
7+
Version of Platform Firmware: 0.9.12
8+
Version of Interferometer Firmware: 0.58
9+
==================
10+
Example Description: Read the wavelength from Thorlabs WM20X Wavelength Meter using TLWAVE dll (USB/TCP)
11+
"""
12+
import time
13+
from PyTLWAVE import TLWAVE
14+
15+
discover = True
16+
17+
wavemeter = None
18+
19+
if discover:
20+
# Here we try to find wavemeters using USB
21+
wavemetersession = TLWAVE()
22+
23+
deviceCount = wavemetersession.findRsrc()
24+
print("devices found: " + str(deviceCount))
25+
26+
resourceNames = []
27+
for i in range(deviceCount):
28+
localName = wavemetersession.getRsrcName(i)
29+
print(localName)
30+
resourceNames.append(localName)
31+
32+
wavemetersession.close()
33+
34+
wavemeter = TLWAVE(resourceNames[0])
35+
else:
36+
# Here is an example of connecting to using ethernet to a specific ip
37+
wavemeter = TLWAVE(b"TCPIP0::192.168.55.6::2000::SOCKET", False)
38+
39+
def waitfornewdata():
40+
"""Wait for new data flag (9th bit) in status register"""
41+
newdata = False
42+
newstatus = False
43+
while not newdata:
44+
wavemeter.writeRaw("STAT:OPER:COND?\n")
45+
value, numbytes = wavemeter.readRaw(1024)
46+
try:
47+
reg_value = int(value)
48+
if reg_value is not None:
49+
if (reg_value & 512) == 512: # Checking the 9th bit
50+
newdata = True
51+
if (reg_value & 1024) == 1024: # Checking the 10th bit
52+
newstatus = True
53+
except Exception as e:
54+
print(f"Error parsing response: {e}, got: '{value}'")
55+
56+
if not newdata:
57+
time.sleep(0.1)
58+
if newstatus:
59+
wavemeter.writeRaw("SENS:STAT:COND?\n")
60+
rawValue, numbytes = wavemeter.readRaw(1024)
61+
statuscode = int(rawValue)
62+
print(f"New status: {statuscode}")
63+
newstatus = False
64+
65+
66+
67+
for i in range(1000):
68+
# Loop and print the wavelength continuously
69+
wavemeter.writeRaw("FETC:WAV?\n")
70+
rawValue, numbytes = wavemeter.readRaw(1024)
71+
wavelength = float(rawValue)
72+
print(f"{wavelength} nm(vac)")
73+
waitfornewdata()
74+
75+
wavemeter.close()
76+

0 commit comments

Comments
 (0)