Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
60 changes: 60 additions & 0 deletions Python/Thorlabs ERM2xx Extinction Ratio Meters/ERM200_PyVisa.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
"""
ERM200_SCPI
Example Date of Creation: 2025-08-25
Example Date of Last Modification on Github: 2025-08-25
Version of Python: 3.13
Version of the Thorlabs SDK used: -
Tested with ERM210
==================
Example Description: The example shows how to use SCPI commands in Python with pyvisa
"""

import pyvisa
import time

def main():
rm = None
device = None
try:
#Opens a resource manager
rm = pyvisa.ResourceManager()


#Opens the connection to the device. The variable instr is the handle for the device.
# !!! In the USB number the serial number (M01...) and PID (0x8032) needs to be changed to the one of the connected device.
#Check with the Windows DEvice Manager
instr = rm.open_resource('USB0::0x1313::0x8032::M01099532::INSTR')


#print the device information
print(instr.query("*IDN?"))

result = instr.query("MEAS?")
er, phi = result.split(",")
print("Measured ER: ", er)
print("Measured Phi: ", phi)

finally:
#Close device in any case
if device is not None:
try:
device.close()
except Exception:
pass

#Close resource manager in any case
if rm is not None:
try:
instr.close()
except Exception:
pass

#close out session
rm.close()


import sys
print(sys.version)

if __name__ == "__main__":
main()
4 changes: 4 additions & 0 deletions Python/Thorlabs ERM2xx Extinction Ratio Meters/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,7 @@ Please note that the code connects to the first available ERM2xx device. If you

lib.TLERM200_getRsrcName(erm_handle, 0, resource)

### ERM200_PyVisa.py
This sample code shows how you can control a Thorlabs ERM200, ERM210 or ERM220 extinction ratio meter in Python with SCPI commands and PyVisa.
Use Visa drivers to run the example.

Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
# Ethernet Raw Socket Communication
This python command line sample demonstrates how to communicate with a Thorlabs Ethernet capable power meter using
raw python socket communication without any additional library like anyvisa.

# Details
The powermeter uses a binary protocol to frame large request/response messages to multiple TCP packets. The given codes
uses synchronous IO to implement this binary protocol for data exchange. The example does not contain any network device
discovery. You need to know the IP and port number of the power meter to connect. Once the connection is established you
can use the methods to send and receive text and binary request and response data. The implementation is totally platform
independent.

