Skip to content

Commit 9b151c5

Browse files
committed
raw socket communication example
1 parent 826efa9 commit 9b151c5

File tree

2 files changed

+216
-0
lines changed

2 files changed

+216
-0
lines changed
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
# Ethernet Raw Socket Communication
2+
This python command line sample demonstrates how to communicate with a Thorlabs Ethernet capable power meter using
3+
raw python socket communication without any additional library like anyvisa.
4+
5+
# Details
6+
The powermeter uses a binary protocol to frame large request/response messages to multiple TCP packets. The given codes
7+
uses synchronous IO to implement this binary protocol for data exchange. The example does not contain any network device
8+
discovery. You need to know the IP and port number of the power meter to connect. Once the connection is established you
9+
can use the methods to send and receive text and binary request and response data. The implementation is totally platform
10+
independent.
11+
12+
For more technical background information about fast mode refer to SCPI command description. You can find a description for
13+
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.
14+
15+
## Limitations
16+
Please be aware the power meter will close the connection automatically if no communication is ongoing for 30 seconds.
17+
If you send an ernous (e.g. an unkown SCPI command) request, the receive function will issue a timeout error as the
18+
device does not send any response data.
19+
20+
## Supported Meters
21+
- PM103E
22+
- PM5020
Lines changed: 194 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,194 @@
1+
"""
2+
Example Thorlabs Power Meter raw ethernet socket communication
3+
Example Date of Creation 2024-08-07
4+
Example Date of Last Modification on Github 2024-08-07
5+
Version of Python 3.11.2
6+
Version of the Thorlabs SDK used none
7+
==================
8+
This examples shows how to communicate with a Thorlabs ethernet capable
9+
Power Meter using raw python socket communication. The sample implements
10+
the binary framing protocol to transfer SCPI request and response data
11+
as payload.
12+
"""
13+
import struct
14+
import socket
15+
16+
#Maximal size of a TCP segment of the Thorlabs power meter
17+
TCP_MAX_SEG_SIZE = 1440
18+
#Size of the header in bytes of a TCP segment of the Thorlabs power meter
19+
TL_TCP_SEG_HEADER_SIZE = 4
20+
#Maximal amount of payload bytes for a single frame
21+
MAX_FRAME_LEN = TCP_MAX_SEG_SIZE - TL_TCP_SEG_HEADER_SIZE
22+
23+
def TL_TCP_frame_transmit_bin(socket: socket, request: bytearray):
24+
"""
25+
Transmits a binary request to the Thorlabs power meter.
26+
The request is split into multiple frames if it is too large.
27+
28+
:param socket: The openend socket to use for transmission
29+
:param request: The binary request to transmit to power meter
30+
"""
31+
reqLen = len(request)
32+
writtenBytes = 0
33+
frameCnt = 0
34+
while writtenBytes < reqLen:
35+
#Limit max lenth of request
36+
frameLen = reqLen - writtenBytes
37+
if frameLen > MAX_FRAME_LEN:
38+
frameLen = MAX_FRAME_LEN
39+
40+
binReq = []
41+
if frameCnt == 0:
42+
binReq = struct.pack('<cch', bytes([0xCA]), bytes([frameCnt]), reqLen)
43+
binReq = binReq + request[writtenBytes : frameLen]
44+
else:
45+
binReq = struct.pack('<cch', bytes([0xCB]), bytes([frameCnt]), frameLen)
46+
binReq = binReq + request[writtenBytes : writtenBytes+frameLen]
47+
48+
#Send segment to server
49+
socket.sendall(binReq)
50+
51+
frameCnt += 1
52+
writtenBytes += frameLen
53+
54+
def TL_TCP_frame_receive_bin(socket: socket):
55+
"""
56+
Receives a binary response from the Thorlabs power meter.
57+
The response is assembled from multiple frames.
58+
You might want to set the timeout of the socket
59+
to avoid endless blocking. See socket.settimeout().
60+
If there has been an enour request send before, the
61+
function will also return a timeout error.
62+
63+
:param socket: The openend socket to use for reception
64+
:return: The binary response from power meter
65+
"""
66+
resp = bytearray()
67+
respIdx = 0
68+
69+
respHeader = bytearray(TL_TCP_SEG_HEADER_SIZE)
70+
respHeaderLen = 0
71+
respHeadSeq = 0
72+
73+
respBinSegLen = 0
74+
respBinTotalLen = 0
75+
76+
while True:
77+
data = socket.recv(1500)
78+
#Iterate all received bytes
79+
for d in data:
80+
#Already parsed frame start header?
81+
if respBinTotalLen == 0:
82+
#Waiting for start of frame header
83+
if d != 0xCA and respHeaderLen == 0:
84+
continue
85+
respHeader[respHeaderLen] = d
86+
respHeaderLen += 1
87+
88+
#Parsed entire header?
89+
if respHeaderLen == TL_TCP_SEG_HEADER_SIZE:
90+
#Verify header?
91+
if respHeader[1] != 0:
92+
respBinSegLen = respBinTotalLen = 0
93+
else:
94+
respBinSegLen = respBinTotalLen = struct.unpack('<h', respHeader[2:4])[0]
95+
if respBinSegLen > MAX_FRAME_LEN:
96+
respBinSegLen = MAX_FRAME_LEN
97+
respHeadSeq = 1
98+
resp = bytearray(respBinTotalLen)
99+
respIdx = 0
100+
101+
#Finally reset header parsing memory
102+
respHeaderLen = 0
103+
else:
104+
#Wait for start of conintous header?
105+
if respBinSegLen == 0:
106+
#Wait for start of continous frame
107+
if d != 0xCB and respHeaderLen == 0:
108+
continue
109+
respHeader[respHeaderLen] = d
110+
respHeaderLen += 1
111+
112+
#Parsed entire header?
113+
if respHeaderLen == TL_TCP_SEG_HEADER_SIZE:
114+
#Verify header sequence number
115+
if respHeader[1] != respHeadSeq:
116+
respBinSegLen = respBinTotalLen = 0
117+
else:
118+
respBinSegLen = struct.unpack('<h', respHeader[2:4])[0]
119+
respHeadSeq += 1
120+
121+
#Ensure segment size is not larger than expected total payload
122+
if respBinSegLen > respBinTotalLen:
123+
respBinSegLen = respBinTotalLen = 0
124+
125+
#Empty header? We are done -> go back to init
126+
if respBinSegLen == 0:
127+
respBinSegLen = respBinTotalLen = 0
128+
129+
#Finally reset header parsing memory
130+
respHeaderLen = 0
131+
132+
else:
133+
#Append data to response
134+
resp[respIdx] = d
135+
respIdx += 1
136+
respBinSegLen -= 1
137+
respBinTotalLen -= 1
138+
139+
if respBinTotalLen == 0:
140+
return resp
141+
142+
def TL_TCP_frame_receive(socket: socket):
143+
"""
144+
Receives a text response from the Thorlabs power meter.
145+
The response is assembled from multiple frames.
146+
For closer details read TL_TCP_frame_receive_bin()
147+
comments.
148+
149+
:param socket: The openend socket to use for reception
150+
"""
151+
binResp = TL_TCP_frame_receive_bin(socket)
152+
return binResp.decode('ASCII')
153+
154+
def TL_TCP_frame_transmit(socket: socket, req:str):
155+
"""
156+
Transmits a text request to the Thorlabs power meter..
157+
The request is split into multiple frames if it is too large.
158+
159+
:param socket: The openend socket to use for transmission
160+
:param req: The text request to transmit to power meter
161+
"""
162+
return TL_TCP_frame_transmit_bin(socket, req.encode('ASCII'))
163+
164+
def TL_TCP_query(socket: socket, req:str):
165+
"""
166+
Sends a text request to the Thorlabs power meter and returns the text response.
167+
168+
:param socket: The openend socket to use for transmission
169+
:param req: The text request to transmit to power meter
170+
"""
171+
TL_TCP_frame_transmit(socket, req)
172+
return TL_TCP_frame_receive(socket).strip()
173+
174+
def TL_TCP_query_bin(socket: socket, req:str):
175+
"""
176+
Sends a text request to the Thorlabs power meter and returns the binary response.
177+
178+
:param socket: The openend socket to use for transmission
179+
:param req: The text request to transmit to power meter
180+
"""
181+
TL_TCP_frame_transmit(socket, req)
182+
return TL_TCP_frame_receive_bin(socket)
183+
184+
if __name__ == '__main__':
185+
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
186+
#Set connection timeout
187+
s.settimeout(2)
188+
#Connect to PM with given IP and port number. Default port is 2000.
189+
s.connect(("10.10.4.22", 2000))
190+
191+
#Set read timeout
192+
s.settimeout(1)
193+
print(TL_TCP_query(s,"*IDN?\n"))
194+
print(TL_TCP_query(s,"SYST:ERR?\n"))

0 commit comments

Comments
 (0)