cli.py
Go to the documentation of this file.
00001 #! /usr/bin/env python 
00002 # -*- coding: utf-8 -*-
00003 #     _____
00004 #    /  _  \
00005 #   / _/ \  \
00006 #  / / \_/   \
00007 # /  \_/  _   \  ___  _    ___   ___   ____   ____   ___   _____  _   _
00008 # \  / \_/ \  / /  _\| |  | __| / _ \ | ┌┐ \ | ┌┐ \ / _ \ |_   _|| | | |
00009 #  \ \_/ \_/ /  | |  | |  | └─┐| |_| || └┘ / | └┘_/| |_| |  | |  | └─┘ |
00010 #   \  \_/  /   | |_ | |_ | ┌─┘|  _  || |\ \ | |   |  _  |  | |  | ┌─┐ |
00011 #    \_____/    \___/|___||___||_| |_||_| \_\|_|   |_| |_|  |_|  |_| |_|
00012 #            ROBOTICS™
00013 #
00014 #  File: cli.py
00015 #  Desc: Horizon Interface Demo Python Module & Script
00016 #  
00017 #  Copyright © 2010 Clearpath Robotics, Inc. 
00018 #  All Rights Reserved
00019 # 
00020 #  Redistribution and use in source and binary forms, with or without
00021 #  modification, are permitted provided that the following conditions are met:
00022 #      * Redistributions of source code must retain the above copyright
00023 #        notice, this list of conditions and the following disclaimer.
00024 #      * Redistributions in binary form must reproduce the above copyright
00025 #        notice, this list of conditions and the following disclaimer in the
00026 #        documentation and/or other materials provided with the distribution.
00027 #      * Neither the name of Clearpath Robotics, Inc. nor the
00028 #        names of its contributors may be used to endorse or promote products
00029 #        derived from this software without specific prior written permission.
00030 # 
00031 #  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 
00032 #  AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 
00033 #  IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 
00034 #  ARE DISCLAIMED. IN NO EVENT SHALL CLEARPATH ROBOTICS, INC. BE LIABLE FOR ANY
00035 #  DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
00036 #  (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
00037 #  LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
00038 #  ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
00039 #  (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
00040 #  SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
00041 #
00042 #  Please send comments, questions, or patches to code@clearpathrobotics.com
00043 #
00044 
00045 
00046 
00047 
00048 ################################################################################
00049 # Script Run Method Check
00050 
00051 
00052 
00053 # Check if run as a script
00054 if __name__ == "__main__":
00055     
00056     # Check if run with '-m'
00057     try:
00058         from .. import utils
00059         
00060     # Not run with '-m'
00061     except:
00062         
00063         # Notify of proper run method
00064         print ("ERROR: clearpath.cli is a module and must be run "\
00065                "by:\n  python -m clearpath.cli")
00066 
00067         # Exit Error
00068         import sys
00069         sys.exit(1)
00070 
00071 
00072 
00073 
00074 ################################################################################
00075 # Module
00076 
00077 
00078 
00079 ## @package clearpath.horizon.demo
00080 #  Horizon Interface Demo Python Module & Script
00081 # 
00082 #  Horizon Basic Command-Line Controller.                                     \n
00083 #  Supported Horizon version(s): 0.1 - 1.0
00084 #
00085 #  @author     Ryan Gariepy
00086 #  @author     Malcolm Robert
00087 #  @date       14/01/10
00088 #  @req        clearpath.utils                                                \n
00089 #              clearpath.horizon                                              \n
00090 #              clearpath.horizon.messages                                     \n
00091 #              clearpath.horizon.payloads                                     \n
00092 #              clearpath.horizon.protocol                                     \n
00093 #              clearpath.horizon.transports                                   \n
00094 #              pyCrypto [http://www.dlitz.net/software/pycrypto/]             \n
00095 #              -- for encryption                                              \n
00096 #              pySerial [http://pyserial.sourceforge.net/]                    \n
00097 #              -- for serial support
00098 #  @version    1.0
00099 #
00100 #  @section USE
00101 #  
00102 #  The intended purpose of this module is to provide a CMD wrapper for the
00103 #  Horizon interface to provide the user with a basic command-line demo
00104 #  that can fully control a Horizon based platform. Further, this module 
00105 #  provides script code to bootstrap the CMD wrapper.
00106 #
00107 #  While the wrapper (HorizonDemo) can be used directly, it is only useful
00108 #  for command-line controls and its use outside of this module is discouraged.
00109 #  To use the wrapper, instantiate and open Horizon, then instantiate the
00110 #  wrapper with the parameters port (the prompt prefix), horizon 
00111 #  (the opened Horizon), and output (filename to output data messages to).
00112 #  Instantiation will automatically poll the platform for information then
00113 #  print to standard out a splash. Then, to enter the interactive shell, call
00114 #  cmdloop. Note that this method will block (calling Horizon methods as needed)
00115 #  until the user exits the interactive shell.
00116 #
00117 #  This script is executed by running
00118 #  'python -m clearpath.horizon.demo'
00119 #  To list the required and optional command-line arguments run
00120 #  'python -m clearpath.horizon.demo --help'
00121 #
00122 #  Various logging messages (debug, warning,...) are logged with the standard
00123 #  Python logger to the log named 'clearpath.horizon.demo'.                   \n
00124 #  Messages are ignored by default; remove the only handler from it and turn
00125 #  on propagation to get messages to propagate.
00126 #  When this module is run as a script, logging messages are turned on according
00127 #  to command-line arguments.
00128 #
00129 #  @section HISTORY
00130 #  Version 0.1 - 0.3 {Ryan Gariepy}
00131 #  - Initial Creation as protocol_demo.py
00132 #
00133 #  Version 0.4 {Malcolm Robert}
00134 #  - Move to horizon.py
00135 #  - Added command-line options / arguments
00136 #  - Added interactive command shell
00137 #  - Added version support
00138 #  - Added Doxygen documentation
00139 #  - Changed version scheme to match Horizon doc
00140 #  - Horizon support for v0.4
00141 #
00142 #  Version 0.5
00143 #  - Move to horizon_demo.py
00144 #  - Added TCP and UDP support
00145 #  - Horizon support for v0.5
00146 #
00147 #  Version 0.6
00148 #  - Move to demo.py
00149 #  - Horizon support for v0.6
00150 #
00151 #  Version 0.7
00152 #  - Horizon support for v0.7
00153 #
00154 #  Version 0.8
00155 #  - Horizon support for v0.8
00156 #
00157 #  Version 1.0
00158 #  - Added Encryption Support
00159 #  - Horizon support for v 0.1 - 1.0
00160 #  - Python 2.6+ & 3.x compatible
00161 #
00162 #  @section License
00163 #  @copydoc public_license
00164 #
00165 """Horizon Basic Command-Line Controller 
00166 
00167    Copyright © 2010 Clearpath Robotics, Inc.
00168    All rights reserved
00169    
00170    Created: 14/01/10
00171    Author:  Ryan Gariepy & Malcolm Robert
00172    Version: 1.0
00173    """
00174 
00175 # Required Clearpath Modules
00176 from .. import utils            # Clearpath Utilities
00177 from .  import Horizon          # Horizon Interface
00178 from .  import logger as horizon_logger
00179                                 # Horizon Interface Logger 
00180 from .  import messages         # Horizon Protocol Message Definition
00181 from .  import payloads         # Horizon Protocol Message Payload Definitions
00182 from .  import protocol         # Horizon Protocol Interface
00183 from .  import transports       # Horizon Transport Definitions
00184 
00185 # Required Python Modules
00186 import cmd                      # Interactive Command-Line Sessions
00187 import datetime                 # Date & Time Manipulation
00188 import inspect                  # Detailed Python Manipulation
00189 import logging                  # Logging Utilities
00190 import optparse                 # Command-Line Options Parsing
00191 import os                       # Operating System Capabilities
00192 import re                       # Regular Expressions
00193 import sys                      # Python Interpreter Functionality
00194 import threading                # Python Thread Support
00195 import time                     # System Date & Time
00196 import readline
00197 
00198 
00199 try:
00200     import serial               # Serial Port Control
00201 except ImportError:
00202     pass
00203 
00204 
00205 # Module Support
00206 ## Module Version
00207 __version__  = "1.0"
00208 """Module Version"""
00209 ## SVN Code Revision
00210 __revision__ = "$Revision: 800 $"
00211 """SVN Code Revision"""
00212 
00213 
00214 
00215 ## Message Log
00216 logger = logging.getLogger('clearpath.horizon.demo')
00217 """Horizon Demo Module Log"""
00218 logger.setLevel(logging.NOTSET)
00219 logger.addHandler(utils.NullLoggingHandler())
00220 logger.propagate = False
00221 logger.debug("Loading clearpath.horizon.demo ...")
00222 
00223 
00224 
00225 
00226 ################################################################################
00227 # Basic Command-Line Controller
00228 
00229 
00230 
00231 ## Horizon Command-Line Controller
00232 #
00233 #  Interactive shell for controlling a Horizon device.
00234 #
00235 #  @see cmd.Cmd
00236 #  @since 0.1
00237 #
00238 #  @pydoc
00239 class HorizonDemo(cmd.Cmd):
00240     """Horizon Command-Line Controller"""
00241     
00242     
00243     ## Create A Horizon Command-Line Controller
00244     #
00245     #  @param  port    The device name to use in the prompt ('horizon:port$')
00246     #  @param  horizon The Horizon instance to send commands to.
00247     #                  Must already be opened.
00248     #  @param  output  Where to send subscription data?
00249     #                  '' and '/dev/null' indicates nowhere and enables 
00250     #                  'get_waiting' 
00251     #
00252     #  @pydoc
00253     def __init__(self,port,horizon,output=''):
00254         """Create A Horizon Command-Line Controller"""
00255         logger.debug("%s: Instance creation started..." % \
00256                      self.__class__.__name__)
00257         
00258         cmd.Cmd.__init__(self)  # Superclass initialization
00259         
00260         # Class Variables
00261         ## Repetition Commands
00262         self._commands = []     # Format: tuple([start,freq,tuple([meth,vals])])
00263         # Commands are repeated from another thread, so we need to synchronize all
00264         # access to _commands using this lock:
00265         self._cmd_lock = threading.RLock()
00266         ## Horizon Interface
00267         self._horizon = horizon
00268         ## Subscription Output
00269         self._output = None
00270         ## Repeat Thread
00271         self._repeater = threading.Thread(target = self._repeat)
00272         ## Repeat Looping?
00273         self._repeating = False
00274         
00275         # Setup CMD
00276         self.intro = "\n------------------------------\n" \
00277                        "--          Horizon         --\n" \
00278                        "-- Clearpath Robotics, Inc. --\n" \
00279                        "------------------------------\n\n" \
00280                        "Basic Horizon Interactive Command-Line Controller "\
00281                        "Type 'help' to get the list of available commands.\n\n"\
00282                        "%s\n" % self._about()
00283         self.doc_leader = \
00284           "\nBasic Horizon Interactive Command-Line Controller \n"\
00285             "These shell commands are defined internally. "\
00286             "Type 'help' to see this list.\n"\
00287             "Type 'help <name>' to find out how to use the command 'name'.\n"\
00288             "Use 'horizon_demo --doc' to find out more about Horizon in "\
00289             "general.\n"
00290         self.prompt = 'horizon:' + port + '$ '
00291                 
00292         # explore all attributes of horizon for possibilities
00293         for i in Horizon.__dict__:
00294             
00295             # Test doc string and version
00296             if i[0] != '_': #getattr(self._horizon,i) != None and \
00297                     #getattr(self._horizon,i).__doc__ != None and \
00298                     #doc.match(getattr(self._horizon,i).__doc__):
00299                 
00300                 # command Function Wrapper to call _parse_arguments on name
00301                 def tmp(self,line,method=i):
00302                     self._parse_arguments(method=getattr(self._horizon,method),
00303                                           arguments=line)
00304 
00305                 def tmp_help(self,method=i):
00306                     self._print_help(method)
00307 
00308                     
00309                 # copy doc string
00310                 tmp.__doc__ = getattr(self._horizon,i).__doc__
00311                 
00312                 # add new command
00313                 setattr(HorizonDemo, "do_"+i, tmp)
00314                 setattr(HorizonDemo, "help_"+i, tmp_help)
00315                 
00316         # No Output -> additional command
00317         if output == '' or output == '/dev/null' or output == None:
00318             setattr(HorizonDemo, "do_get_waiting", self._waiting)
00319             setattr(HorizonDemo, "complete_get_waiting", self._waiting_complete)
00320             
00321         # Output
00322         else:
00323             self._output = logging.getLogger("HorizonDemo")
00324             self._output.setLevel(logging.DEBUG)
00325             logger_handler = logging.FileHandler(filename=output)
00326             logger_handler.setFormatter(logging.Formatter("%(message)s"))
00327             self._output.addHandler(logger_handler)
00328         
00329         # Setup Thread
00330         self._repeater.setDaemon(True)
00331     
00332         logger.debug("%s: ...instance creation complete." % \
00333                      self.__class__.__name__)
00334                 
00335         
00336     ## Destroy a Horizon Demo.
00337     #
00338     #  Horizon Demo class destructor.                                    \n
00339     #  Stops the repeat thread if running.
00340     #
00341     #  @pydoc
00342     def __del__(self):
00343         """Destroy a Horizon Demo."""
00344         logger.debug("%s: Instance destruction started..." % \
00345                      self.__class__.__name__)
00346         
00347         # Stop Thread
00348         self._repeating = False
00349         
00350         logger.debug("%s: ...instance destruction complete." % 
00351                      self.__class__.__name__)
00352                 
00353         
00354     ## Get Horizon About String
00355     #
00356     #  @return string of Horizon information
00357     #
00358     #  @pydoc
00359     def _about(self):
00360         """Get Horizon About String"""
00361         
00362         result = ""
00363         
00364         # Get info
00365         try:
00366             # Transport Information
00367             result += '-- Horizon  --\n'\
00368                       'Transport:     %s\n'\
00369                       'Device:        %s\n'\
00370                       'Version:       %d.%d\n' % \
00371                       (self._horizon._protocol._transport.__class__.__name__, 
00372                        str(self._horizon._protocol._transport),
00373                        self._horizon.version[0],self._horizon.version[1])
00374             
00375             # Platform Name
00376             name = self._horizon.request_platform_name()
00377             result += '-- Platform --\n'\
00378                       'Name:          %s\n' % (name.name)
00379                       
00380             # Platform Information
00381             info = self._horizon.request_platform_info()
00382             result += 'Model:         %s\n'\
00383                       'Revision:      %d\n'\
00384                       'Serial Number: %010d\n' % (info.model, info.revision, info.serial)
00385 
00386             # Platform Firmware
00387             firm = self._horizon.request_firmware_info()
00388             result += '-- Firmware --\n'\
00389                       'Horizon:       %d.%d\n'\
00390                       'Version:       %d.%d\n'\
00391                       'Last Updated:  %s\n' % (firm.version[0], firm.version[1],
00392                                                firm.firmware[0], firm.firmware[1],
00393                                                firm.written.strftime("%I:%M %p - %B %d, %Y"))
00394         
00395             # Platform Status
00396             syst = self._horizon.request_system_status()
00397             sec = syst.uptime
00398             mil = sec % 1000
00399             sec = (sec - mil) / 1000
00400             min = sec
00401             sec = min % 60
00402             min = (min - sec) / 60
00403             hou = min
00404             min = hou % 60
00405             hou = (hou - min) / 60
00406             day = hou
00407             hou = day % 24
00408             day = (day - hou) / 24
00409             result += '-- Status   --\n'\
00410                       'Uptime:        %02ddays %02dh %02dm %02ds %03dms\n'\
00411                       % (day, hou, min, sec, mil)
00412                           
00413             # Platform Power Status            i
00414             powe = self._horizon.request_power_status()
00415             result += '-- Status   --\n'
00416             result += 'Power:         %02.2f%%\n' % ((sum(powe.charges,0.0)/
00417                                                         len(powe.charges)))
00418                 
00419             # Platform Safety Status
00420             safe = self._horizon.request_safety_status()
00421             emer = ''
00422             if safe.has_emergency_stop():
00423                 emer = 'EMERGENCY STOP'
00424             elif safe.flags & 0x0002 > 0:
00425                 emer = 'PAUSED'
00426             result += 'Mode:          %s\n' % (emer)
00427                   
00428         # Error(s) encountered
00429         except NotImplementedError as ex:
00430             logger.error(str(ex))
00431             result = "ERROR: Failed to obtain device info!"
00432         except utils.SubscriptionError as ex:
00433             logger.error(str(ex))
00434             result = "ERROR: Failed to obtain device info!"
00435         except utils.TimeoutError as ex:
00436             logger.error(str(ex))
00437             result = "ERROR: Failed to obtain device info!"
00438         except utils.TransportError as ex:
00439             logger.error(str(ex))
00440             result = "ERROR: Failed to obtain device info!"
00441         except ValueError as ex:
00442             logger.error(str(ex))
00443             result = "ERROR: Failed to obtain device info!"
00444             
00445         # Return
00446         return result[:-1]
00447                     
00448         
00449     ## Extract Arguments
00450     #
00451     #  Extract arguments from a Line using whitespace as separators.
00452     #
00453     #  @param  arguments The line to extract from
00454     #  @return List of string arguments, empty if none
00455     #
00456     #  @pydoc
00457     def _extract_arguments(self, arguments):
00458         """Extract arguments from a Line."""
00459         
00460         # Prepare regex(s)
00461         quote = '(?:(?P<quo>[\"\'])(.+?)(?P=quo))'
00462         escapes = '(?:(?:\\\\\\s)|(?:\\\\a)|(?:\\\\b)|(?:\\\\f)|(?:\\\\n)|' \
00463                     '(?:\\\\r)|(?:\\\\t)|(?:\\\\v)|(?:\\\\\\\\))'
00464         nonwhite = '(?:[^\\t\\n\\r\\f\\v\\\\\\ ])'
00465         token = '(?:(?:'+escapes+'|'+nonwhite+')+)'
00466         block = '('+quote+'|'+token+')'
00467         line = '^'+token+'(?:\\s+'+token+')*$'
00468         parser = re.compile(line)
00469         matcher = re.compile(block)
00470         replacer = re.compile('\\\\([^\\\\])')
00471         
00472         # Format & Parse the provided line
00473         arguments = arguments.strip()
00474         match = parser.match(arguments)
00475         params = []
00476         if match != None:
00477             match = matcher.findall(arguments)
00478             for sub in match:
00479                 rsub = sub[0]
00480                 
00481                 # Extract from Quotes
00482                 if sub[1] != '':
00483                     rsub = sub[2]
00484                 
00485                 # Replace Escapes (if not quoted in ')
00486                 if sub[1] != "'":
00487                     rsub = rsub.replace('\\a','\a')
00488                     rsub = rsub.replace('\\b','\b')
00489                     rsub = rsub.replace('\\f','\f')
00490                     rsub = rsub.replace('\\n','\n')
00491                     rsub = rsub.replace('\\r','\r')
00492                     rsub = rsub.replace('\\t','\t')
00493                     rsub = rsub.replace('\\v','\v')
00494                     rsub = replacer.sub(r'\1',rsub)
00495                     rsub = rsub.replace('\\\\','\\')
00496                 
00497                 # Add To List
00498                 params.append(rsub)
00499                 
00500         # Return result
00501         return params
00502     
00503 
00504     ## Convert Arguments to their Proper Types
00505     # 
00506     #  Converts arguments based on given default values.                      \n
00507     #  Prints argument errors to stdout.
00508     #
00509     #  @param  params The arguments to convert
00510     #  @param  method The method to convert for
00511     #  @return Dictionary of values, Empty if none, None if error encountered
00512     #
00513     #  @pydoc
00514     def _arguments_to_values(self, params, method):
00515         """Convert arguments to their proper types."""
00516         
00517         # Get a list of the method's arguments and their default values
00518         args = inspect.getargspec(method)
00519         if 'handler' in args[0]:
00520             args[0].remove('handler')
00521         if 'subscription' in args[0]:
00522             args[0].remove('subscription')
00523         values = {}
00524         
00525         # Prepare Boolean regex
00526         yes = re.compile('^(?:(?:[yY][eE][sS])|(?:[tT][rR][uU][eE])|(?:1))$')
00527         no = re.compile('^(?:(?:[nN][oO])|(?:[fF][aA][lL][sS][eE])|(?:0))$')
00528         
00529         # Check Number of Argmuents
00530         if (params == None and len(args[0]) > 1) or \
00531                 (len(params) != len(args[0]) - 1 and (len(args[0]) == 1 or 
00532                             type(args[3][-2]) != dict)):
00533             print(("ERROR: %s invalid number of arguments" % method.__name__))
00534             return None
00535         
00536         # Test the provided arguments
00537         for i,param in enumerate(params):
00538             
00539             # Dictionary
00540             if type(args[3][i]) == dict:
00541                 values[args[0][i+1]] = {}
00542                 
00543                 # Steal rest of arguments
00544                 for j in range(i,len(params)):
00545                     tokens = params[j].split(':')
00546                     if len(tokens) != 2:
00547                         print(("ERROR: %s argument %d: must be key:value" % (
00548                                                         method.__name__, (j+1))))
00549                         return None
00550                     key = None
00551                     if type(list(args[3][i].keys())[0]) == str:
00552                         key = tokens[0]
00553                     elif type(list(args[3][i].keys())[0]) == float:
00554                         try:
00555                             key = float(tokens[0])
00556                         except ValueError:
00557                             print(("ERROR: %s argument %d: key must be a float"\
00558                                     % (method.__name__, (j+1))))
00559                             return None
00560                     elif sys.version_info[0] < 3 and \
00561                             type(list(args[3][i].keys())[0]) == int:
00562                         try:
00563                             key = int(tokens[0])
00564                         except ValueError:
00565                             print(("ERROR: %s argument %d: key must be a long"\
00566                                     % (method.__name__,(j+1))))
00567                             return None
00568                     elif type(list(args[3][i].keys())[0]) == int:
00569                         try:
00570                             key = int(tokens[0])
00571                         except ValueError:
00572                             print(("ERROR: %s argument %d: key must be an int"\
00573                                     % (method.__name__,(j+1))))
00574                             return None
00575                     elif type(list(args[3][i].keys())[0]) == bool:
00576                         if yes.match(tokens[0]):
00577                             key = True
00578                         elif no.match(tokens[0]):
00579                             key = False
00580                         else:
00581                             print(("ERROR: %s argument %d: key must be a "\
00582                                    "boolean" % (method.__name__,(j+1))))
00583                             return None
00584                     print (key)
00585                     if type(args[3][i][list(args[3][i].keys())[0]]) == str:
00586                         values[args[0][i+1]][key] = tokens[1]
00587                     elif type(args[3][i][list(args[3][i].keys())[0]]) == float:
00588                         try:
00589                             values[args[0][i+1]][key] = float(tokens[1])
00590                         except ValueError:
00591                             print(("ERROR: %s argument %d: key must be a float"\
00592                                     % (method.__name__, (j+1))))
00593                             return None
00594                     elif sys.version_info[0] < 3 and \
00595                             type(args[3][i][list(args[3][i].keys())[0]]) == int:
00596                         try:
00597                             values[args[0][i+1]][key] = int(tokens[1])
00598                         except ValueError:
00599                             print(("ERROR: %s argument %d: key must be a long"\
00600                                     % (method.__name__,(j+1))))
00601                             return None
00602                     elif type(args[3][i][list(args[3][i].keys())[0]]) == int:
00603                         try:
00604                             values[args[0][i+1]][key] = int(tokens[1])
00605                         except ValueError:
00606                             print(("ERROR: %s argument %d: key must be an int"\
00607                                     % (method.__name__,(j+1))))
00608                             return None
00609                     elif type(args[3][i][list(args[3][i].keys())[0]]) == bool:
00610                         if yes.match(tokens[1]):
00611                             values[args[0][i+1]][key] = True
00612                         elif no.match(tokens[1]):
00613                             values[args[0][i+1]][key] = False
00614                         else:
00615                             print(("ERROR: %s argument %d: key must be a "\
00616                                    "boolean" % (method.__name__,(j+1))))
00617                             return None
00618                 print((values[args[0][i+1]]))
00619                 break
00620             
00621             # String
00622             elif type(args[3][i]) == str:
00623                 values[args[0][i+1]] = param
00624                 
00625             # Float
00626             elif type(args[3][i]) == float:
00627                 try:
00628                     values[args[0][i+1]] = float(param)
00629                 except ValueError:
00630                     print(("ERROR: %s argument %d: value must be a float" % (
00631                                                         method.__name__, (i+1))))
00632                     return None
00633             
00634             # Long
00635             elif sys.version_info[0] < 3 and type(args[3][i]) == int:
00636                 try:
00637                     values[args[0][i+1]] = int(param)
00638                 except ValueError:
00639                     print(("ERROR: %s argument %d: value must be a long" % (
00640                                                         method.__name__,(i+1))))
00641                     return None
00642             
00643             # Int
00644             elif type(args[3][i]) == int:
00645                 try:
00646                     values[args[0][i+1]] = int(param)
00647                 except ValueError:
00648                     print(("ERROR: %s argument %d: value must be an int" % (
00649                                                         method.__name__,(i+1))))
00650                     return None
00651             
00652             # Boolean
00653             elif type(args[3][i]) == bool:
00654                 if yes.match(param):
00655                     values[args[0][i+1]] = True
00656                 elif no.match(param):
00657                     values[args[0][i+1]] = False
00658                 else:
00659                     print(("ERROR: %s argument %d: value must be a boolean" % (
00660                                                         method.__name__,(i+1))))
00661                     return None
00662             
00663         # Result(s)
00664         return values
00665         
00666     
00667     ## Parse a Line of Arguments
00668     #
00669     #  Extracts arguments from a line for the given method.                   \n
00670     #  Runs the method.                                                       \n
00671     #  Outputs anything that is returned.                                     \n
00672     #  ValueErrors are treated as command argument errors.
00673     #
00674     #  @param method    The method to execute
00675     #  @param arguments The string containing the whitespace separated arguments 
00676     #
00677     #  @pydoc 
00678     def _parse_arguments(self,method,arguments):
00679         """Parse a Line of Arguments"""
00680         
00681         # Get passed arguments
00682         params = self._extract_arguments(arguments)
00683         
00684         # Convert to values
00685         values = self._arguments_to_values(params, method)
00686         if values == None: return
00687         
00688         # Run the method
00689         try:
00690             result = method(**values)
00691             if result: print((result.print_format()))
00692         except NotImplementedError as ex:
00693             print(("ERROR: %s is not supported on the platform!" % \
00694                 method.__name__))
00695             logger.debug(ex)
00696         except utils.TransportError as ex:
00697             print(("ERROR: Transport send for %s failed!" % \
00698                 method.__name__))
00699             logger.debug(ex)
00700         except ValueError as ex:
00701             print(("ERROR: " + str(ex)))
00702             logger.debug(ex)
00703         except utils.TimeoutError as ex:
00704             print(("ERROR: Timeout occured waiting for %s completion!" % \
00705                 method.__name__))
00706             logger.debug(ex)
00707 
00708 
00709     ## Prints a usage message for a command
00710     #
00711     def _print_help(self, method):
00712         helpstr = 'usage: ' + method
00713         argspec = inspect.getargspec(getattr(self._horizon, method))
00714 
00715         # Inspect the horizon function argspec to figure out args for usage
00716         for i in range(len(argspec.args)):
00717             # Arg names that are not user specified:
00718             if argspec.args[i] in ['self', 'subscription', 'handler'] :
00719                 continue
00720 
00721             if i < (len(argspec.args) - len(argspec.defaults)) :
00722                 # arg has no default, so just print its name
00723                 helpstr += ' <' + argspec.args[i] + '>'
00724             else:
00725                 # arg has a default, print the type
00726                 argtype = str(type(argspec.defaults[i-len(argspec.args)]))
00727                 # argtype has form "<type 'typename'>", we want to extract "typename"
00728                 argtype = argtype[argtype.index('\'')+1:]
00729                 argtype = argtype[:argtype.index('\'')]
00730                 helpstr += ' <' + argtype + ' ' + argspec.args[i] + '>'
00731 
00732         print(helpstr)
00733             
00734             
00735     ## Output Subscription Data
00736     #
00737     #  Writes subscription data to the output file.
00738     #
00739     #  @param payload The received payload
00740     #
00741     #  @pydoc
00742     def _handler(self, payload):
00743         """Output Subscription Data"""
00744         
00745         # Format Timestamp
00746         sec = payload.timestamp
00747         mil = sec % 1000
00748         sec = (sec - mil) / 1000
00749         min = sec
00750         sec = min % 60
00751         min = (min - sec) / 60
00752         hou = min
00753         min = hou % 60
00754         hou = (hou - min) / 60
00755         day = hou
00756         hou = day % 24
00757         day = (day - hou) / 24
00758         
00759         if payload.error != None:
00760             self._output.debug('-- %02ddays %02dh %02dm %02ds %03dms --\n%s' % (
00761                 day, hou, min, sec, mil, str(payload.error)))
00762         else:
00763             self._output.debug('-- %02ddays %02dh %02dm %02ds %03dms --\n%s' % (
00764                 day, hou, min, sec, mil, payload.print_format()))
00765 
00766     
00767     ## Command Repeater Loop
00768     #  
00769     #  Repeater Thread method. Loops on _repeating waiting to send commands.
00770     #
00771     #  @pydoc
00772     def _repeat(self):
00773         """Command Repeater Loop"""
00774         logger.debug("%s: Entering repeat loop..." % self.__class__.__name__)
00775         
00776         # Continuous Loop
00777         while self._repeating:
00778             
00779             # Check for no commands -> stop thread
00780             if len(self._commands) == 0:
00781                 self._repeating = False
00782                 self._repeater = threading.Thread(target = self._repeat)
00783                 self._repeater.setDaemon(True)
00784                 break
00785             
00786             # Update Timestamp
00787             timestamp = 0
00788             t = datetime.datetime.today()
00789             timestamp = t.microsecond/1000 + t.second*1000 + \
00790                   t.minute*60*1000 + t.hour*60*60*1000 + t.day*24*60*60*1000
00791             while timestamp > 4294967295: timestamp -= 4294967295
00792             if sys.version_info[0] > 2:
00793                 timestamp = int(timestamp)
00794             else:
00795                 timestamp = int(timestamp)
00796                 
00797             # Check Commands
00798             self._cmd_lock.acquire()
00799             try:
00800                 for cmd in self._commands:
00801                     diff = 0
00802                     if timestamp > cmd[0]:
00803                         diff = timestamp - cmd[0]
00804                     else:
00805                         diff = 4294967295 - cmd[0] + timestamp
00806                     if diff % int(1000/cmd[1]) == 0:
00807                         # Run command
00808                         try:
00809                             cmd[2][0](**cmd[2][1])
00810                         except Exception:
00811                             pass
00812             finally:
00813                 self._cmd_lock.release()
00814             
00815             # Release Processor
00816             time.sleep(0.001)
00817         
00818         logger.debug("%s: ...repeat loop exited." % self.__class__.__name__)
00819             
00820          
00821     ## Get Waiting Data
00822     #
00823     #  Outputs data retrieved from subscriptions.
00824     #
00825     #  @param line The rest of the line
00826     #
00827     #  @pydoc
00828     def _waiting(self, line):
00829         """syntax: get_waiting [<request>]\n""" \
00830         """-- Show any data received from a subscription on 'request'.\n""" \
00831         """   If request is not specified then list all data."""
00832         
00833         # Get passed arguments
00834         params = self._extract_arguments(line)
00835         
00836         # Check number of arguments
00837         if len(params) > 1:
00838             print ("ERROR: too many arguments!")
00839             return
00840         
00841         # Check request
00842         request = None
00843         if len(params) == 1:
00844             if not params[0] in self.completenames('request_'):
00845                 print ("ERROR: Unsupported / Invalid command!")
00846                 return
00847             method = None
00848             try:
00849                 method = getattr(self._horizon, params[0])
00850             except AttributeError:
00851                 print ("ERROR: Unsupported / Invalid command!")
00852                 return
00853             args = inspect.getargspec(method)
00854             if not 'subscription' in args[0]:
00855                 print(("ERROR: %s does not support subscriptions!" % params[1]))
00856                 return
00857             request = params[0]
00858             
00859         # Get Data
00860         data = []
00861         try:
00862             data = self._horizon.get_waiting_data(request=request)
00863         except Exception as ex:
00864             print(("Error: unexpected error encountered!\n%s" % str(ex)))
00865         
00866         # Output Data
00867         if len(data) == 0:
00868             if request == None:
00869                 print ("No data.")
00870             else:
00871                 print(("No data for %s." % request))
00872         else:
00873             for payload in data:
00874                 if isinstance(payload[1], payloads.Null):
00875                     continue
00876                 
00877                 # Format Timestamp
00878                 sec = payload[2]
00879                 mil = sec % 1000
00880                 sec = (sec - mil) / 1000
00881                 min = sec
00882                 sec = min % 60
00883                 min = (min - sec) / 60
00884                 hou = min
00885                 min = hou % 60
00886                 hou = (hou - min) / 60
00887                 day = hou
00888                 hou = day % 24
00889                 day = (day - hou) / 24
00890           
00891                 print(('-- %02ddays %02dh %02dm %02ds %03dms --' % (
00892                             day, hou, min, sec, mil)))
00893                 print((payload[1].print_format()))
00894     
00895     
00896     ## Get Waiting Command Tab Completion
00897     #
00898     #  Calculates a list of possible paramters for get_waiting based on the 
00899     #  entered prefix.
00900     #
00901     #  @param  text   The last parameter before tab
00902     #  @param  line   The entire line of text
00903     #  @param  begidx ???
00904     #  @param  endidx ???
00905     #  @return List of string possibilities
00906     #
00907     #  @pydoc
00908     def _waiting_complete(self, text, line, begidx, endidx):
00909         """Get Waiting Command Tab Completion"""
00910         
00911         # Get passed arguments
00912         params = self._extract_arguments(line)
00913         
00914         # Complete Command
00915         if (len(params) == 2 and len(text.strip()) > 0) or \
00916                 (len(params) == 1 and len(text.strip()) == 0):
00917             cmds = self.completenames(text)
00918             for cmd in cmds[:]:
00919                 if not cmd.startswith('request_'):
00920                     cmds.remove(cmd)
00921         
00922         # Resultant List
00923         return cmds
00924             
00925            
00926     ## Empty Line Action
00927     #
00928     #  Does nothing when an empty line is entered rather than the default
00929     #  'repeat last command'.
00930     #
00931     #  @pydoc
00932     def emptyline(self):
00933         """Empty Line Action"""
00934         
00935         pass    # Do Nothing
00936     
00937     
00938     ## List all Commands
00939     #
00940     #  Overrides Cmd default to categorize Horizon messages.
00941     #
00942     #  @see Cmd.do_help
00943     #
00944     #  @pydoc
00945     def do_help(self, arg):
00946         """syntax: help [<command>]\n""" \
00947         """-- Show the help text for the requested command.\n""" \
00948         """   If command is not specified then list all commands."""
00949         
00950         # Use default Cmd processing for command help
00951         if arg:
00952             return cmd.Cmd.do_help(self, arg)
00953 
00954         # List all commands (based on cmd.Cmd.do_help)
00955         else:
00956             cmds_cmd = []   # Commands
00957             cmds_req = []   # Requests
00958             cmds_hor = []   # Horizon Other
00959             cmds     = []   # Shell Control
00960             
00961             # Get commands
00962             commands = self.get_names()
00963             commands.sort()
00964             
00965             # Categorize Commands
00966             prev = ''
00967             for command in commands:
00968                 if command[:3] == 'do_':
00969                     if command == prev:
00970                         continue
00971                     prev = command
00972                     command = command[3:]
00973                     if len(command) > 8 and command[:8] == 'request_':
00974                         cmds_req.append(command)
00975                     elif len(command) > 4 and command[:4] == 'set_':
00976                         cmds_cmd.append(command)
00977                     else:
00978                         try:
00979                             getattr(self._horizon,command)
00980                             cmds_hor.append(command)
00981                         except AttributeError:
00982                             if command == 'subscribe' or command == 'about' or \
00983                               command == 'get_waiting' or command == 'get_time':
00984                                 cmds_hor.append(command)
00985                             else:
00986                                 cmds.append(command)
00987             
00988             # Output Lists
00989             self.stdout.write("%s\n"%str(self.doc_leader))
00990             self.print_topics('Horizon', cmds_hor, 15,80)
00991             self.print_topics('Horizon Commands', cmds_cmd,15,80)
00992             self.print_topics('Horizon Requests', cmds_req, 15,80)
00993             self.print_topics('Interactive Shell', cmds, 15,80)
00994     
00995     
00996     ## About Horizon Device.
00997     #
00998     #  Get information on the Horizon device.
00999     #
01000     #  @param     arguments  Line entered after 'about'
01001     #
01002     #  @pydoc
01003     def do_about(self, arguments):
01004         """syntax: about \n""" \
01005         """-- Get general info about the Horizon device."""
01006         
01007         # Get passed arguments
01008         params = self._extract_arguments(arguments)
01009         
01010         # Check arguments
01011         if len(params) > 0:
01012             print ("ERROR: Too many arguments!")
01013             return
01014         
01015         # Print Information
01016         print((self._about()))
01017     
01018     
01019     ## Horizon Device Alive?
01020     #
01021     #  Check if the Horizon device is alive.
01022     #
01023     #  @param     arguments  Line entered after 'about'
01024     #
01025     #  @pydoc
01026     def do_is_alive(self, arguments):
01027         """syntax: is_alive \n""" \
01028         """-- Check if the Horizon device is alive."""
01029         
01030         # Get passed arguments
01031         params = self._extract_arguments(arguments)
01032         
01033         # Check arguments
01034         if len(params) > 0:
01035             print ("ERROR: Too many arguments!")
01036             return
01037         
01038         # Print Information
01039         print((self._horizon.alive))
01040     
01041     
01042     ## Horizon Device Time
01043     #
01044     #  Get the Horizon device time.
01045     #
01046     #  @param     arguments  Line entered after 'get_time'
01047     #
01048     #  @pydoc
01049     def do_get_time(self, arguments):
01050         """syntax: get_time \n""" \
01051         """-- Get the Horizon device time."""
01052         
01053         # Get passed arguments
01054         params = self._extract_arguments(arguments)
01055         
01056         # Check arguments
01057         if len(params) > 0:
01058             print ("ERROR: Too many arguments!")
01059             return
01060         
01061         # Get information
01062         try:
01063             sec = self._horizon.device_time
01064             
01065             # Format time
01066             mil = sec % 1000
01067             sec = (sec - mil) / 1000
01068             min = sec
01069             sec = min % 60
01070             min = (min - sec) / 60
01071             hou = min
01072             min = hou % 60
01073             hou = (hou - min) / 60
01074             day = hou
01075             hou = day % 24
01076             day = (day - hou) / 24
01077                     
01078             # Output time        
01079             print(('Time: %02ddays %02dh %02dm %02ds %03dms' \
01080                    % (day, hou, min, sec, mil)))
01081                   
01082         # Error(s) encountered
01083         except NotImplementedError:
01084             print ("ERROR: Failed to obtain device time!")
01085         except utils.SubscriptionError:
01086             print ("ERROR: Failed to obtain device time!")
01087         except utils.TimeoutError:
01088             print ("ERROR: Failed to obtain device time!")
01089         except utils.TransportError:
01090             print ("ERROR: Failed to obtain device time!")
01091         except ValueError:
01092             print ("ERROR: Failed to obtain device time!")
01093     
01094     
01095     ## Subscribe to Command.
01096     #
01097     #  Subscribe to receive request data at a specified interval.
01098     #
01099     #  @param     arguments  Line entered after 'subscribe'
01100     #
01101     #  @pydoc
01102     def do_subscribe(self, arguments):
01103         """syntax: subscribe <frequency> <command>\n""" \
01104         """-- Receive data at <frequency> Hz.\n""" \
01105         """   <frequncy> = off | (1-65534)\n""" \
01106         """   <command> = request command with pertinent arguments"""
01107         
01108         # Get passed arguments
01109         params = self._extract_arguments(arguments)
01110         
01111         # Check Number of Argmuents
01112         if params == None or len(params) < 2:
01113             print ("ERROR: subscribe invalid number of arguments")
01114             return None
01115         
01116         # Check frequency
01117         freq = 0xFFFF
01118         if params[0] != 'off' and ((not params[0].isdigit()) or \
01119                                 int(params[0]) < 1 or int(params[0]) > 65534):
01120             print ("ERROR: Invalid frequency!")
01121             return
01122         elif params[0].isdigit():
01123             freq = int(params[0])
01124         
01125         # Check Command
01126         if not params[1] in self.completenames('request_'):
01127             print ("ERROR: Unsupported / Invalid command!")
01128             return
01129         method = None
01130         try:
01131             method = getattr(self._horizon, params[1])
01132         except AttributeError:
01133             print ("ERROR: Unsupported / Invalid command!")
01134             return
01135         args = inspect.getargspec(method)
01136         if not 'subscription' in args[0]:
01137             print(("ERROR: %s does not support subscriptions!" % params[1]))
01138             return
01139         
01140         # Check Command Argmuents
01141         values = self._arguments_to_values(params[2:], method)
01142         if values == None:
01143             return
01144         values['subscription'] = freq
01145             
01146         # Outputting?
01147         if self._output != None:
01148         
01149             # Manage Handlers - Add
01150             if not freq == 0xFFFF:
01151                 self._horizon.add_handler(handler=self._handler, 
01152                                           request=params[1])
01153             
01154             # Manage Handlers - Remove
01155             else:
01156                 self._horizon.remove_handler(handler=self._handler, 
01157                                              request=params[1])
01158     
01159         # Run the method
01160         try:
01161             res = method(**values)
01162             if res != None: 
01163                 if isinstance(res, payloads.Payload):
01164                     print((res.print_format()))
01165                 else:
01166                     print (res)
01167         except NotImplementedError:
01168             print(("ERROR: %s is not supported on the platform!" % \
01169                 method.__name__))
01170         except utils.SubscriptionError:
01171             print(("ERROR: %s Subscription failed due to frequency!" % \
01172                 method.__name__)) 
01173         except utils.TimeoutError:
01174             print(("ERROR: Timeout occured waiting for %s subscription!" % \
01175                 method.__name__)) 
01176         except utils.TransportError:
01177             print(("ERROR: Transport send for %s subscription failed!" % \
01178                 method.__name__))
01179         except ValueError as ex:
01180             print(("ERROR: " + str(ex)))
01181     
01182     
01183     ## Subscribe Command Tab Completion
01184     #
01185     #  Calculates a list of possible paramters for subscribe based on the 
01186     #  entered prefix.
01187     #
01188     #  @param  text   The last parameter before tab
01189     #  @param  line   The entire line of text
01190     #  @param  begidx ??? - Passed on to other functions
01191     #  @param  endidx ??? - Passed on to other functions
01192     #  @return List of string possibilities
01193     #
01194     #  @pydoc
01195     def complete_subscribe(self, text, line, begidx, endidx):
01196         """Subscribe Command Tab Completion"""
01197         
01198         # Get passed arguments
01199         params = self._extract_arguments(line)
01200         
01201         # Frequency List
01202         if len(params) == 1 and len(text.strip()) == 0:
01203             cmds = list(map(str,list(range(1,65535))))
01204             cmds.insert(0, 'off')
01205             return cmds
01206         
01207         # Complete Frequency
01208         elif len(params) == 2 and len(text.strip()) > 0:
01209             params[1] = params[1].rstrip('0')
01210             if params[1] == 'off' or params[1] == 'of' or params[1] == 'o':
01211                 return ['off']
01212             elif params[1].isdigit() and int(params[1]) < 65535 and \
01213                     int(params[1]) > -1:
01214                 cmds = [params[1]]
01215                 last = [params[1]]
01216                 if len('65535') - len(params[1]) > 1:
01217                     for multiple in range(1,len('65535') - len(params[1])):
01218                         tmp = []
01219                         for cmd in last:
01220                             for i in range(0,10):
01221                                 tmp.append(cmd+str(i))
01222                         cmds += tmp
01223                         last = tmp
01224                 if len('65535') - len(params[1]) > 0:
01225                     for cmd in last:
01226                         for i in range(0,10):
01227                             if int(cmd+str(i)) < 65535:
01228                                 cmds.append(cmd+str(i))
01229                 return cmds
01230             else:
01231                 return []
01232         
01233         # Complete Command
01234         elif (len(params) == 3 and len(text.strip()) > 0) or \
01235                 (len(params) == 2 and len(text.strip()) == 0):
01236             cmds = self.completenames(text)
01237             for cmd in cmds[:]:
01238                 if not cmd.startswith('request_'):
01239                     cmds.remove(cmd)
01240         
01241         # Complete Arguments
01242         else:
01243             complete = None
01244             try:
01245                 complete = getattr(self, 'complete_' + params[1])
01246             except AttributeError:
01247                 complete = self.completedefault
01248             cmds = complete(text, ' '.join(params[1:]), begidx, endidx)
01249         
01250         # Resultant List
01251         return cmds
01252     
01253    
01254     ## Manipulate repeated commands.
01255     #
01256     #  @param arguments  Line entered after 'repeat'
01257     #
01258     #  @pydoc
01259     def do_repeat(self, arguments):
01260         """usage: repeat [list | off [n] | [frequency command]]\n\n""" \
01261         """Manipulate repeated commands\n\n""" \
01262         """Options: \n""" \
01263         """list ................. Print a numbered list of active repeat commands\n""" \
01264         """off [n] .............. Turn off a given repeat command.  [n] is the number provided\n""" \
01265         """                       by the list command, the most recent command is removed if  \n""" \
01266         """                       [n] is not specified.\n""" \
01267         """[frequency] [command]  Cause [command] to be executed at [frequency]\n""" \
01268 
01269         arguments = arguments.strip()
01270 
01271         if arguments == "list":
01272             self._list_repeats()
01273         elif arguments.startswith("off"):
01274             self._remove_repeat(arguments)
01275         else:
01276             self._setup_repeat(arguments)
01277   
01278     ## List active repeat commands
01279     def _list_repeats(self):
01280         self._cmd_lock.acquire()
01281         try:
01282             if len(self._commands) == 0:
01283                 print("No repeat commands are active.")
01284                 return
01285 
01286             for i in range(len(self._commands)):
01287                 # _commands format is tuple(timestamp, freq, tuple(method, dict(args)))
01288                 cmd_str = "{0}: [{1} Hz]".format(i, self._commands[i][1])
01289                 cmd_str += " " + self._commands[i][2][0].func_name
01290                 for name in self._commands[i][2][1]:
01291                     cmd_str += " {0}={1}".format(name, self._commands[i][2][1][name] ) 
01292                 print(cmd_str)
01293         finally:
01294             self._cmd_lock.release()
01295 
01296     ## Remove an active repeat command
01297     #  
01298     #  @param arguments Command line arguments after 'repeat', assumed to be stripped.
01299     def _remove_repeat(self, arguments):
01300         self._cmd_lock.acquire()
01301         try:
01302             if len(self._commands) == 0:
01303                 print("No repeat commands are active.")
01304                 return
01305             
01306             # Remove last item, by default
01307             rem_inx = len(self._commands) - 1;
01308 
01309             # Parse & validate arguments
01310             if arguments != "off":
01311                 # String is stripped.  It will always start with 'off', but 
01312                 # may have an index argument we need to parse out.  Do so:
01313                 try:
01314                     rem_inx = int(arguments[3:])
01315                 except ValueError:
01316                     print("Index argument is not a valid integer.")
01317                     return
01318                 if (rem_inx < 0) or (rem_inx >= len(self._commands)):
01319                     print("Index argument out of range.")
01320                     return
01321 
01322             # We have a valid index to remove.  Now, remove it.
01323             del self._commands[rem_inx]
01324 
01325         finally:
01326             self._cmd_lock.release()
01327        
01328 
01329     ## Subscribe to repeat a command at a specified interval.
01330     #
01331     #  @param     arguments  Line entered after 'repeat'
01332     #
01333     #  @pydoc
01334     def _setup_repeat(self, arguments):
01335         # Get passed arguments
01336         params = self._extract_arguments(arguments)
01337         
01338         # Check Number of Argmuents
01339         if params == None or len(params) < 2:
01340             print ("ERROR: repeat: invalid number of arguments")
01341             return None
01342         
01343         # Check frequency
01344         freq = 0xFFFF
01345         if params[0] != 'off' and ((not params[0].isdigit()) or \
01346                                 int(params[0]) < 1 or int(params[0]) > 65534):
01347             print ("ERROR: Invalid frequency!")
01348             return
01349         elif params[0].isdigit():
01350             freq = int(params[0])
01351         
01352         # Check Command
01353         if not params[1] in self.completenames('set_'):
01354             print ("ERROR: Unsupported command!")
01355             return
01356         method = None
01357         try:
01358             method = getattr(self._horizon, params[1])
01359         except AttributeError:
01360             print ("ERROR: Invalid command!")
01361             return
01362         
01363         # Check Command Argmuents
01364         values = self._arguments_to_values(params[2:], method)
01365         if values == None:
01366             return
01367 
01368         if freq == 0xFFFF:
01369             print("Repeat commands may be disabled with 'repeat off [n]'")
01370             return
01371     
01372         # Run the method
01373         try:
01374             method(**values)
01375         except NotImplementedError:
01376             print(("ERROR: %s is not supported on the platform!" % \
01377                 method.__name__))
01378         except utils.SubscriptionError:
01379             print(("ERROR: %s Subscription failed due to frequency!" % \
01380                 method.__name__)) 
01381         except utils.TimeoutError:
01382             print(("ERROR: Timeout occured waiting for %s subscription!" % \
01383                 method.__name__)) 
01384         except utils.TransportError:
01385             print(("ERROR: Transport send for %s subscription failed!" % \
01386                 method.__name__))
01387         except ValueError as ex:
01388             print(("ERROR: " + str(ex)))
01389         
01390         # Frequency
01391         
01392         # Update Timestamp
01393         timestamp = 0
01394         t = datetime.datetime.today()
01395         timestamp = t.microsecond/1000 + t.second*1000 + \
01396               t.minute*60*1000 + t.hour*60*60*1000 + t.day*24*60*60*1000
01397         while timestamp > 4294967295: timestamp -= 4294967295
01398         timestamp = int(timestamp)
01399         
01400         # Add new
01401         self._cmd_lock.acquire()
01402         self._commands.append(tuple([timestamp,freq,
01403                                      tuple([method,values])]))
01404         self._cmd_lock.release()
01405         
01406         # Start Thread
01407         if self._repeating == False:
01408             self._repeating = True
01409             self._repeater.start()
01410             
01411     
01412     ## Repeat Command Tab Completion
01413     #
01414     #  Calculates a list of possible paramters for repeat based on the 
01415     #  entered prefix.
01416     #
01417     #  @param  text   The last parameter before tab
01418     #  @param  line   The entire line of text
01419     #  @param  begidx ??? - Passed on to other functions
01420     #  @param  endidx ??? - Passed on to other functions
01421     #  @return List of string possibilities
01422     #
01423     #  @pydoc
01424     def complete_repeat(self, text, line, begidx, endidx):
01425         """Repeat Command Tab Completion"""
01426         
01427         # Get passed arguments
01428         params = self._extract_arguments(line)
01429         
01430         # Frequency List
01431         if len(params) == 1 and len(text.strip()) == 0:
01432             cmds = list(map(str,list(range(1,65535))))
01433             cmds.insert(0, 'off')
01434             return cmds
01435         
01436         # Complete Frequency
01437         elif len(params) == 2 and len(text.strip()) > 0:
01438             params[1] = params[1].rstrip('0')
01439             if params[1] == 'off' or params[1] == 'of' or params[1] == 'o':
01440                 return ['off']
01441             elif params[1].isdigit() and int(params[1]) < 65535 and \
01442                     int(params[1]) > -1:
01443                 cmds = [params[1]]
01444                 last = [params[1]]
01445                 if len('65535') - len(params[1]) > 1:
01446                     for multiple in range(1,len('65535') - len(params[1])):
01447                         tmp = []
01448                         for cmd in last:
01449                             for i in range(0,10):
01450                                 tmp.append(cmd+str(i))
01451                         cmds += tmp
01452                         last = tmp
01453                 if len('65535') - len(params[1]) > 0:
01454                     for cmd in last:
01455                         for i in range(0,10):
01456                             if int(cmd+str(i)) < 65535:
01457                                 cmds.append(cmd+str(i))
01458                 return cmds
01459             else:
01460                 return []
01461         
01462         # Complete Command
01463         elif (len(params) == 3 and len(text.strip()) > 0) or \
01464                 (len(params) == 2 and len(text.strip()) == 0):
01465             cmds = self.completenames(text)
01466             for cmd in cmds[:]:
01467                 if not cmd.startswith('set_'):
01468                     cmds.remove(cmd)
01469         
01470         # Complete Arguments
01471         else:
01472             complete = None
01473             try:
01474                 complete = getattr(self, 'complete_' + params[1])
01475             except AttributeError:
01476                 complete = self.completedefault
01477             cmds = complete(text, ' '.join(params[1:]), begidx, endidx)
01478         
01479         # Resultant List
01480         return cmds
01481     
01482     
01483     ## Exit the Interactive Shell.
01484     #
01485     #  Stop the command loop
01486     #
01487     #  @param     self This object
01488     #  @param     arg  Line entered after 'exit'
01489     #  @return    True Forces end of loop
01490     #
01491     #  @pydoc
01492     def do_quit(self, arg):
01493         """syntax: quit""" \
01494         """-- Exit the Interactive Shell."""
01495         
01496         # Exit
01497         return True
01498         
01499         
01500     ## Exit the Interactive Shell.
01501     #
01502     #  Stop the command loop
01503     #
01504     #  @param     self This object
01505     #  @param     arg  Line entered after 'exit'
01506     #  @return    True Forces end of loop
01507     #
01508     #  @pydoc
01509     def do_exit(self, arg):
01510         """syntax: exit""" \
01511         """-- Because quit just isn't enough."""
01512         
01513         # Exit
01514         return True
01515         
01516         
01517     ## Exit the Interactive Shell.
01518     #
01519     #  Stop the command loop
01520     #
01521     #  @param     self This object
01522     #  @param     arg  Line entered after 'exit'
01523     #  @return    True Forces end of loop
01524     #
01525     #  @pydoc
01526     def do_EOF(self, arg):
01527         """syntax: ^d""" \
01528         """-- Because quit and exit just aren't enough."""
01529         
01530         # Spacer
01531         print ("exit")
01532         
01533         # Exit
01534         return True
01535     
01536 
01537 logger.debug("... clearpath.horizon.demo loaded.")
01538 
01539 
01540 
01541 
01542 ################################################################################
01543 # Script Code
01544 
01545 
01546 ## Bootstrap the basic Horizon Demo.
01547 #
01548 #  Main Program Set-up for Basic Command-Line Interface
01549 #  - Gets a list of serial ports
01550 #  - Parses Command-Line Options
01551 #  - Initializes the Horizon Interface
01552 #  - Launches CMD control Loop
01553 #  - Cleans-up after completion
01554 #
01555 #  @pre    OS must be Posix compliant or Windows NT
01556 #  @pre    Posix: Read access on /dev or full access of Serial ports
01557 #  @pre    Windows: Full access of COM ports
01558 #
01559 #  @pydoc
01560 def __main():
01561     """Bootstrap the basic Horizon Demo."""
01562     
01563     # Setup Logger
01564     while len(horizon_logger.handlers) > 0: 
01565         horizon_logger.removeHandler(horizon_logger.handlers[0])
01566     while len(logger.handlers) > 0: 
01567         logger.removeHandler(logger.handlers[0])
01568     logger_handler = logging.StreamHandler()
01569     logger_handler.setFormatter(
01570                             logging.Formatter("%(levelname)s: %(message)s"))
01571     horizon_logger.addHandler(logger_handler)
01572     horizon_logger.setLevel(logging.WARNING)
01573     logger.propagate = True
01574     
01575     # Check verbosity
01576     ver_re = re.compile(r'^-v$')
01577     for arg in sys.argv[1:]:
01578         if ver_re.match(arg):
01579             horizon_logger.setLevel(logging.INFO)
01580             logger.info("Verbose mode entered.")
01581             break
01582     
01583     # Get list of available serial ports
01584     logger.info("Looking for serial ports...")
01585     ports = []
01586     try:
01587         serial
01588         ports = utils.list_serial_ports()
01589     except OSError as ex:
01590         logging.critical(ex)
01591         sys.exit(1)
01592     except IOError as ex:
01593         logging.critical(ex)
01594         sys.exit(1)
01595     except NameError:
01596         logger.warning("Serial not supported. Please install pySerial.")
01597     if len(ports) > 0:
01598         ports.sort()
01599         logger.info("...located %d serial ports: %s" %(len(ports),
01600                                                        ', '.join(ports)))
01601         
01602     # Get Command-Line Options
01603     logger.info("Parsing command-line arguments...")
01604     usage = 'usage: python -m clearpath.horizon.demo [<device>] [options]\n\n'
01605     try:
01606         serial
01607         if len(ports) == 0: raise NameError("")
01608         usage = usage + '<device>  = <serial> | (tcp | udp):<address>\n' + \
01609                         '<serial>  = ' + ' | '.join(ports) + '\n'
01610     except NameError:
01611         usage = usage + '<device>  = (tcp | udp):<address>\n'
01612     usage = usage + '<address> = <host>:<port>\n' + \
01613                     '<host>    = computer hostname or IP address\n' + \
01614                     '<port>    = computer port number (1-65535)'
01615     desc = 'Horizon Basic Command-Line Controller: Interactively control a ' + \
01616            'Horizon device connected at <device>.'
01617     vers = 'Horizon Protocol Document version to communicate with. ' + \
01618            'Supported version(s): '
01619     ver = []
01620     p = optparse.OptionParser(usage=usage,description=desc)
01621     p.add_option('--output', '-o', action='store', type='str', metavar='FILE', 
01622                  default='/dev/null', help='Location to send subscription '\
01623                  'data. Default location (/dev/null) enables "get_waiting" '\
01624                  'command. To watch the results in real-time, use the "tail '\
01625                  '-F" command on this file.')
01626     p.add_option('--verbose', '-v', action='count', 
01627                  help='Print detailed messages. Use twice for debug messages.')
01628     p.add_option('--debug_messages', action='store', type='choice',
01629                  choices=['DEBUG','INFO','WARNING','ERROR','NONE'], 
01630                  help='Select the debug level for messages.py. '\
01631                  'INFO is useful for general debug and DEBUG is useful for' \
01632                  ' crash location detection. This flag automatically enables'\
01633                  ' verbose debug messages (-v -v) regardless of LEVEL.')
01634     p.add_option('--debug_payloads', action='store', type='choice',
01635                  choices=['DEBUG','INFO','WARNING','ERROR','NONE'], 
01636                  help='Select the debug level for payloads.py. '\
01637                  'INFO is useful for general debug and DEBUG is useful for' \
01638                  ' crash location detection. This flag automatically enables'\
01639                  ' verbose debug messages (-v -v) regardless of LEVEL.')
01640     p.add_option('--debug_protocol', action='store', type='choice',
01641                  choices=['DEBUG','INFO','WARNING','ERROR','NONE'], 
01642                  help='Select the debug level for protocol.py. '\
01643                  'INFO is useful for general debug and DEBUG is useful for' \
01644                  ' crash location detection. This flag automatically enables'\
01645                  ' verbose debug messages (-v -v) regardless of LEVEL.')
01646     p.add_option('--debug_transports', action='store', type='choice',
01647                  choices=['DEBUG','INFO','WARNING','ERROR','NONE'], 
01648                  help='Select the debug level for transports.py. '\
01649                  'INFO is useful for general debug and DEBUG is useful for' \
01650                  ' crash location detection. This flag automatically enables'\
01651                  ' verbose debug messages (-v -v) regardless of LEVEL.')
01652     p.add_option('--doc', action='store_true', 
01653                  help='Access the latest Horizon specification document ')
01654     encr = []
01655     try:
01656         Crypto.Random
01657         try:
01658             Crypto.Cipher.AES
01659             encr += ['AES']
01660         except NameError:
01661             pass
01662         try:
01663             Crypto.Cipher.Blowfish
01664             encr += ['Blowfish']
01665         except NameError:
01666             pass
01667         try:
01668             Crypto.Cipher.CAST
01669             encr += ['CAST']
01670         except NameError:
01671             pass
01672         try:
01673             Crypto.Cipher.DES
01674             encr += ['DES']
01675         except NameError:
01676             pass
01677         try:
01678             Crypto.Cipher.DES3
01679             encr += ['DES3']
01680         except NameError:
01681             pass
01682         try:
01683             Crypto.Cipher.ARC2
01684             encr += ['RC2']
01685         except NameError:
01686             pass
01687         p.add_option('--encryption', action='store', type='choice',
01688                  choices=encr, 
01689                  help='Select the encryption cipher to use for TCP '\
01690                  'communication ['+', '.join(encr)+']. '\
01691                  'Not all choices may be available; refer to '\
01692                  '`python -m clearpath.horizon.demo --doc` for details.', 
01693                  metavar='CIPHER')
01694         p.add_option('--key', action='store',type='str', metavar='KEY', 
01695                  help='The encryption key to use with --encryption. '\
01696                  'The key length is dependent upon the chosen cipher.')
01697     except NameError:
01698         pass
01699     p.add_option('--version', action='store', type='str', metavar='VER', 
01700                  default='0.0', help=vers)
01701     options, arguments = p.parse_args()
01702     logger.info("...command-line arguments parsed.")
01703     
01704     # Check for doc
01705     if options.doc:
01706         logger.info("Documentation flag found: Launching documentation...")
01707         loc = __file__.rstrip('demo.py')+'doc/'
01708         if os.system("man -l "+loc+"horizon.3") != 0:
01709             if os.system("less "+loc+"horizon.txt") != 0:
01710                 os.system("cat "+loc+"horizon.txt")
01711         sys.exit(0)
01712     
01713     # Check Number of Command-Line Arguments
01714     if len(arguments) > 1:
01715         p.print_usage()
01716         sys.stderr.write("horizon_demo.py: error: too many arguments\n")
01717         sys.exit(1)
01718         
01719     # Address Regular Expression
01720     addr_re1 = re.compile(r'^((?:[a-zA-Z0-9]+' \
01721                            '(?:[a-zA-Z0-9\\-]+[a-zA-Z0-9])?)' \
01722                            '(?:\\.(?:[a-zA-Z0-9]+(?:[a-zA-Z0-9\\-]+[a-zA-Z0-9]'\
01723                            ')?))*):([1-9][0-9]{0,4})$')
01724     addr_re2 = re.compile(r'^((?:[a-zA-Z0-9\\-]{1,63})' \
01725                            '(?:\\.(?:[a-zA-Z0-9\\-]' \
01726                            '{1,63}))*):([1-9][0-9]{0,4})$')
01727     addr_re3 = re.compile(r'^([a-zA-Z0-9\\-\\.]{1,255}):' \
01728                            '((?:6553[0-5])|(?:655' \
01729                            '[0-2][0-9])|(?:65[0-4][0-9]{2})|(?:6[0-4][0-9]{3})'\
01730                            '|(?:[1-5][0-9]{4})|(?:[1-9][0-9]{0,3}))$')
01731     
01732     # Check verbosity
01733     if options.debug_messages != None or options.debug_payloads != None or \
01734             options.debug_protocol != None or options.debug_transports != None:
01735         logger_handler.setFormatter(logging.Formatter(
01736                                         "%(name)s: %(levelname)s: %(message)s"))
01737         horizon_logger.setLevel(logging.DEBUG)
01738         logger.info("Debug mode entered.")
01739         if options.debug_messages != None:
01740             messages.logger.propagate = True
01741             if options.debug_messages == 'DEBUG':
01742                 messages.logger.setLevel(logging.DEBUG)
01743             elif options.debug_messages == 'INFO':
01744                 messages.logger.setLevel(logging.INFO)
01745             elif options.debug_messages == 'WARNING':
01746                 messages.logger.setLevel(logging.WARNING)
01747             elif options.debug_messages == 'ERROR':
01748                 messages.logger.setLevel(logging.ERROR)
01749             else:
01750                 messages.logger.propagate = False
01751         if options.debug_payloads != None:
01752             payloads.logger.propagate = True
01753             if options.debug_payloads == 'DEBUG':
01754                 payloads.logger.setLevel(logging.DEBUG)
01755             elif options.debug_payloads == 'INFO':
01756                 payloads.logger.setLevel(logging.INFO)
01757             elif options.debug_payloads == 'WARNING':
01758                 payloads.logger.setLevel(logging.WARNING)
01759             elif options.debug_payloads == 'ERROR':
01760                 payloads.logger.setLevel(logging.ERROR)
01761             else:
01762                 payloads.logger.propagate = False
01763         if options.debug_protocol != None:
01764             protocol.logger.propagate = True
01765             if options.debug_protocol == 'DEBUG':
01766                 protocol.logger.setLevel(logging.DEBUG)
01767             elif options.debug_protocol == 'INFO':
01768                 protocol.logger.setLevel(logging.INFO)
01769             elif options.debug_protocol == 'WARNING':
01770                 protocol.logger.setLevel(logging.WARNING)
01771             elif options.debug_protocol == 'ERROR':
01772                 protocol.logger.setLevel(logging.ERROR)
01773             else:
01774                 protocol.logger.propagate = False
01775         if options.debug_transports != None:
01776             transports.logger.propagate = True
01777             if options.debug_transports == 'DEBUG':
01778                 transports.logger.setLevel(logging.DEBUG)
01779             elif options.debug_transports == 'INFO':
01780                 transports.logger.setLevel(logging.INFO)
01781             elif options.debug_transports == 'WARNING':
01782                 transports.logger.setLevel(logging.WARNING)
01783             elif options.debug_transports == 'ERROR':
01784                 transports.logger.setLevel(logging.ERROR)
01785             else:
01786                 transports.logger.propagate = False
01787     elif options.verbose != None and options.verbose > 1:
01788         horizon_logger.setLevel(logging.DEBUG)
01789         logger.info("Debug mode entered.")
01790 
01791     transport = None 
01792     transport_args = {}
01793 
01794     if len(arguments) == 0:
01795         # Autodetect
01796         transport = transports.Serial.autodetect
01797 
01798     else:
01799         # Otherwise, try to treat it as a serial port
01800         transport = transports.Serial
01801         transport_args = {'port':arguments[0]}
01802         logger.info("Using serial port %s." % arguments[0])
01803             
01804     # Invalid Device
01805     if transport == None:
01806         p.print_usage()
01807         sys.stderr.write("error: invalid device specified\n")
01808         sys.exit(1)
01809    
01810     # Check for output
01811     output = ''
01812     if options.output != '/dev/null' and (os.path.isdir(output) or \
01813             (os.path.isfile(output) and not os.access(output, os.R_OK|os.W_OK))\
01814             or (not os.path.lexists(output) and not os.access(
01815                 os.path.dirname(os.path.abspath(output)), os.R_OK|os.W_OK))):
01816         p.print_usage()
01817         ex = "error: option --output: inaccessible value"
01818         sys.stderr.write(ex + '\n')
01819         sys.exit(1)
01820     else:
01821         if options.output != '/dev/null': output = options.output
01822         if output != '':
01823             logger.info("Saving subscription data to '%s'." % output)
01824         
01825     # Try to initialize Horizon
01826     horizon = None
01827     command = None
01828     err = 0
01829     try:
01830         
01831         # Init Horizon
01832         logger.debug("Initializing Horizon Interface...")
01833         horizon = Horizon(transport=transport,
01834                           transport_args=transport_args)
01835         logger.debug("...Horizon Interface Initialized.")
01836         
01837         # open the communications
01838         logger.debug("Opening Communications...")
01839         horizon.open()
01840         logger.debug("...Communications Open.")
01841         
01842         # Start command Loop
01843         command = HorizonDemo(port=str(horizon), 
01844                               horizon=horizon, 
01845                               output=output)
01846         logger.debug("Entering shell...")
01847         histfile = os.path.join(os.environ["HOME"], ".cprcli_history")
01848         try:
01849             readline.read_history_file(histfile)
01850         except IOError:
01851             # No history, which is cool.
01852             pass
01853 
01854         readline.set_history_length(100)
01855         command.cmdloop()
01856         readline.write_history_file(histfile)
01857         logger.debug("...shell exited.")
01858 
01859  #   except utils.TransportError as ex:
01860  #       logger.error("Unable to autodetect serial Horizon device.")
01861  #       err = 1
01862    
01863 #    except IOError as ex:
01864 #        logger.error("Connection Failed: %s", ex)
01865 #        logger.debug("...shell exited.")
01866 #        err = 1
01867     
01868     except KeyboardInterrupt:
01869         print ("") # Only catch to prevent program closing before clean-up
01870         logger.debug("...shell exited.")
01871     
01872     #except Exception as ex:
01873     #    logging.critical(ex)
01874     #    logger.debug("...shell exited.")
01875     #    err = 1
01876                
01877     # Cleanup
01878     finally:
01879         if horizon != None:
01880             logger.debug("Closing Communications...")
01881             horizon.close()
01882             logger.debug("...Communications Closed.")
01883         
01884     # Finish Program
01885     logger.info("Program Complete!")
01886     sys.exit(err)
01887 
01888 
01889 
01890 
01891 ################################################################################
01892 # Script
01893 
01894 
01895 # Check if run as a script
01896 if __name__ == "__main__":
01897     
01898     # Run the main method
01899     __main()
01900 
01901     # Exit Bad - Should not reach so if it does: error
01902     sys.exit(1)


clearpath_base
Author(s): Mike Purvis
autogenerated on Sun Oct 5 2014 22:52:08