Capture the display of a Rigol DS1000Z series oscilloscope by LAN using LXI SCPI commands
Você não pode selecionar mais de 25 tópicos Os tópicos devem começar com uma letra ou um número, podem incluir traços ('-') e podem ter até 35 caracteres.

OscScreenGrabLAN.py 10.0KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300
  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("New run started...")
  43. logging.info("Log message: INFO level set.")
  44. log_running_Python_versions()
  45. # Update the next lines for your own default settings:
  46. path_to_save = "captures/"
  47. save_format = "PNG"
  48. IP_DS1104Z = "192.168.1.3"
  49. # Rigol/LXI specific constants
  50. port = 5555
  51. expected_len = 1152068
  52. TMC_header_len = 11
  53. terminator_len = 3
  54. big_wait = 10
  55. small_wait = 1
  56. company = 0
  57. model = 1
  58. serial = 2
  59. # Check command line parameters
  60. script_name = os.path.basename(sys.argv[0])
  61. def print_help():
  62. print
  63. print "Usage:"
  64. print " " + "python " + script_name + " png|bmp|csv [oscilloscope_IP [save_path]]"
  65. print
  66. print "Usage examples:"
  67. print " " + "python " + script_name + " png"
  68. print " " + "python " + script_name + " csv 192.168.1.3"
  69. print
  70. print "The following usage cases are not yet implemented:"
  71. print " " + "python " + script_name + " bmp 192.168.1.3 my_place_for_captures"
  72. print
  73. print "This program captures either the waveform or the whole screen"
  74. print " of a Rigol DS1000Z series oscilloscope, then save it on the computer"
  75. print " as a CSV, PNG or BMP file with a timestamp in the file name."
  76. print
  77. print " The program is using LXI protocol, so the computer"
  78. print " must have LAN connection with the oscilloscope."
  79. print " USB and/or GPIB connections are not used by this software."
  80. print
  81. print " No VISA, IVI or Rigol drivers are needed."
  82. print
  83. # Read/verify file type
  84. if len(sys.argv) <= 1:
  85. print_help()
  86. sys.exit("Warning - wrong command line parameters.")
  87. elif sys.argv[1].lower() not in ["png", "bmp", "csv"]:
  88. print_help()
  89. print "This file type is not supported: ", sys.argv[1]
  90. sys.exit("ERROR")
  91. file_format = sys.argv[1].lower()
  92. # Read IP
  93. if len(sys.argv) > 1:
  94. IP_DS1104Z = sys.argv[2]
  95. # Create/check if 'path' exists
  96. # Check network response (ping)
  97. if platform.system() == "Windows":
  98. response = os.system("ping -n 1 " + IP_DS1104Z + " > nul")
  99. else:
  100. response = os.system("ping -c 1 " + IP_DS1104Z + " > /dev/null")
  101. if response != 0:
  102. print
  103. print "No response pinging " + IP_DS1104Z
  104. print "Check network cables and settings."
  105. print "You should be able to ping the oscilloscope."
  106. # Open a modified telnet session
  107. # The default telnetlib drops 0x00 characters,
  108. # so a modified library 'telnetlib_receive_all' is used instead
  109. tn = Telnet(IP_DS1104Z, port)
  110. instrument_id = command(tn, "*idn?") # ask for instrument ID
  111. # Check if instrument is set to accept LAN commands
  112. if instrument_id == "command error":
  113. print "Instrument reply:", instrument_id
  114. print "Check the oscilloscope settings."
  115. print "Utility -> IO Setting -> RemoteIO -> LAN must be ON"
  116. sys.exit("ERROR")
  117. # Check if instrument is indeed a Rigol DS1000Z series
  118. id_fields = instrument_id.split(",")
  119. if (id_fields[company] != "RIGOL TECHNOLOGIES") or \
  120. (id_fields[model][:3] != "DS1") or (id_fields[model][-1] != "Z"):
  121. print "Found instrument model", id_fields[model], "instead of expected model, DS1*Z"
  122. print "ERROR: No Rigol from series DS1000Z found at ", IP_DS1104Z
  123. sys.exit("ERROR")
  124. print "Instrument ID:",
  125. print instrument_id
  126. # Prepare filename as C:\MODEL_SERIAL_YYYY-MM-DD_HH.MM.SS
  127. timestamp = time.strftime("%Y-%m-%d_%H.%M.%S", time.localtime())
  128. filename = path_to_save + id_fields[model] + "_" + id_fields[serial] + "_" + timestamp
  129. if file_format in ["png", "bmp"]:
  130. # Ask for an oscilloscope display print screen
  131. print "Receiving screen capture..."
  132. buff = command(tn, "display:data?")
  133. # Just in case the transfer did not complete in the expected time
  134. while len(buff) < expected_len:
  135. logging.warning("Received LESS data then expected! (" +
  136. str(len(buff)) + " out of " + str(expected_len) + " expected raw BMP bytes.)")
  137. tmp = tn.read_until("\n", small_wait)
  138. if len(tmp) == 0:
  139. break
  140. buff += tmp
  141. logging.warning(str(len(tmp)) + " leftover bytes added to 'buff'.")
  142. if len(buff) < expected_len:
  143. logging.error("Received LESS data then expected! (" +
  144. str(len(buff)) + " out of " + str(expected_len) + " expected raw BMP bytes.)")
  145. sys.exit("ERROR")
  146. # Strip TMC Blockheader and terminator bytes
  147. buff = buff[TMC_header_len:-terminator_len]
  148. # Save as PNG or BMP according to file_format
  149. im = Image.open(StringIO.StringIO(buff))
  150. im.save(filename + "." + file_format, file_format)
  151. print "Saved file:", filename + "." + file_format
  152. elif file_format == "csv":
  153. # Put osc in STOP mode
  154. # tn.write("stop")
  155. # response = tn.read_until("\n", 1)
  156. # Scan for displayed channels
  157. channel_list = []
  158. for channel in ["chan1", "chan2", "chan3", "chan4", "math"]:
  159. response = command(tn, channel + ":display?")
  160. # Strip '\n' terminator
  161. response = response[:-1]
  162. if response == '1':
  163. channel_list += [channel]
  164. csv_buff = ""
  165. depth = get_memory_depth(tn)
  166. # for each active channel
  167. for channel in channel_list:
  168. print
  169. # Set WAVE parameters
  170. command(tn, "waveform:source " + channel)
  171. command(tn, "waveform:form asc")
  172. # Maximum = only displayed data when osc. in RUN mode, or full memory data when STOPed
  173. # Always ONLY displayed data (1200 points) if MATH channel is selected
  174. command(tn, "waveform:mode max")
  175. # Get all possible data
  176. buff = ""
  177. data_available = True
  178. # max_chunk is dependent of the wav:mode and the oscilloscope type
  179. # if you get on the oscilloscope screen the error message
  180. # "Memory lack in waveform reading!", then decrease max_chunk value
  181. max_chunk = 100000 # tested for DS1104Z
  182. if max_chunk > depth:
  183. max_chunk = depth
  184. n1 = 1
  185. n2 = max_chunk
  186. while data_available:
  187. display_n1 = n1
  188. stop_point = is_waveform_from_to(tn, n1, n2)
  189. if stop_point == 0:
  190. logging.error("ERROR: Stop data point index is Zero while available data is True.")
  191. sys.exit("ERROR")
  192. elif stop_point < n1:
  193. break
  194. elif stop_point < n2:
  195. n2 = stop_point
  196. is_waveform_from_to(tn, n1, n2)
  197. data_available = False
  198. else:
  199. data_available = True
  200. n1 = n2 + 1
  201. n2 += max_chunk
  202. print "Data from channel '" + str(channel) + "', points " +\
  203. str(display_n1) + "-" + str(stop_point) + ": Receiving..."
  204. buff_chunks = command(tn, "waveform:data?")
  205. # Just in case the transfer did not complete in the expected time
  206. while buff_chunks[-1] != "\n":
  207. logging.warning("The data transfer did not complete in the expected time of " +
  208. str(small_wait) + " second(s).")
  209. tmp = tn.read_until("\n", small_wait)
  210. if len(tmp) == 0:
  211. break
  212. buff_chunks += tmp
  213. logging.warning(str(len(tmp)) + " leftover bytes added to 'buff_chunks'.")
  214. # Append data chunks
  215. # Strip TMC Blockheader and terminator bytes
  216. buff += buff_chunks[TMC_header_len:-1] + ","
  217. buff = buff[:-1]
  218. # Append each value to csv_buff
  219. # Process data
  220. buff_list = buff.split(",")
  221. buff_rows = len(buff_list)
  222. # Put red data into csv_buff
  223. csv_buff_list = csv_buff.split(os.linesep)
  224. csv_rows = len(csv_buff_list)
  225. current_row = 0
  226. if csv_buff == "":
  227. csv_first_column = True
  228. csv_buff = str(channel) + os.linesep
  229. else:
  230. csv_first_column = False
  231. csv_buff = str(csv_buff_list[current_row]) + "," + str(channel) + os.linesep
  232. for point in buff_list:
  233. current_row += 1
  234. if csv_first_column:
  235. csv_buff += str(point) + os.linesep
  236. else:
  237. if current_row < csv_rows:
  238. csv_buff += str(csv_buff_list[current_row]) + "," + str(point) + os.linesep
  239. else:
  240. csv_buff += "," + str(point) + os.linesep
  241. # Save data as CSV
  242. scr_file = open(filename + "." + file_format, "wb")
  243. scr_file.write(csv_buff)
  244. scr_file.close()
  245. print "Saved file: '", filename + "." + file_format + "'"
  246. tn.close()