For more technical background information about fast mode refer to SCPI command description. You can find a description for
every Meter in the [commandDocu](../commandDocu) folder. For example the PM103E [SCPI command description](https://htmlpreview.github.io/?https://github.com/Selanarixx/Light_Analysis_Examples/blob/develop/Python/Thorlabs%20PMxxx%20Power%20Meters/scpi/commandDocu/pm103E.html) html file.

## Limitations
Please be aware the power meter will close the connection automatically if no communication is ongoing for 30 seconds.
If you send an ernous (e.g. an unkown SCPI command) request, the receive function will issue a timeout error as the
device does not send any response data.

## Supported Meters
- PM103E
- PM5020
Original file line number Diff line number Diff line change
@@ -0,0 +1,194 @@
"""
Example Thorlabs Power Meter raw ethernet socket communication
Example Date of Creation 2024-08-07
Example Date of Last Modification on Github 2024-08-07
Version of Python 3.11.2
Version of the Thorlabs SDK used none
==================
This examples shows how to communicate with a Thorlabs ethernet capable
Power Meter using raw python socket communication. The sample implements
the binary framing protocol to transfer SCPI request and response data
as payload.
"""
import struct
import socket

#Maximal size of a TCP segment of the Thorlabs power meter
TCP_MAX_SEG_SIZE = 1440
#Size of the header in bytes of a TCP segment of the Thorlabs power meter
TL_TCP_SEG_HEADER_SIZE = 4
#Maximal amount of payload bytes for a single frame
MAX_FRAME_LEN = TCP_MAX_SEG_SIZE - TL_TCP_SEG_HEADER_SIZE

def TL_TCP_frame_transmit_bin(socket: socket, request: bytearray):
"""
Transmits a binary request to the Thorlabs power meter.
The request is split into multiple frames if it is too large.

:param socket: The openend socket to use for transmission
:param request: The binary request to transmit to power meter
"""
reqLen = len(request)
writtenBytes = 0
frameCnt = 0
while writtenBytes < reqLen:
#Limit max lenth of request
frameLen = reqLen - writtenBytes
if frameLen > MAX_FRAME_LEN:
frameLen = MAX_FRAME_LEN

binReq = []
if frameCnt == 0:
binReq = struct.pack('<cch', bytes([0xCA]), bytes([frameCnt]), reqLen)
binReq = binReq + request[writtenBytes : frameLen]
else:
binReq = struct.pack('<cch', bytes([0xCB]), bytes([frameCnt]), frameLen)
binReq = binReq + request[writtenBytes : writtenBytes+frameLen]

#Send segment to server
socket.sendall(binReq)

frameCnt += 1
writtenBytes += frameLen

def TL_TCP_frame_receive_bin(socket: socket):
"""
Receives a binary response from the Thorlabs power meter.
The response is assembled from multiple frames.
You might want to set the timeout of the socket
to avoid endless blocking. See socket.settimeout().
If there has been an enour request send before, the
function will also return a timeout error.

:param socket: The openend socket to use for reception
:return: The binary response from power meter
"""
resp = bytearray()
respIdx = 0

respHeader = bytearray(TL_TCP_SEG_HEADER_SIZE)
respHeaderLen = 0
respHeadSeq = 0

respBinSegLen = 0
respBinTotalLen = 0

while True:
data = socket.recv(1500)
#Iterate all received bytes
for d in data:
#Already parsed frame start header?
if respBinTotalLen == 0:
#Waiting for start of frame header
if d != 0xCA and respHeaderLen == 0:
continue
respHeader[respHeaderLen] = d
respHeaderLen += 1

#Parsed entire header?
if respHeaderLen == TL_TCP_SEG_HEADER_SIZE:
#Verify header?
if respHeader[1] != 0:
respBinSegLen = respBinTotalLen = 0
else:
respBinSegLen = respBinTotalLen = struct.unpack('<h', respHeader[2:4])[0]
if respBinSegLen > MAX_FRAME_LEN:
respBinSegLen = MAX_FRAME_LEN
respHeadSeq = 1
resp = bytearray(respBinTotalLen)
respIdx = 0

#Finally reset header parsing memory
respHeaderLen = 0
else:
#Wait for start of conintous header?
if respBinSegLen == 0:
#Wait for start of continous frame
if d != 0xCB and respHeaderLen == 0:
continue
respHeader[respHeaderLen] = d
respHeaderLen += 1

#Parsed entire header?
if respHeaderLen == TL_TCP_SEG_HEADER_SIZE:
#Verify header sequence number
if respHeader[1] != respHeadSeq:
respBinSegLen = respBinTotalLen = 0
else:
respBinSegLen = struct.unpack('<h', respHeader[2:4])[0]
respHeadSeq += 1

#Ensure segment size is not larger than expected total payload
if respBinSegLen > respBinTotalLen:
respBinSegLen = respBinTotalLen = 0

#Empty header? We are done -> go back to init
if respBinSegLen == 0:
respBinSegLen = respBinTotalLen = 0

#Finally reset header parsing memory
respHeaderLen = 0

else:
#Append data to response
resp[respIdx] = d
respIdx += 1
respBinSegLen -= 1
respBinTotalLen -= 1

if respBinTotalLen == 0:
return resp

def TL_TCP_frame_receive(socket: socket):
"""
Receives a text response from the Thorlabs power meter.
The response is assembled from multiple frames.
For closer details read TL_TCP_frame_receive_bin()
comments.

:param socket: The openend socket to use for reception
"""
binResp = TL_TCP_frame_receive_bin(socket)
return binResp.decode('ASCII')

def TL_TCP_frame_transmit(socket: socket, req:str):
"""
Transmits a text request to the Thorlabs power meter..
The request is split into multiple frames if it is too large.

:param socket: The openend socket to use for transmission
:param req: The text request to transmit to power meter
"""
return TL_TCP_frame_transmit_bin(socket, req.encode('ASCII'))

def TL_TCP_query(socket: socket, req:str):
"""
Sends a text request to the Thorlabs power meter and returns the text response.

:param socket: The openend socket to use for transmission
:param req: The text request to transmit to power meter
"""
TL_TCP_frame_transmit(socket, req)
return TL_TCP_frame_receive(socket).strip()

def TL_TCP_query_bin(socket: socket, req:str):
"""
Sends a text request to the Thorlabs power meter and returns the binary response.

:param socket: The openend socket to use for transmission
:param req: The text request to transmit to power meter
"""
TL_TCP_frame_transmit(socket, req)
return TL_TCP_frame_receive_bin(socket)

if __name__ == '__main__':
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
#Set connection timeout
s.settimeout(2)
#Connect to PM with given IP and port number. Default port is 2000.
s.connect(("10.10.4.22", 2000))

#Set read timeout
s.settimeout(1)
print(TL_TCP_query(s,"*IDN?\n"))
print(TL_TCP_query(s,"SYST:ERR?\n"))