$search
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)