Capture the display of a Rigol DS1000Z series oscilloscope by LAN using LXI SCPI commands
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

OscScreenGrabLAN.py 10KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316
  1. #!/usr/bin/env python
  2. from telnetlib_receive_all import Telnet
  3. from Rigol_functions import *
  4. import time
  5. from PIL import Image
  6. import StringIO
  7. import sys
  8. import os
  9. import platform
  10. import logging
  11. __version__ = 'v1.0.0'
  12. __author__ = 'RoGeorge'
  13. #
  14. # TODO: Replace the fixed delay between commands with *OPC? (Operation Complete) query
  15. # TODO: Add debug mode
  16. # TODO: Add debug switch
  17. # TODO: Add Python and modules version
  18. # TODO: Add script version
  19. # TODO: Add message for csv data points: mdep (all) or 1200 (screen), depending on RUN/STOP state, MATH and WAV:MODE
  20. # TODO: Clarify info, warning, error, debug and print messages
  21. # TODO: Remove debugging print lines
  22. # TODO: Add .gitignore
  23. #
  24. """
  25. # TODO: Use "waveform:data?" multiple times to extract the whole 12M points
  26. in order to overcome the "Memory lack in waveform reading!" screen message
  27. """
  28. # TODO: Detect if the osc is in RUN or in STOP mode (looking at the length of data extracted)
  29. # TODO: Investigate scaling. Sometimes 3.0e-008 instead of expected 3.0e-000
  30. # TODO: Add timestamp and mark the trigger point as t0
  31. # TODO: Use channels label instead of chan1, chan2, chan3, chan4, math
  32. # TODO: Add command line parameters file path
  33. # TODO: Speed-up the transfer, try to replace Telnet with direct TCP
  34. # TODO: Add GUI
  35. # TODO: Add browse and custom filename selection
  36. # TODO: Create executable distributions
  37. #
  38. # Set the desired logging level (DEBUG, INFO, WARNING, ERROR, CRITICAL)
  39. logging.basicConfig(level=logging.INFO,
  40. format='%(asctime)s - %(levelname)s - %(message)s',
  41. filename=os.path.basename(sys.argv[0]) + '.log')
  42. logging.info("Log message: INFO level set.")
  43. # Update the next lines for your own default settings:
  44. path_to_save = "captures/"
  45. save_format = "PNG"
  46. IP_DS1104Z = "192.168.1.3"
  47. # Rigol/LXI specific constants
  48. port = 5555
  49. expected_len = 1152068
  50. TMC_header_len = 11
  51. terminator_len = 3
  52. big_wait = 10
  53. small_wait = 1
  54. company = 0
  55. model = 1
  56. serial = 2
  57. # Check command line parameters
  58. script_name = os.path.basename(sys.argv[0])
  59. def print_help():
  60. # Print usage
  61. print
  62. print "Usage:"
  63. print " " + "python " + script_name + " png|bmp|csv [oscilloscope_IP [save_path]]"
  64. print
  65. print "Usage examples:"
  66. print " " + "python " + script_name + " png"
  67. print " " + "python " + script_name + " csv 192.168.1.3"
  68. print
  69. print "The following usage cases are not yet implemented:"
  70. print " " + "python " + script_name + " bmp 192.168.1.3 my_place_for_captures"
  71. print
  72. print "This program captures either the waveform or the whole screen"
  73. print " of a Rigol DS1000Z series oscilloscope, then save it on the computer"
  74. print " as a CSV, PNG or BMP file with a timestamp in the file name."
  75. print
  76. print " The program is using LXI protocol, so the computer"
  77. print " must have LAN connection with the oscilloscope."
  78. print " USB and/or GPIB connections are not used by this software."
  79. print
  80. print " No VISA, IVI or Rigol drivers are needed."
  81. print
  82. # Read/verify file type
  83. if len(sys.argv) <= 1:
  84. print_help()
  85. sys.exit("Warning - wrong command line parameters.")
  86. elif sys.argv[1].lower() not in ["png", "bmp", "csv"]:
  87. print_help()
  88. print "This file type is not supported: ", sys.argv[1]
  89. print
  90. print_running_Python_versions()
  91. sys.exit("ERROR")
  92. file_format = sys.argv[1].lower()
  93. # Read IP
  94. if len(sys.argv) > 1:
  95. IP_DS1104Z = sys.argv[2]
  96. # Create/check if 'path' exists
  97. # Check network response (ping)
  98. if platform.system() == "Windows":
  99. response = os.system("ping -n 1 " + IP_DS1104Z + " > nul")
  100. else:
  101. response = os.system("ping -c 1 " + IP_DS1104Z + " > /dev/null")
  102. if response != 0:
  103. print
  104. print "No response pinging " + IP_DS1104Z
  105. print "Check network cables and settings."
  106. print "You should be able to ping the oscilloscope."
  107. # Open a modified telnet session
  108. # The default telnetlib drops 0x00 characters,
  109. # so a modified library 'telnetlib_receive_all' is used instead
  110. tn = Telnet(IP_DS1104Z, port)
  111. instrument_id = command(tn, "*idn?") # ask for instrument ID
  112. # Check if instrument is set to accept LAN commands
  113. if instrument_id == "command error":
  114. print instrument_id
  115. print "Check the oscilloscope settings."
  116. print "Utility -> IO Setting -> RemoteIO -> LAN must be ON"
  117. print
  118. print_running_Python_versions()
  119. sys.exit("ERROR")
  120. # Check if instrument is indeed a Rigol DS1000Z series
  121. id_fields = instrument_id.split(",")
  122. if (id_fields[company] != "RIGOL TECHNOLOGIES") or \
  123. (id_fields[model][:3] != "DS1") or (id_fields[model][-1] != "Z"):
  124. print
  125. print "ERROR: No Rigol from series DS1000Z found at ", IP_DS1104Z
  126. print
  127. print_running_Python_versions()
  128. sys.exit("ERROR")
  129. print "Instrument ID:",
  130. print instrument_id
  131. # Prepare filename as C:\MODEL_SERIAL_YYYY-MM-DD_HH.MM.SS
  132. timestamp = time.strftime("%Y-%m-%d_%H.%M.%S", time.localtime())
  133. filename = path_to_save + id_fields[model] + "_" + id_fields[serial] + "_" + timestamp
  134. if file_format in ["png", "bmp"]:
  135. # Ask for an oscilloscope display print screen
  136. print "Receiving screen capture..."
  137. buff = command(tn, "display:data?")
  138. # Just in case the transfer did not complete in the expected time
  139. while len(buff) < expected_len:
  140. logging.warning("Received LESS data then expected! (" +
  141. str(len(buff)) + " out of " + str(expected_len) + " expected raw BMP bytes.)")
  142. tmp = tn.read_until("\n", small_wait)
  143. if len(tmp) == 0:
  144. break
  145. buff += tmp
  146. logging.warning(str(len(tmp)) + " leftover bytes added to 'buff'.")
  147. if len(buff) < expected_len:
  148. logging.error("Received LESS data then expected! (" +
  149. str(len(buff)) + " out of " + str(expected_len) + " expected raw BMP bytes.)")
  150. sys.exit("ERROR")
  151. # Strip TMC Blockheader and terminator bytes
  152. buff = buff[TMC_header_len:-terminator_len]
  153. # Save as PNG or BMP according to file_format
  154. im = Image.open(StringIO.StringIO(buff))
  155. im.save(filename + "." + file_format, file_format)
  156. print "Saved file:", filename + "." + file_format
  157. elif file_format == "csv":
  158. # Put osc in STOP mode
  159. # tn.write("stop")
  160. # response = tn.read_until("\n", 1)
  161. # Scan for displayed channels
  162. channel_list = []
  163. for channel in ["chan1", "chan2", "chan3", "chan4", "math"]:
  164. response = command(tn, channel + ":display?")
  165. # Strip '\n' terminator
  166. response = response[:-1]
  167. if response == '1':
  168. channel_list += [channel]
  169. print "Active channels on the display:", channel_list
  170. csv_buff = ""
  171. depth = get_memory_depth(tn)
  172. # for each active channel
  173. for channel in channel_list:
  174. print
  175. # Set WAVE parameters
  176. command(tn, "waveform:source " + channel)
  177. command(tn, "waveform:form asc")
  178. # Maximum = only displayed data when osc. in RUN mode, or full memory data when STOPed
  179. # Always ONLY displayed data (1200 points) if MATH channel is selected
  180. command(tn, "waveform:mode max")
  181. # Get all possible data
  182. buff = ""
  183. data_available = True
  184. # max_chunk is dependent of the wav:mode and the oscilloscope type
  185. # if you get on the oscilloscope screen the error message
  186. # "Memory lack in waveform reading!", then decrease max_chunk value
  187. max_chunk = 100000 # tested for DS1104Z
  188. print "max_chunk=", max_chunk
  189. print "depth=", depth
  190. print "max_chunk > depth:", max_chunk > depth
  191. if max_chunk > depth:
  192. max_chunk = depth
  193. print "max_chunk=", max_chunk
  194. n1 = 1
  195. n2 = max_chunk
  196. print "n2=", n2
  197. while data_available:
  198. display_n1 = n1
  199. print
  200. print "n1=", n1
  201. print "n2=", n2
  202. stop_point = is_waveform_from_to(tn, n1, n2)
  203. print "stop_point=", stop_point
  204. if stop_point == 0:
  205. print_running_Python_versions()
  206. logging.error("ERROR: Stop data point index is Zero while available data is True.")
  207. sys.exit("ERROR")
  208. elif stop_point < n1:
  209. break
  210. elif stop_point < n2:
  211. n2 = stop_point
  212. is_waveform_from_to(tn, n1, n2)
  213. data_available = False
  214. else:
  215. data_available = True
  216. n1 = n2 + 1
  217. n2 += max_chunk
  218. print "Data from channel '" + str(channel) + "', points " +\
  219. str(display_n1) + "-" + str(stop_point) + ": Receiving..."
  220. buff_chunks = command(tn, "waveform:data?")
  221. # Just in case the transfer did not complete in the expected time
  222. while buff_chunks[-1] != "\n":
  223. logging.warning("The data transfer did not complete in the expected time of " +
  224. str(small_wait) + " second(s).")
  225. tmp = tn.read_until("\n", small_wait)
  226. if len(tmp) == 0:
  227. break
  228. buff_chunks += tmp
  229. logging.warning(str(len(tmp)) + " leftover bytes added to 'buff_chunks'.")
  230. # Append data chunks
  231. # Strip TMC Blockheader and terminator bytes
  232. buff += buff_chunks[TMC_header_len:-1] + ","
  233. buff = buff[:-1]
  234. # Append each value to csv_buff
  235. # Process data
  236. buff_list = buff.split(",")
  237. buff_rows = len(buff_list)
  238. # Put red data into csv_buff
  239. csv_buff_list = csv_buff.split(os.linesep)
  240. csv_rows = len(csv_buff_list)
  241. current_row = 0
  242. if csv_buff == "":
  243. csv_first_column = True
  244. csv_buff = str(channel) + os.linesep
  245. else:
  246. csv_first_column = False
  247. csv_buff = str(csv_buff_list[current_row]) + "," + str(channel) + os.linesep
  248. for point in buff_list:
  249. current_row += 1
  250. if csv_first_column:
  251. csv_buff += str(point) + os.linesep
  252. else:
  253. if current_row < csv_rows:
  254. csv_buff += str(csv_buff_list[current_row]) + "," + str(point) + os.linesep
  255. else:
  256. csv_buff += "," + str(point) + os.linesep
  257. # Save data as CSV
  258. scr_file = open(filename + "." + file_format, "wb")
  259. scr_file.write(csv_buff)
  260. scr_file.close()
  261. print "Saved file: '", filename + "." + file_format + "'"
  262. tn.close()