opc.py
上传用户:liufeng210
上传日期:2016-03-28
资源大小:164k
文件大小:21k
源码类别:

PlugIns编程

开发平台:

Python

  1. ###########################################################################
  2. #
  3. # OpenOPC Command Line Client
  4. #
  5. # A cross-platform OPC-DA client built using the OpenOPC for Python
  6. # library module.
  7. #
  8. # Copyright (c) 2007-2008 Barry Barnreiter (barry_b@users.sourceforge.net)
  9. #
  10. ###########################################################################
  11. from sys import *
  12. from getopt import *
  13. from os import *
  14. import signal
  15. import sys
  16. import os
  17. import types
  18. import datetime
  19. import re, time, csv
  20. import OpenOPC
  21. try:
  22.    import Pyro
  23. except ImportError:
  24.    pyro_found = False
  25. else:
  26.    pyro_found = True
  27. # Common function aliases
  28. write = sys.stdout.write
  29. # Initialize default settings
  30. if os.name == 'nt':
  31.    opc_mode = 'dcom'
  32. else:
  33.    opc_mode = 'open'
  34. opc_class = OpenOPC.OPC_CLASS
  35. client_name = OpenOPC.OPC_CLIENT
  36. opc_host = 'localhost'
  37. opc_server = OpenOPC.OPC_SERVER
  38. open_host = 'localhost'
  39. open_port = 7766
  40. action = 'read'
  41. style = 'table'
  42. append = ''
  43. num_columns = 0
  44. pipe = False
  45. verbose = False
  46. recursive = False
  47. read_function = 'async'
  48. data_source = 'hybrid'
  49. group_size = None
  50. update_rate = None
  51. timeout = 5000
  52. tx_pause = 0
  53. repeat = 1
  54. repeat_pause = None
  55. property_ids = None
  56. include_err_msg = False
  57. if environ.has_key('OPC_MODE'):         opc_mode = environ['OPC_MODE']
  58. if environ.has_key('OPC_CLASS'):        opc_class = environ['OPC_CLASS']
  59. if environ.has_key('OPC_CLIENT'):       client_name = environ['OPC_CLIENT']
  60. if environ.has_key('OPC_HOST'):         opc_host = environ['OPC_HOST']
  61. if environ.has_key('OPC_SERVER'):       opc_server = environ['OPC_SERVER']
  62. if environ.has_key('OPC_GATE_HOST'):    open_host = environ['OPC_GATE_HOST']
  63. if environ.has_key('OPC_GATE_PORT'):    open_port = environ['OPC_GATE_PORT']
  64. if environ.has_key('OPC_TIMEOUT'):      timeout = int(environ['OPC_TIMEOUT'])
  65. # FUNCTION: Print comand line usage summary
  66. def usage():
  67.    print 'OpenOPC Command Line Client', OpenOPC.__version__
  68.    print 'Copyright (c) 2007-2008 Barry Barnreiter (barry_b@users.sourceforge.net)'
  69.    print ''
  70.    print 'Usage:  opc [OPTIONS] [ACTION] [ITEM|PATH...]'
  71.    print ''
  72.    print 'Actions:'
  73.    print '  -r, --read                 Read ITEM values (default action)'
  74.    print '  -w, --write                Write values to ITEMs (use ITEM=VALUE)'
  75.    print '  -p, --properties           View properties of ITEMs'
  76.    print '  -l, --list                 List items at specified PATHs (tree browser)'
  77.    print '  -f, --flat                 List all ITEM names (flat browser)'
  78.    print '  -i, --info                 Display OPC server information'
  79.    print '  -q, --servers              Query list of available OPC servers'
  80.    print '  -S, --sessions             List sessions in OpenOPC Gateway Service'
  81.    print ''
  82.    print 'Options:'
  83.    print '  -m MODE, --mode=MODE       Protocol MODE (dcom, open) (default: OPC_MODE)'
  84.    print '  -C CLASS,--class=CLASS     OPC Automation CLASS (default: OPC_CLASS)'
  85.    print '  -n NAME, --name=NAME       Set OPC Client NAME (default: OPC_CLIENT)'
  86.    print '  -h HOST, --host=HOST       DCOM OPC HOST (default: OPC_HOST)'
  87.    print '  -s SERV, --server=SERVER   DCOM OPC SERVER (default: OPC_SERVER)'
  88.    print '  -H HOST, --gate-host=HOST  OpenOPC Gateway HOST (default: OPC_GATE_HOST)'
  89.    print '  -P PORT, --gate-port=PORT  OpenOPC Gateway PORT (default: OPC_GATE_PORT)'
  90.    print ''
  91.    print '  -F FUNC, --function=FUNC   Read FUNCTION to use (sync, async)'
  92.    print '  -c SRC,  --source=SOURCE   Set data SOURCE for reads (cache, device, hybrid)'
  93.    print '  -g SIZE, --size=SIZE       Group tags into SIZE items per transaction'
  94.    print '  -z MSEC, --pause=MSEC      Sleep MSEC milliseconds between transactions'
  95.    print '  -u MSEC, --update=MSEC     Set update rate for group to MSEC milliseconds'
  96.    print '  -t MSEC, --timeout=MSEC    Set read timeout to MSEC mulliseconds'
  97.    print ''
  98.    print '  -o FMT,  --output=FORMAT   Output FORMAT (table, values, pairs, csv, html)'
  99.    print '  -L SEC,  --repeat=SEC      Loop ACTION every SEC seconds until stopped'
  100.    print '  -y ID,   --id=ID,...       Retrieve only specific Property IDs'
  101.    print '  -a STR,  --append=STR,...  Append STRINGS to each input item name'
  102.    print '  -x N     --rotate=N        Rotate output orientation in groups of N values'
  103.    print '  -v,      --verbose         Verbose mode showing all OPC function calls'
  104.    print '  -e,      --errors          Include descriptive error message strings'
  105.    print '  -R,      --recursive       List items recursively when browsing tree'
  106.    print '  -,       --pipe            Pipe item/value list from standard input'
  107. # Helper class for handling signals (i.e. Ctrl-C)
  108. class SigHandler:
  109.   def __init__(self):
  110.       self.signaled = 0
  111.       self.sn = None
  112.   def __call__(self, sn, sf):
  113.       self.sn = sn 
  114.       self.signaled += 1
  115. # FUNCTION: Iterable version of rotate()
  116. def irotate(data, num_columns, value_idx = 1):
  117.    if num_columns == 0:
  118.       for row in data: yield row
  119.       return
  120.    
  121.    new_row = []
  122.    for i, row in enumerate(data):
  123.       if type(row) not in (types.ListType, types.TupleType):
  124.          value_idx = 0
  125.          row = [row]
  126.       new_row.append(row[value_idx])
  127.       if (i + 1) % num_columns == 0:
  128.          yield new_row
  129.          new_row = []
  130.    if len(new_row) > 0:
  131.       yield new_row
  132. # FUNCTION: Rotate the values of every N rows to form N columns
  133. def rotate(data, num_columns, value_idx = 1):
  134.    return list(irotate(data, num_columns, value_idx))
  135. # FUNCTION: Print output in the specified style from a list of data
  136.    
  137. def output(data, style = 'table', value_idx = 1):
  138.    global write
  139.    name_idx = 0
  140.    # Cast value to a stirng (trap Unicode errors)
  141.    def to_str(value):
  142.       try:
  143.          if type(value) == types.FloatType:
  144.             return '%.4f' % value
  145.          else:
  146.             return str(value)
  147.       except:
  148.          return ''
  149.    # Generator passed (single row passed at a time)
  150.    if type(data) == types.GeneratorType:
  151.       generator = True
  152.       pad_length = []
  153.    # List passed (multiple rows passed all at once)
  154.    elif type(data) in (types.ListType, types.TupleType):
  155.       generator = False
  156.       
  157.       if len(data) == 0: return
  158.       if type(data[0]) not in (types.ListType, types.TupleType):
  159.          data = [[e] for e in data]
  160.          
  161.       if style == 'table' or style == '':
  162.          pad_length = []
  163.          num_columns = len(data[0])
  164.          for i in range(num_columns-1):
  165.             pad_length.append(len(max([to_str(row[i]) for row in data], key=len)) + 5)
  166.          pad_length.append(0)
  167.    else:
  168.       raise TypeError, "output(): 'data' parameter must be a list or a generator"
  169.    if style == 'html':
  170.       write('<table border=1>n')
  171.    rows = []
  172.    for i, row in enumerate(data):
  173.       rows.append(row)
  174.       if style == 'values':
  175.          write('%s' % str(row[value_idx]))
  176.       elif style == 'pairs':
  177.          write('%s,%s' % (row[name_idx], row[value_idx]))
  178.       else:
  179.          if generator and (style == 'table' or style == ''):
  180.             # Convert single value into a single element list, thus making it
  181.             # represent a 1-column wide table.
  182.             if type(row) not in (types.ListType, types.TupleType):
  183.                row = [row]
  184.             num_columns = len(row)
  185.             # Allow columns widths to always grow wider, but never shrink.
  186.             # Unfortunetly we won't know the required width until the generator is finished!
  187.             for k in range(num_columns-1):
  188.                new_length = len(to_str(row[k]))
  189.                if i == 0:
  190.                   pad_length.append(new_length + 5)
  191.                else:
  192.                   if new_length - pad_length[k] > 0:  pad_length[k] = new_length
  193.             if i == 0:
  194.                pad_length.append(0)
  195.          for j, item in enumerate(row):               
  196.             if style == 'csv':
  197.                if j > 0: write(',')
  198.                write('%s' % to_str(item))
  199.             elif style == 'html':
  200.                if j == 0: write('  <tr>n')
  201.                if len(to_str(item)) < 40:
  202.                   write('    <td nowrap>%s</td>n' % to_str(item))
  203.                else:
  204.                   write('    <td>%s</td>n' % to_str(item))
  205.                if j == len(row)-1: write('  </tr>')
  206.             else:
  207.                if num_columns > 1:
  208.                   write('%s' % to_str(item).ljust(pad_length[j]))
  209.                else:
  210.                   write('%s' % to_str(item))
  211.       write('n')
  212.    if style == 'html':
  213.       write('</table>')
  214.    return rows
  215. # FUNCTION: Convert Unix time to formatted time string
  216. def time2str(t):
  217.    d = datetime.datetime.fromtimestamp(t)
  218.    return d.strftime('%x %H:%M:%S')
  219. ######## MAIN ######## 
  220. # Parse command line arguments
  221. if argv.count('-') > 0:
  222.    argv[argv.index('-')] = '--pipe'
  223.    pipe = True
  224. try:
  225.    opts, args = gnu_getopt(argv[1:], 'rwlpfiqRSevx:m:C:H:P:c:h:s:L:F:z:o:a:u:t:g:y:n:', ['read','write','list','properties','flat','info','mode=','gate-host=','gate-port=','class=','host=','server=','output=','pause=','pipe','servers','sessions','repeat=','function=','append=','update=','timeout=','size=','source=','id=','verbose','recursive','rotate=','errors','name='])
  226. except GetoptError:
  227.    usage()
  228.    exit()   
  229. for o, a in opts:
  230.    if o in ['-m', '--mode']       : opc_mode = a
  231.    if o in ['-C', '--class']      : opc_class = a
  232.    if o in ['-n', '--name']       : client_name = a
  233.    if o in ['-H', '--open-host']  : open_host = a;  opc_mode = 'open' 
  234.    if o in ['-P', '--open-port']  : open_port = a;  opc_mode = 'open'
  235.    if o in ['-h', '--host']       : opc_host = a
  236.    if o in ['-s', '--server']     : opc_server = a
  237.    
  238.    if o in ['-r', '--read']       : action = 'read'
  239.    if o in ['-w', '--write']      : action = 'write'
  240.    if o in ['-l', '--list']       : action = 'list'
  241.    if o in ['-f', '--flat']       : action = 'flat'
  242.    if o in ['-p', '--properties'] : action = 'properties'
  243.    if o in ['-i', '--info']       : action = 'info'
  244.    if o in ['-q', '--servers']    : action = 'servers'
  245.    if o in ['-S', '--sessions']   : action = 'sessions'
  246.    if o in ['-o', '--output']     : style = a
  247.    if o in ['-L', '--repeat']     : repeat_pause = float(a);
  248.    if o in ['-F', '--function']   : read_function = a;
  249.    if o in ['-z', '--pause']      : tx_pause = int(a)
  250.    if o in ['-u', '--update']     : update_rate = int(a)
  251.    if o in ['-t', '--timeout']    : timeout = int(a)
  252.    if o in ['-g', '--size']       : group_size = int(a)
  253.    if o in ['-c', '--source']     : data_source = a
  254.    if o in ['-y', '--id']         : property_ids = a
  255.    if o in ['-a', '--append']     : append = a
  256.    if o in ['-x', '--rotate']     : num_columns = int(a)
  257.    if o in ['-v', '--verbose']    : verbose = True
  258.    if o in ['-e', '--errors']     : include_err_msg = True
  259.    if o in ['-R', '--recursive']  : recursive = True
  260.    if o in ['--pipe']             : pipe = True
  261. # Check validity of command line options
  262. if num_columns > 0 and style in ('values', 'pairs'):
  263.    print "'%s' style format may not be used with rotate" % style
  264.    exit()
  265.    
  266. if opc_mode not in ('open', 'dcom'):
  267.    print "'%s' is not a valid protocol mode (options: dcom, open)" % opc_mode
  268.    exit()
  269. if opc_mode == 'dcom' and not OpenOPC.win32com_found:
  270.    print "win32com modules required when using DCOM protocol mode (http://pywin32.sourceforge.net/)"
  271.    exit()
  272. if opc_mode == 'open' and not pyro_found:
  273.    print "Pyro module required when using Open protocol mode (http://pyro.sourceforge.net)"
  274.    exit()
  275. if style not in ('table', 'values', 'pairs', 'csv', 'html'):
  276.    print "'%s' is not a valid style format (options: table, values, pairs, csv, html)" % style
  277.    exit()
  278. if read_function not in ('sync', 'async'):
  279.    print "'%s' is not a valid read function (options: sync, async)" % read_function
  280.    exit()
  281. else:
  282.    sync = (read_function == 'sync')
  283.    
  284. if data_source not in ('cache', 'device', 'hybrid'):
  285.    print "'%s' is not a valid data source mode (options: cache, device, hybrid)" % data_source
  286.    exit()
  287. if len(argv[1:]) == 0 or argv[1] == '/?' or argv[1] == '--help':
  288.    usage()
  289.    exit()
  290. if opc_server == '' and action not in ('servers', 'sessions'):
  291.    print 'OPC server name missing: use -s option or set OPC_SERVER environment variable'
  292.    exit()
  293. if data_source in ('cache', 'hybrid') and read_function == 'async' and update_rate == None and repeat_pause != None:
  294.    update_rate = int(repeat_pause * 1000.0)
  295. elif update_rate == None:
  296.    update_rate = -1
  297. # Build tag list
  298. tags = []
  299. # Tag list passed via standrd input
  300. if pipe:
  301.    try:
  302.       reader = csv.reader(sys.stdin)
  303.       tags_nested = list(reader)
  304.    except KeyboardInterrupt:
  305.       exit()
  306.       
  307.    try:
  308.       tags = [line[0] for line in tags_nested]
  309.    except IndexError:
  310.       print 'Input stream must be formatted as ITEM or ITEM,VALUE lines'
  311.       exit()
  312.       
  313.    if len(tags) == 0: exit()
  314.    if action == 'write':
  315.       try:
  316.          tag_value_pairs = [(item[0], item[1]) for item in tags_nested]
  317.       except IndexError:
  318.          print 'Write input must be in ITEM,VALUE (CSV) format'
  319.          exit()
  320. # Tag list passed via command line arguments
  321. else:
  322.    for a in args:
  323.       tags.append(a.replace('+', ' '))
  324.    tags_nested = [[tag] for tag in tags]
  325.    if action == 'write':
  326.       if len(tags) % 2 == 0:
  327.          tag_value_pairs = [(tags[i], tags[i+1]) for i in range(0, len(tags), 2)]
  328.       else:
  329.          print 'Write arguments must be supplied in ITEM=VALUE or ITEM VALUE format'
  330.          exit()
  331. if len(append) > 0:
  332.    tags = [t + a  for t in tags for a in append.split(',')]
  333. if property_ids != None:
  334.    try:
  335.       property_ids = [int(p) for p in property_ids.split(',')]
  336.    except ValueError:
  337.       print 'Property ids must be numeric'
  338.       exit()
  339.    
  340. if action in ('read','write') and not pipe and len(tags) == 0:
  341.    usage()
  342.    exit()
  343. # Were only health monitoring "@" tags supplied?
  344. health_tags = [t for t in tags if t[:1] == '@']
  345. opc_tags = [t for t in tags if t[:1] != '@']
  346. if len(health_tags) > 0 and len(opc_tags) == 0:
  347.     health_only = True
  348. else:
  349.     health_only = False
  350. # Establish signal handler for keyboard interrupts
  351. sh = SigHandler()
  352. signal.signal(signal.SIGINT,sh)
  353. signal.signal(signal.SIGBREAK,sh)
  354. signal.signal(signal.SIGTERM,sh)
  355. # ACTION: List active sessions in OpenOPC service
  356. if action == 'sessions':
  357.    print '  %-38s %-18s %-18s' % ('Remote Client', 'Start Time', 'Last Transaction')
  358.    try:
  359.       for guid, host, init_time, tx_time in OpenOPC.get_sessions(open_host, open_port):
  360.          print '  %-38s %-18s %-18s'  % (host, time2str(init_time), time2str(tx_time))
  361.    except:
  362.       error_msg = sys.exc_info()[1]
  363.       print "Cannot connect to OpenOPC service at %s:%s - %s" % (open_host, open_port, error_msg)
  364.    exit()
  365.    
  366. # Connect to OpenOPC service (Open mode)
  367. if opc_mode == 'open':
  368.    try:
  369.       opc = OpenOPC.open_client(open_host, open_port)
  370.    except:
  371.       error_msg = sys.exc_info()[1]
  372.       print "Cannot connect to OpenOPC Gateway Service at %s:%s - %s" % (open_host, open_port, error_msg)
  373.       exit()
  374. # Dispatch to COM class (DCOM mode)
  375. else:
  376.    try:
  377.       opc = OpenOPC.client(opc_class, client_name)
  378.    except OpenOPC.OPCError, error_msg:
  379.       print "Failed to initialize an OPC Automation Class from the search list '%s' - %s" % (opc_class, error_msg)
  380.       exit()
  381. # Connect to OPC server
  382. if action not in ['servers'] and not health_only:
  383.    try:
  384.       opc.connect(opc_server, opc_host)
  385.    except OpenOPC.OPCError, error_msg:
  386.       if opc_mode == 'open': error_msg = error_msg[0]
  387.       print "Connect to OPC server '%s' on '%s' failed - %s" % (opc_server, opc_host, error_msg)
  388.       exit()
  389. # Perform requested action...
  390. start_time = time.time()
  391. # ACTION: Read Items
  392. if action == 'read':
  393.    if group_size and len(tags) > group_size and opc_mode == 'dcom':
  394.       opc_read = opc.iread
  395.       rotate = irotate
  396.    else:
  397.       opc_read = opc.read
  398.             
  399.    if verbose:
  400.       def trace(msg): print msg
  401.       opc.set_trace(trace)
  402.    success_count = 0
  403.    total_count = 0
  404.    com_connected = True
  405.    pyro_connected = True
  406.    while not sh.signaled:
  407.       try:
  408.          if not pyro_connected:
  409.             opc = OpenOPC.open_client(open_host, open_port)
  410.             opc.connect(opc_server, opc_host)
  411.             opc_read = opc.read
  412.             pyro_connected = True
  413.             com_connected = True
  414.          if not com_connected:
  415.             opc.connect(opc_server, opc_host)
  416.             com_connected = True
  417.          status = output(rotate(opc_read(tags,
  418.                                   group='test',
  419.                                   size=group_size,
  420.                                   pause=tx_pause,
  421.                                   source=data_source,
  422.                                   update=update_rate,
  423.                                   timeout=timeout,
  424.                                   sync=sync,
  425.                                   include_error=include_err_msg),
  426.                         num_columns), style)
  427.                         
  428.       except OpenOPC.TimeoutError, error_msg:
  429.          if opc_mode == 'open': error_msg = error_msg[0]
  430.          print error_msg
  431.          success = False
  432.       except OpenOPC.OPCError, error_msg:
  433.          if opc_mode == 'open': error_msg = error_msg[0]
  434.          print error_msg
  435.          success = False
  436.  
  437.          if opc.ping():
  438.             com_connected = True
  439.          else:
  440.             com_connected = False
  441.             
  442.       except (Pyro.errors.ConnectionClosedError, Pyro.errors.ProtocolError), error_msg:
  443.           print 'Gateway Service: %s' % error_msg
  444.           success = False
  445.           pyro_connected = False
  446.       except TypeError, error_msg:
  447.           if opc_mode == 'open': error_msg = error_msg[0]
  448.           print error_msg
  449.           break
  450.             
  451.       else:
  452.          success = True
  453.       if success and num_columns == 0:
  454.          success_count += len([s for s in status if s[2] != 'Error'])
  455.          total_count += len(status)
  456.       if repeat_pause != None:
  457.          try:
  458.             time.sleep(repeat_pause)
  459.          except IOError:
  460.             break
  461.       else:
  462.          break
  463.    if style == 'table' and num_columns == 0:
  464.       print 'nRead %d of %d items (%.2f seconds)' % (success_count, total_count, time.time() - start_time)
  465.    try:
  466.       opc.remove('test')
  467.    except OpenOPC.OPCError, error_msg:
  468.       if opc_mode == 'open': error_msg = error_msg[0]
  469.       print error_msg
  470.          
  471. # ACTION: Write Items
  472. elif action == 'write':
  473.    if group_size and len(tags) > group_size and opc_mode == 'dcom':
  474.       opc_write = opc.iwrite
  475.       rotate = irotate
  476.    else:
  477.       opc_write = opc.write
  478.    try:
  479.       status = output(rotate(opc_write(tag_value_pairs,
  480.                                 size=group_size,
  481.                                 pause=tx_pause,
  482.                                 include_error=include_err_msg),
  483.                          num_columns), style)
  484.    except OpenOPC.OPCError, error_msg:
  485.       if opc_mode == 'open': error_msg = error_msg[0]
  486.       print error_msg
  487.    if style == 'table' and num_columns == 0:
  488.       success = len([s for s in status if s[1] != 'Error'])
  489.       print 'nWrote %d of %d items (%.2f seconds)' % (success, len(tag_value_pairs), time.time() - start_time)
  490. # ACTION: List Items (Tree Browser)
  491.    
  492. elif action == 'list':
  493.    if opc_mode == 'open':
  494.       opc_list = opc.list
  495.    else:
  496.       opc_list = opc.ilist
  497.       rotate = irotate
  498.    try:
  499.       output(rotate(opc_list(tags, recursive=recursive), num_columns), style)
  500.    except OpenOPC.OPCError, error_msg:
  501.       if opc_mode == 'open': error_msg = error_msg[0]
  502.       print error_msg
  503. # ACTION: List Items (Flat Browser)
  504.    
  505. elif action == 'flat':
  506.    try:
  507.       output(opc.list(tags, flat=True), style)
  508.    except OpenOPC.OPCError, error_msg:
  509.       if opc_mode == 'open': error_msg = error_msg[0]
  510.       print error_msg
  511. # ACTION: Item Properties
  512. elif action == 'properties':
  513.    if opc_mode == 'open':
  514.       opc_properties = opc.properties
  515.    else:
  516.       opc_properties = opc.iproperties
  517.       rotate = irotate
  518.    if property_ids != None:
  519.       value_idx = 2
  520.    else:
  521.       value_idx = 3
  522.    try:
  523.       output(rotate(opc_properties(tags, property_ids), num_columns, value_idx), style, value_idx)
  524.    except OpenOPC.OPCError, error_msg:
  525.       if opc_mode == 'open': error_msg = error_msg[0]
  526.       print error_msg
  527. # ACTION: Server Info
  528. elif action == 'info':
  529.    try:
  530.       output(rotate(opc.info(), num_columns), style)
  531.    except OpenOPC.OPCError, error_msg:
  532.       if opc_mode == 'open': error_msg = error_msg[0]
  533.       print error_msg
  534. # ACTION: List Servers
  535. elif action == 'servers':
  536.    try:
  537.       output(rotate(opc.servers(opc_host), num_columns), style)
  538.    except OpenOPC.OPCError, error_msg:
  539.       if opc_mode == 'open': error_msg = error_msg[0]
  540.       print "Error getting server list from '%s' - %s" % (opc_host, error_msg)
  541. # Disconnect from OPC Server
  542. try:
  543.    opc.close()
  544. except OpenOPC.OPCError, error_msg:
  545.    if opc_mode == 'open': error_msg = error_msg[0]
  546.    print error_msg