Capture the display of a Rigol DS1000Z series oscilloscope by LAN using LXI SCPI commands
Vous ne pouvez pas sélectionner plus de 25 sujets Les noms de sujets doivent commencer par une lettre ou un nombre, peuvent contenir des tirets ('-') et peuvent comporter jusqu'à 35 caractères.

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()