diff --git a/applog.py b/applog.py index a93021c..f61cc31 100644 --- a/applog.py +++ b/applog.py @@ -29,7 +29,7 @@ # # global variables -# +# gFileSessionLog = None gFileLifetimeLog = None gLoggingFlags = 0 @@ -53,17 +53,17 @@ def applog_init(loggingFlags=APPLOGF_LEVEL_INFORMATIONAL | APPLOGF_LEVEL_ERROR, print("Unable to open/create logfile \"{:s}\". {:s}".format(lifetimeLogFilename, str(e))) return e.errno return 0 - + # # set new logging flags -# +# def applog_set_loggingFlags(newLoggingFlags): global gLoggingFlags - gLoggingFlags = newLoggingFlags - + gLoggingFlags = newLoggingFlags + # # shutdown this module -# +# def applog_shutdown(): try: if gFileSessionLog: @@ -72,7 +72,7 @@ def applog_shutdown(): gFileSessionLog.close() except IOError as e: print("Unable to close logfile. {:s}".format(str(e))) - + # # Log message with specified level # @@ -89,31 +89,31 @@ def applog(str, flags=APPLOGF_LEVEL_INFORMATIONAL): print(timeStampStr + str, file=gFileSessionLog) if gFileLifetimeLog: print(timeStampStr + str, file=gFileLifetimeLog) - - + + # # Logging wrapper functions for each level # -def applog_i(s): +def applog_i(s): applog(s, APPLOGF_LEVEL_INFORMATIONAL) -def applog_v(s): +def applog_v(s): applog(s, APPLOGF_LEVEL_VERBOSE) -def applog_w(s): +def applog_w(s): applog(s, APPLOGF_LEVEL_WARNING) -def applog_e(s): +def applog_e(s): applog(s, APPLOGF_LEVEL_ERROR) -def applog_d(s): +def applog_d(s): applog(s, APPLOGF_LEVEL_DEBUG) - + # # no-console equivalents -# -def applog_i_nc(s): +# +def applog_i_nc(s): applog(s, APPLOGF_LEVEL_INFORMATIONAL | APPLOGF_DONT_WRITE_TO_CONSOLE) -def applog_d_nc(s): +def applog_d_nc(s): applog(s, APPLOGF_LEVEL_DEBUG | APPLOGF_DONT_WRITE_TO_CONSOLE) - - + + # # Logging check-enabled functions, used to avoid # performance penalty of generating log message diff --git a/canomate.py b/canomate.py index b625b89..e8f0f25 100644 --- a/canomate.py +++ b/canomate.py @@ -44,10 +44,10 @@ # default values DEFAULT_CCAPI_HTTP_PORT = 8080 DEFAULT_CCAPI_TIMEOUT_SECS = 5.0 -DEFAULT_CCAPI_DOWNLOAD_TIMEOUT_SECS = 0 +DEFAULT_CCAPI_DOWNLOAD_TIMEOUT_SECS = 0 DEFAULT_CCAPI_TRANSPORT_ERROR_RETRIES = 2 DEFAULT_CCAPI_CMD_ERROR_RETRIES = 2 -DEFAULT_CCAPI_MAX_BUSY_RETRY_SECS = 10 +DEFAULT_CCAPI_MAX_BUSY_RETRY_SECS = 10 DEFAULT_CCAPI_RETRY_DELAY_SECS = 0 DEFAULT_EXIT_ON_CMD_ERR = "Yes" @@ -96,9 +96,9 @@ def __init__(self): self.appDataDir = None # directory where our internal data/log files go self.args = None # dictionary of command-line arguments (generated by argparse) self.mainAutomationGroup = None # first/container group for automation operations - self.noYes = ['no', 'yes'] # boolean conversion to no/yes + self.noYes = ['no', 'yes'] # boolean conversion to no/yes self.offOn = ['off', 'on'] # boolean conversion to off/on - + # # Class for spwaning a child app including command-line parameters, with # the output copied to a caller-specified file @@ -119,7 +119,7 @@ def createArgListFromArgStr(argStr): # @staticmethod def execute(executableNameIncludingPath, cmdLineAsStr, fWaitForCompletion = True, outputFilename=None, outputFileWriteMethod=None, msgDuringExec=""): - + # build list containing all command-line parameters, the first of which is the executable we'll be spanwing cmdLineAsStr = executableNameIncludingPath + ' ' + cmdLineAsStr cmdLineArgArray = RunExecutable.createArgListFromArgStr(cmdLineAsStr) @@ -137,7 +137,7 @@ def execute(executableNameIncludingPath, cmdLineAsStr, fWaitForCompletion = True try: with open(stdoutFilename, "w") as stdoutFile, open(stderrFilename, "w") as stderrFile: processRunResult = subprocess.run(cmdLineArgArray, stdout=stdoutFile, stderr=stderrFile) - except Exception as e: + except Exception as e: applog_e("RunExecutable: Launch failed - \"{:s}\"".format(str(e))) return (False, ERRNO_RUN_EXECUTABLE_LAUNCH_FAILED) if msgDuringExec: @@ -154,15 +154,15 @@ def execute(executableNameIncludingPath, cmdLineAsStr, fWaitForCompletion = True if os.path.getmtime(outputFilename) >= g.appStartTime: # file last modification is equal to or newer than our app start time, meaning # we've already written to it once this session. use append instead - outputFileOpenModeStr = "a" + outputFileOpenModeStr = "a" elif outputFileWriteMethod == "append": - outputFileOpenModeStr = "a" + outputFileOpenModeStr = "a" else: assert outputFileWriteMethod == "overwrite" try: with open(outputFilename, outputFileOpenModeStr) as outputFile, open(stdoutFilename, "r") as stdoutFile, open(stderrFilename, "r") as stderrFile: outputFile.write(stdoutFile.read()) outputFile.write(stderrFile.read()) - except Exception as e: + except Exception as e: applog_e("RunExecutable: Error copying RunExecutable output to \"{:s}\" {:s}".format(outputFilename, str(e))) return (False, ERRNO_RUN_EXECUTABLE_OUTPUT_COPY_FAILED) @@ -177,7 +177,7 @@ def execute(executableNameIncludingPath, cmdLineAsStr, fWaitForCompletion = True applog_e("RunExecutable: Launch failed - \"{:s}\"".format(str(e))) return (False, ERRNO_RUN_EXECUTABLE_LAUNCH_FAILED) return (True, ERRNO_OK) - + # # base class for automation operations. Each derived class corresponds to a user-specified automation # op, which is specified on either the command line via --op or inside a file via --opfile @@ -211,7 +211,7 @@ def __init__(self, paramDict): # the keys for these params so we don't interpret them as invalid in self.unusedParamDictKeys; # they only exist in the group to support the inheritance into the child ops of the group # - paramDict.pop('repeatcount', None) + paramDict.pop('repeatcount', None) paramDict.pop('delayafter', None) self.repeatCount = 1 self.delayAfterSecs = 0 @@ -244,8 +244,8 @@ def isGroupOp(self): return self.getClassSuffix() == 'Group' def execute(self): - raise AssertionError("No execute() method in AutomationOp base class") - + raise AssertionError("No execute() method in AutomationOp base class") + # # returns this instance's class name. Example: "AutomationOp_Delay" # @@ -253,12 +253,12 @@ def getClassSuffix(self): return AutomationOp.getAutomationOpClassSuffix(type(self).__name__) # - # returns the suffix of the instance's class name. Example: "Delay" for AutomationOp_Delay + # returns the suffix of the instance's class name. Example: "Delay" for AutomationOp_Delay # @staticmethod def getAutomationOpClassSuffix(fullClassName): return fullClassName[fullClassName.find('_')+1:] - + # # Converts a string value for key into a boolean # @param paramDict Dictionary containing keys:values @@ -285,9 +285,9 @@ def getDictValueAsBool(self, paramDict, key, default=False, fRequireValue=False) exit(ERRNO_CANT_CONVERT_BOOL_VAL) if fRequireValue: applog_e("Missing required parameter '{:s}' for op '{:s}'".format(key, self.getClassSuffix())) - exit(ERRNO_MISSING_AUTOMATION_PARAM) + exit(ERRNO_MISSING_AUTOMATION_PARAM) return default - + # # Converts a string value for key into a scalar value (int or float) # @param paramDict Dictionary containing keys:values @@ -307,16 +307,16 @@ def getDictValueAsScalar(self, paramDict, key, default=None, scalarClass=int, fR except ValueError: applog_e("For op '{:s}', unable to convert value '{:s}' for parameter '{:s}' to a {:s}".format(self.getClassSuffix(), keyStr, key, type(scalarClass).__name__)) exit(ERRNO_CANT_CONVERT_INT_VAL) - return val + return val if fRequiredValue: applog_e("Missing required parameter '{:s}' for op '{:s}'".format(key, self.getClassSuffix())) exit(ERRNO_MISSING_AUTOMATION_PARAM) return default - + # # Gets a string value from the dictionary, applying a default if none is provided # @param paramDict Dictionary containing keys:values - # @param key Key holding the desired string value + # @param key Key holding the desired string value # @param default Default value if the key is not found in the dictionary. # Specify None if the key must exist - if the key is not found the app exits # with a message @@ -336,11 +336,11 @@ def getDictValueAsStr(self, paramDict, key, default=None, fRequiredValue=False): applog_e("Missing required parameter '{:s}' for op '{:s}'".format(key, self.getClassSuffix())) exit(ERRNO_MISSING_AUTOMATION_PARAM) return default - + # - # Gets a string value from the dictionary, matching it to a predefined set of choices + # Gets a string value from the dictionary, matching it to a predefined set of choices # @param paramDict Dictionary containing keys:values - # @param key Key holding the desired string value + # @param key Key holding the desired string value # @param listChoices List containing valid choices. Comparison is case-insensitive # @param default Default value if the key is not found in the dictionary. # Specify None if the key must exist - if the key is not found the app exits @@ -365,7 +365,7 @@ def getDictValueStrChoice(self, paramDict, key, listChoices, default=None, fRequ # be a simple value (seconds) or a time-code in the form of # hh:mm:ss or mm:ss. # @param paramDict Dictionary containing keys:values - # @param key Key holding the desired string value + # @param key Key holding the desired string value # @param default Default value if the key is not found in the dictionary. # Specify None if the key must exist - if the key is not found the app exits # with a message @@ -380,7 +380,7 @@ def getDictValueAsSeconds(self, paramDict, key, default=None, fRequiredValue=Fal except Exception as e: applog_e("For op '{:s}', unable to convert time value '{:s}' for parameter '{:s}' to a valid time".format(self.getClassSuffix(), val, key)) exit(ERRNO_INVALID_TIME_VALUE_SPECIFIED) - return seconds + return seconds if fRequiredValue: applog_e("Missing required parameter '{:s}' for op '{:s}'".format(key, self.getClassSuffix())) exit(ERRNO_MISSING_AUTOMATION_PARAM) @@ -389,12 +389,12 @@ def getDictValueAsSeconds(self, paramDict, key, default=None, fRequiredValue=Fal def printGetResponse(self, resp, respKeyVal='value'): fListAbilities = self.fListAbilities if hasattr(self, 'fListAbilities') else False if resp['success']: - availableStr = " Available: {}".format(resp['ability']) if fListAbilities else "" + availableStr = " Available: {}".format(resp['ability']) if fListAbilities else "" infoStr = "{:s}{:s}".format(resp[respKeyVal], availableStr) else: infoStr = "N/A" applog_i("{:s}: {:s}".format(self.getClassSuffix(), infoStr)) - + class AutomationOp_Delay(AutomationOp): def __init__(self, paramDict): self.delaySecs = self.getDictValueAsSeconds(paramDict, 'delaytime', default=None, fRequiredValue=True) @@ -409,10 +409,10 @@ def __init__(self, paramDict): def execute(self): if self.fBeep: beep() - applog_i_nc("{:s}: Press to continue...".format(self.getClassSuffix())) - print("{:s}: Press to continue...".format(self.getClassSuffix()), end='') + applog_i_nc("{:s}: Press to continue...".format(self.getClassSuffix())) + print("{:s}: Press to continue...".format(self.getClassSuffix()), end='') input() # todo-todo: Flush input before checking. sys.stdin.flush() doesn't work - + class AutomationOp_Beep(AutomationOp): def __init__(self, paramDict): return super().__init__(paramDict) @@ -421,7 +421,7 @@ def execute(self): class AutomationOp_PrintMovieMode(AutomationOp): def __init__(self, paramDict): - return super().__init__(paramDict) + return super().__init__(paramDict) def execute(self): resp = ccapi.getMovieMode(self.retryInfo) self.printGetResponse(resp, respKeyVal='status') @@ -429,7 +429,7 @@ def execute(self): class AutomationOp_PrintMovieQuality(AutomationOp): def __init__(self, paramDict): self.fListAbilities = self.getDictValueAsBool(paramDict, 'listavailable', default=False) - return super().__init__(paramDict) + return super().__init__(paramDict) def execute(self): resp = ccapi.getMovieQuality(self.retryInfo) self.printGetResponse(resp) @@ -437,42 +437,42 @@ def execute(self): class AutomationOp_SetMovieQuality(AutomationOp): def __init__(self, paramDict): self.movieQualityStr = self.getDictValueAsStr(paramDict, 'moviequality', None, fRequiredValue=True) - return super().__init__(paramDict) + return super().__init__(paramDict) def execute(self): - applog_i("{:s}: {:s}".format(self.getClassSuffix(), self.movieQualityStr)) + applog_i("{:s}: {:s}".format(self.getClassSuffix(), self.movieQualityStr)) ccapi.setMovieQuality(self.retryInfo, self.movieQualityStr) - + class AutomationOp_EnterMovieMode(AutomationOp): def __init__(self, paramDict): - return super().__init__(paramDict) + return super().__init__(paramDict) def execute(self): applog_i("{:s}".format(self.getClassSuffix())) ccapi.setMovieMode(self.retryInfo, True) - + class AutomationOp_ExitMovieMode(AutomationOp): def __init__(self, paramDict): - return super().__init__(paramDict) + return super().__init__(paramDict) def execute(self): applog_i("{:s}".format(self.getClassSuffix())) ccapi.setMovieMode(self.retryInfo, False) class AutomationOp_StartMovieRecord(AutomationOp): def __init__(self, paramDict): - return super().__init__(paramDict) + return super().__init__(paramDict) def execute(self): applog_i("{:s}".format(self.getClassSuffix())) ccapi.startStopMovieRecord(self.retryInfo, True) class AutomationOp_StopMovieRecord(AutomationOp): def __init__(self, paramDict): - return super().__init__(paramDict) + return super().__init__(paramDict) def execute(self): applog_i("{:s}".format(self.getClassSuffix())) ccapi.startStopMovieRecord(self.retryInfo, False) class AutomationOp_PrintCameraInfo(AutomationOp): def __init__(self, paramDict): - return super().__init__(paramDict) + return super().__init__(paramDict) def execute(self): cameraInfo = ccapi.getCameraInfo(self.retryInfo) if cameraInfo['success']: @@ -482,39 +482,39 @@ def execute(self): cameraInfoStr = "N/A" infoStr = cameraInfoStr applog_i("{:s}: {:s}".format(self.getClassSuffix(), infoStr)) - + class AutomationOp_PrintTemperatureStatus(AutomationOp): def __init__(self, paramDict): - return super().__init__(paramDict) + return super().__init__(paramDict) def execute(self): resp = ccapi.getTemperatureStatus(self.retryInfo) infoStr = resp['status'] if resp['success'] else "N/A" applog_i("{:s}: {:s}".format(self.getClassSuffix(), infoStr)) - - + + class AutomationOp_TakePhoto(AutomationOp): def __init__(self, paramDict): self.fPerformAF = self.getDictValueAsBool(paramDict, 'autofocus', default=False) - return super().__init__(paramDict) + return super().__init__(paramDict) def execute(self): applog_i("{:s}".format(self.getClassSuffix())) ccapi.takeStillPhoto(self.retryInfo, self.fPerformAF) - + class AutomationOp_PrintBatteryInfo(AutomationOp): def __init__(self, paramDict): - return super().__init__(paramDict) + return super().__init__(paramDict) def execute(self): - resp = ccapi.getBatteryInfo(self.retryInfo) + resp = ccapi.getBatteryInfo(self.retryInfo) if resp['success']: infoStr = "Name: {:s}, Kind: {:s}, Level: {:s}, Quality: {:s}".format( resp['name'], resp['kind'], resp['level'], resp['quality']) else: infoStr = "N/A" applog_i("{:s}: {:s}".format(self.getClassSuffix(), infoStr)) - + class AutomationOp_PrintShootingSettings(AutomationOp): def __init__(self, paramDict): - return super().__init__(paramDict) + return super().__init__(paramDict) def execute(self): resp = ccapi.getShootingSettings(self.retryInfo) if resp['success']: @@ -524,27 +524,27 @@ def execute(self): else: infoStr = "N/A" applog_i("{:s}: {:s}".format(self.getClassSuffix(), infoStr)) - + class AutomationOp_GetInfoByUrl(AutomationOp): def __init__(self, paramDict): self.url = self.getDictValueAsStr(paramDict, 'url', default=None, fRequiredValue=True) self.fullUrl = ccapi.genFullUrl(self.url, verPrefix=None) - return super().__init__(paramDict) + return super().__init__(paramDict) def execute(self): resp = ccapi.get(self.retryInfo, self.fullUrl) infoStr = "'{:s}': {}".format(self.url, resp) if resp['success'] else "N/A" applog_i("{:s}: {:s}".format(self.getClassSuffix(), infoStr)) -class AutomationOp_DisconnectWireless(AutomationOp): +class AutomationOp_DisconnectWireless(AutomationOp): def __init__(self, paramDict): - return super().__init__(paramDict) + return super().__init__(paramDict) def execute(self): applog_i("{:s}".format(self.getClassSuffix())) return ccapi.disconnectWirelss(self.retryInfo) - + class AutomationOp_PrintAPI(AutomationOp): def __init__(self, paramDict): - return super().__init__(paramDict) + return super().__init__(paramDict) def execute(self): resp = ccapi.getApiInfo(self.retryInfo) infoStr = "{}".format(resp) if resp['success'] else "N/A" @@ -556,21 +556,21 @@ def __init__(self, paramDict): self.fullUrl = ccapi.genFullUrl(self.url) self.outputDir = self.getDictValueAsStr(paramDict, 'outputdir', g.args['outputdir']) self.ifExists = self.getDictValueAsStr(paramDict, 'ifexists', g.args['ifexists']) - return super().__init__(paramDict) + return super().__init__(paramDict) def execute(self): - + AutomationOp.lastDownloadedFiles_FullUrl = None - AutomationOp.lastDownloadedFiles_LocalPath = None - + AutomationOp.lastDownloadedFiles_LocalPath = None + # # formualte the full local path+filename to hold the file and handle # the case of what to do if the local file already exists. # filenameFromUrl = CCAPI.getFilenameFromUrl(self.fullUrl) - localFileFullPath = os.path.join(self.outputDir, filenameFromUrl) + localFileFullPath = os.path.join(self.outputDir, filenameFromUrl) localFileFullPath = checkWritingNewFileExists(localFileFullPath, self.ifExists) - - if localFileFullPath: + + if localFileFullPath: # # file didn't exist or we're configured to generate a unique name. in either case, # 'localFileFullPath' holds the local path+filename we're to write the file to @@ -580,11 +580,11 @@ def execute(self): AutomationOp.lastDownloadedFiles_FullUrl = [self.fullUrl] AutomationOp.lastDownloadedFiles_LocalPath = [localFileFullPath] applog_i("{:s} From {:s} stored at {:s} [download time = {:.2f} (s)]".format(self.getClassSuffix(), self.url, localFileFullPath, downloadTimeSecs)) - + class AutomationOp_GetPendingEvents(AutomationOp): def __init__(self, paramDict): self.fPrintEvents = self.getDictValueAsBool(paramDict, 'printevents', default=False) - return super().__init__(paramDict) + return super().__init__(paramDict) def execute(self): resp = ccapi.getPolledUpdate(self.retryInfo) if self.fPrintEvents: @@ -592,12 +592,12 @@ def execute(self): else: infoStr = "Cleared-only (not printed)" if resp['success'] else "N/A" applog_i("{:s}: {:s}".format(self.getClassSuffix(), infoStr)) - + class AutomationOp_WaitForNewFilesOnCamera(AutomationOp): def __init__(self, paramDict): self.maxWaitSecs = self.getDictValueAsScalar(paramDict, 'maxwaittime', default=None, fRequiredValue=True) - return super().__init__(paramDict) - def execute(self): + return super().__init__(paramDict) + def execute(self): AutomationOp.lastFilesFoundDuringPolling = None newFileList = ccapi.pollForNewFilesOnCamera(self.retryInfo, self.maxWaitSecs) applog_i("{:s}: {}".format(self.getClassSuffix(), newFileList)) @@ -607,27 +607,27 @@ class AutomationOp_DownloadNewFilesPolled(AutomationOp): def __init__(self, paramDict): self.outputDir = self.getDictValueAsStr(paramDict, 'outputdir', g.args['outputdir']) self.ifExists = self.getDictValueAsStr(paramDict, 'ifexists', g.args['ifexists']) - return super().__init__(paramDict) + return super().__init__(paramDict) def execute(self): - + AutomationOp.lastDownloadedFiles_FullUrl = [] - AutomationOp.lastDownloadedFiles_LocalPath = [] - + AutomationOp.lastDownloadedFiles_LocalPath = [] + if AutomationOp.lastFilesFoundDuringPolling == None: applog_i("{:s}: No files from previous WaitForNewFilesOnCamera".format(self.getClassSuffix())) return applog_i("{:s}: File(s):".format(self.getClassSuffix())) for index, fullUrl in enumerate(AutomationOp.lastFilesFoundDuringPolling): - + # # formualte the full local path+filename to hold the file and handle # the case of what to do if the local file already exists. # filenameFromUrl = CCAPI.getFilenameFromUrl(fullUrl) - localFileFullPath = os.path.join(self.outputDir, filenameFromUrl) + localFileFullPath = os.path.join(self.outputDir, filenameFromUrl) localFileFullPath = checkWritingNewFileExists(localFileFullPath, self.ifExists) - - if localFileFullPath: + + if localFileFullPath: # # file didn't exist or we're configured to generate a unique name. in either case, # 'localFileFullPath' holds the local path+filename we're to write the file to @@ -641,18 +641,18 @@ def execute(self): if len(AutomationOp.lastDownloadedFiles_FullUrl) == 0: # if no files were downloaded then reset our last-downloaded vars to None (instead of an empty list) AutomationOp.lastDownloadedFiles_FullUrl = None - AutomationOp.lastDownloadedFiles_LocalPath = None + AutomationOp.lastDownloadedFiles_LocalPath = None class AutomationOp_GetInfoOnNewFilesPolled(AutomationOp): def __init__(self, paramDict): - return super().__init__(paramDict) + return super().__init__(paramDict) def execute(self): if AutomationOp.lastFilesFoundDuringPolling == None: applog_i("{:s}: No files from previous WaitForNewFilesOnCamera".format(self.getClassSuffix())) return applog_i("{:s}: File(s):".format(self.getClassSuffix())) for index, fullUrl in enumerate(AutomationOp.lastFilesFoundDuringPolling): - resp = ccapi.getFileInfo(self.retryInfo, fullUrl) + resp = ccapi.getFileInfo(self.retryInfo, fullUrl) if resp['success']: infoStr = "Size: {:s}, Duration: {:s}, Date: {:s}".format(getHumanReadableSize(int(resp['filesize'])), str(resp['playtime']) + ' (s)' if type(resp['playtime']).__name__ == 'int' else 'N/A', @@ -660,7 +660,7 @@ def execute(self): else: infoStr = "N/A" applog_i("File #{:d}: Name: {:s} {:s}".format(index+1, CCAPI.getFilenameFromUrl(fullUrl), infoStr)) - + class AutomationOp_RunExecutable(AutomationOp): def __init__(self, paramDict): self.executable = self.getDictValueAsStr(paramDict, 'executable', None, fRequiredValue=True) @@ -673,11 +673,11 @@ def __init__(self, paramDict): if 'outputfile' in paramDict or 'writemode' in paramDict or 'assertexitcode' in paramDict: applog_i("{:s}: outputfile/writemode/assertexitcode not valid when 'waitforcompletion' is false".format(self.getClassSuffix())) # don't exit, so that the line # of the above can be reported before we exit due to unusedparams - return super().__init__(paramDict) + return super().__init__(paramDict) self.outputFilename = self.getDictValueAsStr(paramDict, 'outputfile', "") self.outputFileWriteMethod = self.getDictValueStrChoice(paramDict, 'writemode', ['append', 'overwrite', 'overwrite_first_time'], 'append') self.assertExitCode = self.getDictValueAsScalar(paramDict, 'assertexitcode', None, int, fRequiredValue=False) - return super().__init__(paramDict) + return super().__init__(paramDict) def execute(self): args = self.args if self.fAppendLastDownloadedFilenames and AutomationOp.lastDownloadedFiles_LocalPath != None: @@ -692,18 +692,18 @@ def execute(self): if self.assertExitCode != None and _errno != self.assertExitCode: applog_i("{:s}: Terminating due exit code of {:d} not matching 'assertExitCode' value of {:d}".format(self.getClassSuffix(), _errno, self.assertExitCode)) exit(ERRNO_RUN_EXECUTABLE_EXIT_MISMATCH_RET_CODE) - + class AutomationOp_PrintMessageToLog(AutomationOp): def __init__(self, paramDict): self.message = self.getDictValueAsStr(paramDict, 'message', None, fRequiredValue=True) - return super().__init__(paramDict) + return super().__init__(paramDict) def execute(self): applog_i("{:s}: {:s}".format(self.getClassSuffix(), self.message)) class AutomationOp_ExitApp(AutomationOp): def __init__(self, paramDict): self.exitCode = self.getDictValueAsScalar(paramDict, 'exitcode', 0, int) - return super().__init__(paramDict) + return super().__init__(paramDict) def execute(self): applog_i("{:s}: exitcode={:d}".format(self.getClassSuffix(), self.exitCode)) exit(self.exitCode) @@ -711,23 +711,23 @@ def execute(self): class AutomationOp_PrintAperture(AutomationOp): def __init__(self, paramDict): self.fListAbilities = self.getDictValueAsBool(paramDict, 'listavailable', default=False) - return super().__init__(paramDict) + return super().__init__(paramDict) def execute(self): resp = ccapi.getAperture(self.retryInfo) - self.printGetResponse(resp) + self.printGetResponse(resp) class AutomationOp_SetAperture(AutomationOp): def __init__(self, paramDict): self.apertureStr = self.getDictValueAsStr(paramDict, 'aperture', None, fRequiredValue=True) - return super().__init__(paramDict) + return super().__init__(paramDict) def execute(self): - applog_i("{:s}: {:s}".format(self.getClassSuffix(), self.apertureStr)) + applog_i("{:s}: {:s}".format(self.getClassSuffix(), self.apertureStr)) ccapi.setAperture(self.retryInfo, self.apertureStr) class AutomationOp_PrintShutterSpeed(AutomationOp): def __init__(self, paramDict): self.fListAbilities = self.getDictValueAsBool(paramDict, 'listavailable', default=False) - return super().__init__(paramDict) + return super().__init__(paramDict) def execute(self): resp = ccapi.getShutterSpeed(self.retryInfo) self.printGetResponse(resp) @@ -735,15 +735,15 @@ def execute(self): class AutomationOp_SetShutterSpeed(AutomationOp): def __init__(self, paramDict): self.shutterSpeedStr = self.getDictValueAsStr(paramDict, 'shutterspeed', None, fRequiredValue=True) - return super().__init__(paramDict) + return super().__init__(paramDict) def execute(self): - applog_i("{:s}: {:s}".format(self.getClassSuffix(), self.shutterSpeedStr)) + applog_i("{:s}: {:s}".format(self.getClassSuffix(), self.shutterSpeedStr)) ccapi.setShutterSpeed(self.retryInfo, self.shutterSpeedStr) class AutomationOp_PrintIso(AutomationOp): def __init__(self, paramDict): self.fListAbilities = self.getDictValueAsBool(paramDict, 'listavailable', default=False) - return super().__init__(paramDict) + return super().__init__(paramDict) def execute(self): resp = ccapi.getIso(self.retryInfo) self.printGetResponse(resp) @@ -751,15 +751,15 @@ def execute(self): class AutomationOp_SetIso(AutomationOp): def __init__(self, paramDict): self.isoStr = self.getDictValueAsStr(paramDict, 'iso', None, fRequiredValue=True) - return super().__init__(paramDict) + return super().__init__(paramDict) def execute(self): - applog_i("{:s}: {:s}".format(self.getClassSuffix(), self.isoStr)) + applog_i("{:s}: {:s}".format(self.getClassSuffix(), self.isoStr)) ccapi.setIso(self.retryInfo, self.isoStr) class AutomationOp_PrintExposureCompensation(AutomationOp): def __init__(self, paramDict): self.fListAbilities = self.getDictValueAsBool(paramDict, 'listavailable', default=False) - return super().__init__(paramDict) + return super().__init__(paramDict) def execute(self): resp = ccapi.getExposureCompensation(self.retryInfo) self.printGetResponse(resp) @@ -767,15 +767,15 @@ def execute(self): class AutomationOp_SetExposureCompensation(AutomationOp): def __init__(self, paramDict): self.exposureCompensationStr = self.getDictValueAsStr(paramDict, 'exposurecompensation', None, fRequiredValue=True) - return super().__init__(paramDict) + return super().__init__(paramDict) def execute(self): - applog_i("{:s}: {:s}".format(self.getClassSuffix(), self.exposureCompensationStr)) + applog_i("{:s}: {:s}".format(self.getClassSuffix(), self.exposureCompensationStr)) ccapi.setExposureCompensation(self.retryInfo, self.exposureCompensationStr) class AutomationOp_PrintWhiteBalance(AutomationOp): def __init__(self, paramDict): self.fListAbilities = self.getDictValueAsBool(paramDict, 'listavailable', default=False) - return super().__init__(paramDict) + return super().__init__(paramDict) def execute(self): resp = ccapi.getWhiteBalance(self.retryInfo) self.printGetResponse(resp) @@ -783,14 +783,14 @@ def execute(self): class AutomationOp_SetWhiteBalance(AutomationOp): def __init__(self, paramDict): self.whiteBalanceStr = self.getDictValueAsStr(paramDict, 'whitebalance', None, fRequiredValue=True) - return super().__init__(paramDict) + return super().__init__(paramDict) def execute(self): - applog_i("{:s}: {:s}".format(self.getClassSuffix(), self.whiteBalanceStr)) + applog_i("{:s}: {:s}".format(self.getClassSuffix(), self.whiteBalanceStr)) ccapi.setWhiteBalance(self.retryInfo, self.whiteBalanceStr) class AutomationOp_PrintShootingModeDial(AutomationOp): def __init__(self, paramDict): - return super().__init__(paramDict) + return super().__init__(paramDict) def execute(self): resp = ccapi.getShootingModeDial(self.retryInfo) self.printGetResponse(resp) @@ -798,7 +798,7 @@ def execute(self): class AutomationOp_PrintDriveMode(AutomationOp): def __init__(self, paramDict): self.fListAbilities = self.getDictValueAsBool(paramDict, 'listavailable', default=False) - return super().__init__(paramDict) + return super().__init__(paramDict) def execute(self): resp = ccapi.getDriveMode(self.retryInfo) self.printGetResponse(resp) @@ -806,14 +806,14 @@ def execute(self): class AutomationOp_SetDriveMode(AutomationOp): def __init__(self, paramDict): self.driveModeStr = self.getDictValueAsStr(paramDict, 'drivemode', None, fRequiredValue=True) - return super().__init__(paramDict) + return super().__init__(paramDict) def execute(self): - applog_i("{:s}: {:s}".format(self.getClassSuffix(), self.driveModeStr)) + applog_i("{:s}: {:s}".format(self.getClassSuffix(), self.driveModeStr)) ccapi.setDriveMode(self.retryInfo, self.driveModeStr) class AutomationOp_PrintAfOperation(AutomationOp): def __init__(self, paramDict): - return super().__init__(paramDict) + return super().__init__(paramDict) def execute(self): resp = ccapi.getAfOperation(self.retryInfo) self.printGetResponse(resp) @@ -821,7 +821,7 @@ def execute(self): class AutomationOp_PrintAfMethod(AutomationOp): def __init__(self, paramDict): self.fListAbilities = self.getDictValueAsBool(paramDict, 'listavailable', default=False) - return super().__init__(paramDict) + return super().__init__(paramDict) def execute(self): resp = ccapi.getAfMethod(self.retryInfo) self.printGetResponse(resp) @@ -829,15 +829,15 @@ def execute(self): class AutomationOp_SetAfMethod(AutomationOp): def __init__(self, paramDict): self.afMethodStr = self.getDictValueAsStr(paramDict, 'afmethod', None, fRequiredValue=True) - return super().__init__(paramDict) + return super().__init__(paramDict) def execute(self): - applog_i("{:s}: {:s}".format(self.getClassSuffix(), self.afMethodStr)) + applog_i("{:s}: {:s}".format(self.getClassSuffix(), self.afMethodStr)) ccapi.setAfMethod(self.retryInfo, self.afMethodStr) class AutomationOp_PrintMeteringMode(AutomationOp): def __init__(self, paramDict): self.fListAbilities = self.getDictValueAsBool(paramDict, 'listavailable', default=False) - return super().__init__(paramDict) + return super().__init__(paramDict) def execute(self): resp = ccapi.getMeteringMode(self.retryInfo) self.printGetResponse(resp) @@ -845,15 +845,15 @@ def execute(self): class AutomationOp_SetMeteringMode(AutomationOp): def __init__(self, paramDict): self.meteringModeStr = self.getDictValueAsStr(paramDict, 'meteringmode', None, fRequiredValue=True) - return super().__init__(paramDict) + return super().__init__(paramDict) def execute(self): - applog_i("{:s}: {:s}".format(self.getClassSuffix(), self.meteringModeStr)) + applog_i("{:s}: {:s}".format(self.getClassSuffix(), self.meteringModeStr)) ccapi.setMeteringMode(self.retryInfo, self.meteringModeStr) class AutomationOp_PrintStillImageQuality(AutomationOp): def __init__(self, paramDict): self.fListAbilities = self.getDictValueAsBool(paramDict, 'listavailable', default=False) - return super().__init__(paramDict) + return super().__init__(paramDict) def execute(self): resp = ccapi.getStillImageQuality(self.retryInfo) if resp['success']: @@ -864,39 +864,39 @@ def execute(self): availableStr = "" infoStr = "{:s}{:s}".format(currentStr, availableStr) print(resp['value']['jpeg']) - applog_i("{:s}: {:s}".format(self.getClassSuffix(), infoStr)) + applog_i("{:s}: {:s}".format(self.getClassSuffix(), infoStr)) class AutomationOp_SetStillImageQuality(AutomationOp): def __init__(self, paramDict): self.rawQualityStr = self.getDictValueAsStr(paramDict, 'rawquality', None, fRequiredValue=True) self.jpegQualityStr = self.getDictValueAsStr(paramDict, 'jpegquality', None, fRequiredValue=True) - return super().__init__(paramDict) + return super().__init__(paramDict) def execute(self): - applog_i("{:s}: Raw: {:s}, JPEG: {:s}".format(self.getClassSuffix(), self.rawQualityStr, self.jpegQualityStr)) + applog_i("{:s}: Raw: {:s}, JPEG: {:s}".format(self.getClassSuffix(), self.rawQualityStr, self.jpegQualityStr)) ccapi.setStillImageQuality(self.retryInfo, self.rawQualityStr, self.jpegQualityStr) class AutomationOp_PrintLensInfo(AutomationOp): def __init__(self, paramDict): - return super().__init__(paramDict) + return super().__init__(paramDict) def execute(self): resp = ccapi.getLensInfo(self.retryInfo) if resp['success']: infoStr = "{:s}".format(resp['name'] if resp['mount'] else "No lens mounted") else: infoStr = "N/A" - applog_i("{:s}: {:s}".format(self.getClassSuffix(), infoStr)) + applog_i("{:s}: {:s}".format(self.getClassSuffix(), infoStr)) class AutomationOp_AssertCameraSettings(AutomationOp): def __init__(self, paramDict): self.modeDialSetting = self.getDictValueAsStr(paramDict, 'modedial', default=None) self.fInMovieMode = self.getDictValueAsBool(paramDict, 'moviemode', default=None) self.afOperation = self.getDictValueAsStr(paramDict, 'afoperation', default=None) - return super().__init__(paramDict) + return super().__init__(paramDict) def execute(self): if self.modeDialSetting != None: resp = ccapi.getShootingModeDial(self.retryInfo) if resp['success'] and resp['value'] != self.modeDialSetting: - applog_i("{:s}: Mode dial setting is \"{:s}\" but must be \"{:s}\"".format(self.getClassSuffix(), resp['value'], self.modeDialSetting)) + applog_i("{:s}: Mode dial setting is \"{:s}\" but must be \"{:s}\"".format(self.getClassSuffix(), resp['value'], self.modeDialSetting)) exit(ERRNO_ASSERT_CAMERA_SETTING_WRONG_VALUE) if self.fInMovieMode != None: resp = ccapi.getMovieMode(self.retryInfo) @@ -908,7 +908,7 @@ def execute(self): if self.afOperation != None: resp = ccapi.getAfOperation(self.retryInfo) if resp['success'] and resp['value'] != self.afOperation: - applog_i("{:s}: AF operation is \"{:s}\" but must be \"{:s}\"".format(self.getClassSuffix(), resp['value'], self.afOperation)) + applog_i("{:s}: AF operation is \"{:s}\" but must be \"{:s}\"".format(self.getClassSuffix(), resp['value'], self.afOperation)) exit(ERRNO_ASSERT_CAMERA_SETTING_WRONG_VALUE) applog_i("{:s}: Conditions met".format(self.getClassSuffix())) @@ -937,37 +937,37 @@ def execute(self): infoStr = "Changed camera time from \"{:s}\" to \"{:s}\"".format(cameraCurrentDateTimeStr, cameraNewDateTimeStr) applog_i("{:s}: {:s}".format(self.getClassSuffix(), infoStr)) -class AutomationOp_Group(AutomationOp): +class AutomationOp_Group(AutomationOp): def __init__(self, paramDict): self.groupName = self.getDictValueAsStr(paramDict, 'groupname', default="") self.groupRepeatCount = self.getDictValueAsScalar(paramDict, 'grouprepeatcount', default=1) self.ops = [] return super().__init__(paramDict) - + def appendOp(self, op): self.ops.append(op) - + def execute(self): - for groupIteration in range(self.groupRepeatCount): + for groupIteration in range(self.groupRepeatCount): if self.groupName != '.MainGroup': # don't print out banner for main group - it's an internal construct to contain the entire automation script applog_i("Performing group \"{:s}\", Iteration {:d}/{:d}".format(self.groupName, groupIteration+1, self.groupRepeatCount)) for opIndex, op in enumerate(self.ops): repeatCount = op.repeatCount - for opIteration in range(repeatCount): + for opIteration in range(repeatCount): op.execute() if op.delayAfterSecs > 0: delayWithConsoleMessage(op.delayAfterSecs, "'delayafter' OP param") - + # # list of all automation operation classes. Update this list whenever adding a new class. It is used to create # instances based on the ops specified by the user on the command line or in the ops file -# +# AutomationOpClasses = [ - AutomationOp_Group, AutomationOp_Delay, AutomationOp_PrintMovieQuality, AutomationOp_SetMovieQuality, + AutomationOp_Group, AutomationOp_Delay, AutomationOp_PrintMovieQuality, AutomationOp_SetMovieQuality, AutomationOp_EnterMovieMode, AutomationOp_ExitMovieMode, AutomationOp_StartMovieRecord, AutomationOp_StopMovieRecord, AutomationOp_PrintCameraInfo, AutomationOp_PrintTemperatureStatus, - AutomationOp_TakePhoto, AutomationOp_PrintBatteryInfo, AutomationOp_PrintShootingSettings, AutomationOp_GetInfoByUrl, + AutomationOp_TakePhoto, AutomationOp_PrintBatteryInfo, AutomationOp_PrintShootingSettings, AutomationOp_GetInfoByUrl, AutomationOp_DisconnectWireless, AutomationOp_PrintAPI, AutomationOp_DownloadFileByUrl, AutomationOp_WaitForNewFilesOnCamera, AutomationOp_GetInfoOnNewFilesPolled, AutomationOp_RunExecutable, AutomationOp_DownloadNewFilesPolled, AutomationOp_PrintMessageToLog, AutomationOp_ExitApp, @@ -979,27 +979,27 @@ def execute(self): AutomationOp_PrintLensInfo, AutomationOp_WaitForEnterKeyToContinue, AutomationOp_Beep, AutomationOp_AssertCameraSettings, AutomationOp_PrintMovieMode, AutomationOp_PrintAfOperation, AutomationOp_GetPendingEvents, AutomationOp_SyncDateTime, AutomationOp_PrintCameraDateTime -] - - +] + + # # Canon CCAPI access class -# -class CCAPI: +# +class CCAPI: class RetryInfo: def __init__(self, retryArgsDict): # timeouts/retries allowed - self.timeoutSecs = retryArgsDict['timeout'] - self.downloadTimeoutSecs = retryArgsDict['downloadtimeout'] + self.timeoutSecs = retryArgsDict['timeout'] + self.downloadTimeoutSecs = retryArgsDict['downloadtimeout'] self.transportErrorRetriesAllowed = retryArgsDict['transportretries'] - self.cmdErrorRetriesAllowed = retryArgsDict['cmdretries'] + self.cmdErrorRetriesAllowed = retryArgsDict['cmdretries'] self.retryDelaySecs = retryArgsDict['retrydelay'] self.maxBusyTimeRetrySecs = retryArgsDict['maxbusyretrytime'] self.fExitOnCmdErr = retryArgsDict['exitoncmderr'] def cmdStarting(self): # vars to tracks errors that occurred during this request - self.cmdTimeStart = secondsElapsed(None) + self.cmdTimeStart = secondsElapsed(None) self.transportErrorCount = 0 self.cmdErrorCount = 0 def shouldRetryBePerformed(self, fTransportError, request=None, resp=None): @@ -1071,12 +1071,12 @@ def genFullUrl(self, relativeUrl, verPrefix='ver100'): # example: http://192.168.1.142:8080/ccapi/ver100/devicestatus/temperature prefix = "/ccapi/" + verPrefix if verPrefix else "" return 'http://{:s}:{:d}{:s}{:s}'.format(self.ipAddress, self.port, prefix, relativeUrl) - + def _get(self, retryInfo, fullUrl, stream=False): timeoutSecs = retryInfo.timeoutSecs if not stream else retryInfo.downloadTimeoutSecs timeoutSecs = timeoutSecs if timeoutSecs > 0 else None retryInfo.cmdStarting() - while True: + while True: applog_d_nc("GET {:s}".format(fullUrl)) consoleWriteLine("GET {:s} ".format(re.findall(":[0-9]+(.*)", fullUrl)[0])) try: @@ -1094,8 +1094,8 @@ def _get(self, retryInfo, fullUrl, stream=False): continue return r exit(ERRNO_HTTP_GET_FAILED) - - + + # # performs CCAPI "get" request, which obtains status/configuration information from the camera # @relativeUrl - Relative CCAPI URL describing what information is requested (ex: "devicestatus/temperature") @@ -1107,15 +1107,15 @@ def get(self, retryInfo, fullUrl): applog_d_nc("GET Result: status_code={:d}, data={}".format(r.status_code, resp)) resp['status_code'] = r.status_code resp['success'] = (r.status_code == 200) - return resp - + return resp + def getRelative(self, retryInfo, relativeUrl): return self.get(retryInfo, self.genFullUrl(relativeUrl)) # # performs CCAPI "post" and "put" requests which performs an action or sets a configuration value # @return JSON object with parsed from JSON values returned by camera - # + # def _postput(self, retryInfo, fullUrl, jsonInput, crudMethodStr): if crudMethodStr == 'POST': @@ -1135,7 +1135,7 @@ def _postput(self, retryInfo, fullUrl, jsonInput, crudMethodStr): applog_e('** {:s} Failed ** "{:}"'.format(crudMethodStr, str(e))) retryInfo.shouldRetryBePerformed(True) # if this returns then it's assumed we're supported to retry otherwise exit() wouldn've been called continue - consoleClearLine() + consoleClearLine() resp = json.loads(r.text) if r.text else dict() resp['status_code'] = r.status_code resp['success'] = (r.status_code == 200 or r.status_code == 202) # 202 means "Accepted" - occurs for network disconnect request @@ -1145,7 +1145,7 @@ def _postput(self, retryInfo, fullUrl, jsonInput, crudMethodStr): if retryInfo.shouldRetryBePerformed(False, r, resp): continue return resp - applog_d_nc("{:s} Result: status_code={:d}, data={}".format(crudMethodStr, r.status_code, resp)) + applog_d_nc("{:s} Result: status_code={:d}, data={}".format(crudMethodStr, r.status_code, resp)) return resp exit(ERRNO_HTTP_POST_FAILED) @@ -1154,51 +1154,51 @@ def post(self, retryInfo, fullUrl, jsonInput): def put(self, retryInfo, fullUrl, jsonInput): return self._postput(retryInfo, fullUrl, jsonInput, 'PUT') - + def postRelative(self, retryInfo, relativeUrl, jsonInput): return self.post(retryInfo, self.genFullUrl(relativeUrl), jsonInput) - + def putRelative(self, retryInfo, relativeUrl, jsonInput): return self.put(retryInfo, self.genFullUrl(relativeUrl), jsonInput) - + def getApiInfo(self, retryInfo): return self.get(retryInfo, self.genFullUrl('/ccapi', verPrefix=None)) - + def getCameraInfo(self, retryInfo): return self.getRelative(retryInfo, '/deviceinformation') - + def getDateTime(self, retryInfo): return self.getRelative(retryInfo, '/functions/datetime') def setDateTime(self, retryInfo, dateTimeStr, fDst): dict = { 'datetime' : dateTimeStr, 'dst' : fDst } return self.putRelative(retryInfo, '/functions/datetime', json.dumps(dict)) - + def takeStillPhoto(self, retryInfo, fPerformAF=False): dict = { 'af' : fPerformAF } return self.postRelative(retryInfo, '/shooting/control/shutterbutton', json.dumps(dict)) - + def getTemperatureStatus(self, retryInfo): return self.getRelative(retryInfo, '/devicestatus/temperature') - + def getMovieQuality(self, retryInfo): return self.getRelative(retryInfo, '/shooting/settings/moviequality') - + def setMovieQuality(self, retryInfo, movieQualityStr): dict = { 'value' : movieQualityStr } return self.putRelative(retryInfo, '/shooting/settings/moviequality', json.dumps(dict)) - + def getMovieMode(self, retryInfo): return self.getRelative(retryInfo, '/shooting/control/moviemode') - + def setMovieMode(self, retryInfo, movieMode=True): dict = { 'action' : 'on' if movieMode==True else 'off' } return self.postRelative(retryInfo, '/shooting/control/moviemode', json.dumps(dict)) - + def startStopMovieRecord(self, retryInfo, start=True): dict = { 'action' : 'start' if start==True else 'stop' } return self.postRelative(retryInfo, '/shooting/control/recbutton', json.dumps(dict)) - + def getPolledUpdate(self, retryInfo): return self.getRelative(retryInfo, '/event/polling?continue=off') @@ -1221,14 +1221,14 @@ def pollForNewFilesOnCamera(self, retryInfo, maxWaitTimeSecs=5, pollIntervalSecs consoleWriteLine("Delaying {:.2f} on poll for new files".format(pollIntervalSecs)) time.sleep(pollIntervalSecs) consoleClearLine() - - @staticmethod + + @staticmethod def getFilenameFromUrl(ccapiUrl): # get filename from URL by splitting the string into array by slashes and using the last element of the array # example URL: http://192.168.1.142:8080/ccapi/ver100/contents/sd/100CANON/IMG_0327.JPG urlComponents = ccapiUrl.split('/') return urlComponents[len(urlComponents)-1] - + def downloadFile(self, retryInfo, fullUrl, localFileFullPath): r = self._get(retryInfo, fullUrl, True) if r.status_code == 200: @@ -1245,16 +1245,16 @@ def downloadFile(self, retryInfo, fullUrl, localFileFullPath): consoleClearLine() return (False, timeElapsed) return (True, 0) # failed - + def getFileInfo(self, retryInfo, fullUrl): return self.get(retryInfo, fullUrl + '?kind=info') - + def getBatteryInfo(self, retryInfo): return self.getRelative(retryInfo, '/devicestatus/battery') - + def getShootingSettings(self, retryInfo): return self.getRelative(retryInfo, '/shooting/settings') - + def disconnectWirelss(self, retryInfo): dict = { 'action' : 'disconnect' } return self.postRelative(retryInfo, '/functions/wificonnection', json.dumps(dict)) @@ -1263,35 +1263,35 @@ def getAperture(self, retryInfo): return self.getRelative(retryInfo, "/shooting/settings/av") def setAperture(self, retryInfo, apertureStr): - dict = { 'value' : apertureStr } + dict = { 'value' : apertureStr } return self.putRelative(retryInfo, '/shooting/settings/av', json.dumps(dict)) def getShutterSpeed(self, retryInfo): return self.getRelative(retryInfo, "/shooting/settings/tv") - + def setShutterSpeed(self, retryInfo, shutterSpeedStr): - dict = { 'value' : shutterSpeedStr } + dict = { 'value' : shutterSpeedStr } return self.putRelative(retryInfo, '/shooting/settings/tv', json.dumps(dict)) - + def getIso(self, retryInfo): return self.getRelative(retryInfo, "/shooting/settings/iso") - + def setIso(self, retryInfo, isoStr): - dict = { 'value' : isoStr } + dict = { 'value' : isoStr } return self.putRelative(retryInfo, '/shooting/settings/iso', json.dumps(dict)) def getExposureCompensation(self, retryInfo): return self.getRelative(retryInfo, "/shooting/settings/exposure") def setExposureCompensation(self, retryInfo, exposureCompensationStr): - dict = { 'value' : exposureCompensationStr } + dict = { 'value' : exposureCompensationStr } return self.putRelative(retryInfo, "/shooting/settings/exposure", json.dumps(dict)) - + def getWhiteBalance(self, retryInfo): return self.getRelative(retryInfo, "/shooting/settings/wb") def setWhiteBalance(self, retryInfo, whiteBalanceStr): - dict = { 'value' : whiteBalanceStr } + dict = { 'value' : whiteBalanceStr } return self.putRelative(retryInfo, '/shooting/settings/wb', json.dumps(dict)) def getShootingModeDial(self, retryInfo): @@ -1301,9 +1301,9 @@ def getDriveMode(self, retryInfo): return self.getRelative(retryInfo, "/shooting/settings/drive") def setDriveMode(self, retryInfo, driveModeStr): - dict = { 'value' : driveModeStr } + dict = { 'value' : driveModeStr } return self.putRelative(retryInfo, '/shooting/settings/drive', json.dumps(dict)) - + def getAfOperation(self, retryInfo): return self.getRelative(retryInfo, "/shooting/settings/afoperation") @@ -1311,14 +1311,14 @@ def getAfMethod(self, retryInfo): return self.getRelative(retryInfo, "/shooting/settings/afmethod") def setAfMethod(self, retryInfo, afMethodStr): - dict = { 'value' : afMethodStr } + dict = { 'value' : afMethodStr } return self.putRelative(retryInfo, '/shooting/settings/afmethod', json.dumps(dict)) def getMeteringMode(self, retryInfo): return self.getRelative(retryInfo, "/shooting/settings/metering") def setMeteringMode(self, retryInfo, meteringModeStr): - dict = { 'value' : meteringModeStr } + dict = { 'value' : meteringModeStr } return self.putRelative(retryInfo, '/shooting/settings/metering', json.dumps(dict)) def getStillImageQuality(self, retryInfo): @@ -1332,7 +1332,7 @@ def getLensInfo(self, retryInfo): return self.getRelative(retryInfo, "/devicestatus/lens") # -# Converts a size value into human-readable form, including commas and a +# Converts a size value into human-readable form, including commas and a # KB/MB/GB suffix as apporpriate # @param sizeBytes Value to convert # @return Value as a human-readable string @@ -1342,7 +1342,7 @@ def getHumanReadableSize(sizeBytes): def truncate(number, digits): # https://stackoverflow.com/a/29257837 return math.floor(number * 10 ** digits) / 10 ** digits - + if sizeBytes < 1024: return '{:,d} Bytes'.format(sizeBytes) if sizeBytes < 1024*1024: @@ -1379,7 +1379,7 @@ def convertTimeStrToSeconds(timeStr, fHandleConversionExeptions=True): applog_e("Unable to convert paramter value \"{:s}\" to a valid time value".format(timeStr)) exit(ERRNO_INVALID_TIME_VALUE_SPECIFIED) return totalSeconds - + # # Delays execution for a specified amount of time, displaying # a temporary console message during the delay @@ -1396,10 +1396,10 @@ def delayWithConsoleMessage(delaySecs, delayReasonDesc=""): secsDelayThisIteration = min(secsRemaining, 1) consoleWriteLine("Delaying, {:.2f} seconds left {:}".format(secsRemaining, delayReasonStr)) time.sleep(secsDelayThisIteration) - secsRemaining -= secsDelayThisIteration + secsRemaining -= secsDelayThisIteration consoleClearLine() applog_i("Delayed for {:.2f} seconds {:s}".format(delaySecs, delayReasonStr)) - + # # Writes a message directly to console (stdout) without newline. This is used # for single-line status/progress displays that update in-place @@ -1411,12 +1411,12 @@ def consoleWriteLine(msg): # # Clears the current line of the console by printing a carriage return, a # line of spaces, then another carriage return to return cursor to first -# column of line. This is used following a consoleWriteLine() sequence +# column of line. This is used following a consoleWriteLine() sequence # def consoleClearLine(numCharsToClear=78): sys.stdout.write("\r" + " "*numCharsToClear + "\r") sys.stdout.flush() - + # returns the number of seconds that have elapsed since # the specified anchor time. if the anchor time is None # then this routine returns the current time, which @@ -1428,8 +1428,8 @@ def secondsElapsed(timeAnchor): if timeAnchor == None: return timeCurrent return timeCurrent - timeAnchor - - + + # # global vars # @@ -1456,19 +1456,19 @@ def generateUniqueFilename(origFilenameWithPath): # prompts the user for a single-character selection (+enter), # only allowing characters specfied in validKeyList. check # is case-insensitive. - # + # def promptWithSingleKeyResponse(promptStr, validKeyListStr): while True: key = input(promptStr).upper() if key and validKeyListStr.upper().find(key.upper()) != -1: - return key + return key if (os.path.exists(localFilenameWithPath)): if ifExistsOptionStr == 'prompt': applog_i("\"{:s}\" exists".format(localFilenameWithPath)) keyResponse = promptWithSingleKeyResponse("(S)kip, (O)verwrite, (U)niquename, (E)xit [+enter]: ", 'soue') else: - keyResponse = '' + keyResponse = '' if ifExistsOptionStr == 'skip' or keyResponse == 'S': applog_i("Skipping \"{:s}\" - file exists".format(localFilenameWithPath)) return None @@ -1498,8 +1498,8 @@ def getAutomationOpClassByName(name): if classNameSuffix.lower() == name.lower(): return classType return None - - + + # # Converts a list of strings into instances of automation ops, collected # inside a main group @@ -1510,7 +1510,7 @@ def getAutomationOpClassByName(name): # @return Main group instance, which has within it operations and possibly nested operation sub-groups # def processOpStrList(lines): - + def processOpListForGroup(groupInst, groupParamDict, groupNestingLevel, lineIndex, lines): # @@ -1518,11 +1518,11 @@ def processOpListForGroup(groupInst, groupParamDict, groupNestingLevel, lineInde # groupParamDict.pop('groupname', None) groupParamDict.pop('grouprepeatcount', None) - + while lineIndex < len(lines): - + line = lines[lineIndex] - + # # converts a string of operation and param=value pairs into # a list. The operation and each param=value pairs are separated @@ -1545,12 +1545,12 @@ def processOpListForGroup(groupInst, groupParamDict, groupNestingLevel, lineInde # empty line lineIndex += 1 continue - + applog_d("Translating op line: {:s}".format(line)) - + opName = opAndParams[0] opNameLower = opName.lower() - + if opNameLower == "endgroup": # end of current group. step out of this recursed group if groupNestingLevel == 0: @@ -1560,12 +1560,12 @@ def processOpListForGroup(groupInst, groupParamDict, groupNestingLevel, lineInde # convert param=value pairs into dictionary try: - paramDict = { x.split('=')[0].lower():x.split('=')[1] for idx, x in enumerate(opAndParams) if idx > 0 } + paramDict = { x.split('=')[0].lower():x.split('=')[1] for idx, x in enumerate(opAndParams) if idx > 0 } except Exception as e: applog_e("Error translating parameter values for \"{:s}\" on line {:d}".format(line, lineIndex+1)) exit(ERRNO_ERROR_PARSING_AUTOMATION_OP_PARAMS) - paramDict = {**groupParamDict, **paramDict} # merge the two dictionaries, with this op's params overriding the group's if present - + paramDict = {**groupParamDict, **paramDict} # merge the two dictionaries, with this op's params overriding the group's if present + # match the operation to the list of operation classes supposed opClass = getAutomationOpClassByName(opName) if not opClass: @@ -1585,7 +1585,7 @@ def processOpListForGroup(groupInst, groupParamDict, groupNestingLevel, lineInde # arguments to pass to the executable # if opName.lower() == 'runexecutable': - if lineIndex == len(lines)-1 or re.findall("^\s*RunExecutableArgs", lines[lineIndex+1], flags=re.IGNORECASE) == []: + if lineIndex == len(lines)-1 or re.findall("^\s*RunExecutableArgs", lines[lineIndex+1], flags=re.IGNORECASE) == []: applog_e("\"{:s}\" operation is missing the RunExecutableArgs= line below it on line {:d}".format(opName, lineIndex+1)) exit(ERRNO_RUNEXECUTABLE_OP_MISSING_ARGUMENT_LINE) argStr = re.findall("^\s*RunExecutableArgs\s*=\s*(.*)", lines[lineIndex+1], flags=re.IGNORECASE)[0] @@ -1599,16 +1599,16 @@ def processOpListForGroup(groupInst, groupParamDict, groupNestingLevel, lineInde if unusedParamDictKeys: applog_e("\"{:s}\" operation on line {:d} has the following unsupported parameters: {}".format(opName, lineIndex+1, unusedParamDictKeys)) exit(ERRNO_UNKNOWN_OP_PARAM) - + # insert operation instance into current group groupInst.appendOp(opInst) - + if opName == "Group": # new group was just created. recurse to place subsequent ops into new gruop lineIndex = processOpListForGroup(opInst, paramDict, groupNestingLevel+1, lineIndex+1, lines) else: lineIndex += 1 - + # # reached the end of the file # @@ -1617,14 +1617,14 @@ def processOpListForGroup(groupInst, groupParamDict, groupNestingLevel, lineInde applog_e("Error: A 'Group' is missing a corresponding 'EndGroup'") exit(ERRNO_UNMATCHED_GROUP_OP) - return lineIndex + return lineIndex mainGroupParamDict = { 'groupname' : '.MainGroup' } mainGroup = AutomationOp_Group(mainGroupParamDict) processOpListForGroup(mainGroup, mainGroupParamDict, 0, 0, lines) return mainGroup - + # # Process a user-specified automation op file, creating instances of # automation gruops and operations based on the content of the file @@ -1645,10 +1645,10 @@ def processOpFile(opFilename): applog_e("Unable to read \"{:s}\": {:s}".format(opFilename, str(e))) exit(ERRNO_CANT_READ_OP_FILE) - lines = [x.rstrip('\n') for x in lines] # remove any trailing newlines - + lines = [x.rstrip('\n') for x in lines] # remove any trailing newlines + return processOpStrList(lines) - + # # processCmdLine - Processes command line arguments @@ -1657,9 +1657,9 @@ class ArgumentParserError(Exception): pass # from http://stackoverflow.com/quest class ArgumentParserWithException(argparse.ArgumentParser): def error(self, message): raise ArgumentParserError(message) - + def processCmdLine(): - + def convertArgStrToBool(argName): argValue = g.args[argName] if argValue.lower() in ['1', 'true', 't', 'yes', 'y']: @@ -1670,14 +1670,14 @@ def convertArgStrToBool(argName): return applog_e("Option \"{:s}\" requires a boolean value but \"{:s}\" specified".format(argName, argValue)) exit(ERRNO_BAD_CMD_LINE_ARG_VALUE) - + parser = ArgumentParserWithException(fromfile_prefix_chars='!',\ formatter_class=argparse.RawDescriptionHelpFormatter, description='Canon CCAPI Automator (Horshack)',\ epilog="Options can also be specified from a file. Use !. Each word in the file must be on its own line.\n\nYou "\ "can abbreviate any argument name provided you use enough characters to\nuniquely distinguish it from other argument names.\n") parser.add_argument('--ipaddress', type=str.lower, help='IP address of camera.', default='', metavar="addr", required=True) - parser.add_argument('--port', type=int, help='CCAPI Port configured on camera. Default is %(default)s', default=DEFAULT_CCAPI_HTTP_PORT, required=False) + parser.add_argument('--port', type=int, help='CCAPI Port configured on camera. Default is %(default)s', default=DEFAULT_CCAPI_HTTP_PORT, required=False) parser.add_argument('--opfile', type=str, help='File containing list of automation operations to perform. Example: --opfile \"c:\My Documents\ops.txt"', default=None, metavar="filename", required=False) parser.add_argument('--op', type=str, help='List of automation operations to perform, each separated with a "|". Example: --op \"Delay secs=5 | DisplayMovieQuality\"', default=None, metavar="oplist", required=False) parser.add_argument('--outputdir', type=str, help='Directory to store image/file(s) to. Default is current directory. If path contains any spaces enclose it in double quotes. Example: --outputdir \"c:\My Documents\"', default=None, metavar="path", required=False) @@ -1685,54 +1685,54 @@ def convertArgStrToBool(argName): parser.add_argument('--exitoncmderr', type=str, help='Exit if camera reports error for an op command. Default is "%(default)s"', default=str(DEFAULT_EXIT_ON_CMD_ERR), required=False) parser.add_argument('--timeout', type=float, help='CCAPI HTTP request timeout in seconds (0 = no timeout). Default is %(default)s seconds', default=DEFAULT_CCAPI_TIMEOUT_SECS, required=False) parser.add_argument('--downloadtimeout', type=int, help='CCAPI HTTP file download timeout in seconds (0 = no timeout). Default is %(default)s', default=DEFAULT_CCAPI_DOWNLOAD_TIMEOUT_SECS, required=False) - parser.add_argument('--transportretries', type=int, help='CCAPI retries for transport errors (such as timeouts). Default is %(default)s', default=DEFAULT_CCAPI_TRANSPORT_ERROR_RETRIES, required=False) - parser.add_argument('--cmdretries', type=int, help='CCAPI retries for cmds ending in error. Default is %(default)s', default=DEFAULT_CCAPI_CMD_ERROR_RETRIES, required=False) - parser.add_argument('--retrydelay', type=float, help='CCAPI delay between retries for cmds ending in error. Default is %(default)s', default=DEFAULT_CCAPI_RETRY_DELAY_SECS, required=False) - parser.add_argument('--maxbusyretrytime', type=int, help='How long to retry when camera reports busy. Default is %(default)s', default=DEFAULT_CCAPI_MAX_BUSY_RETRY_SECS, required=False) + parser.add_argument('--transportretries', type=int, help='CCAPI retries for transport errors (such as timeouts). Default is %(default)s', default=DEFAULT_CCAPI_TRANSPORT_ERROR_RETRIES, required=False) + parser.add_argument('--cmdretries', type=int, help='CCAPI retries for cmds ending in error. Default is %(default)s', default=DEFAULT_CCAPI_CMD_ERROR_RETRIES, required=False) + parser.add_argument('--retrydelay', type=float, help='CCAPI delay between retries for cmds ending in error. Default is %(default)s', default=DEFAULT_CCAPI_RETRY_DELAY_SECS, required=False) + parser.add_argument('--maxbusyretrytime', type=int, help='How long to retry when camera reports busy. Default is %(default)s', default=DEFAULT_CCAPI_MAX_BUSY_RETRY_SECS, required=False) parser.add_argument('--logginglevel', type=str.lower, choices=['normal', 'verbose', 'debug' ], help='Sets how much information is saved to the result log. Default is "%(default)s"', default='normal', required=False) - + # # if there is a default arguments file present, add it to the argument list so that parse_args() will process it # defaultArgFilename = os.path.join(g.appDir, "{:s}-defaultopts".format(APP_NAME)) if os.path.exists(defaultArgFilename): sys.argv.insert(1, "!" + defaultArgFilename) # insert as first arg (past script name), so that the options in the file can still be overriden by user-entered cmd line options - + if len(sys.argv) == 1: # print help if no parameters passed parser.print_help() exit(0) - - + + # perform the argparse try: args = vars(parser.parse_args()) except ArgumentParserError as e: applog_e("Command line error: " + str(e)) - exit(ERRNO_BAD_CMD_LINE_ARG) - + exit(ERRNO_BAD_CMD_LINE_ARG) + # set our global var to the processed argument list and log them - g.args = args + g.args = args # # do any processing required on command-line arguments # - - if not g.args['outputdir']: - # no explicit output directory specified - use current directory + + if not g.args['outputdir']: + # no explicit output directory specified - use current directory g.args['outputdir'] = '.\\' if g.isWin32 else './' - + # process 'logginglevel' (at init we default to 'normal') if g.args['logginglevel'] == 'verbose': applog_set_loggingFlags(APPLOGF_LEVEL_INFORMATIONAL | APPLOGF_LEVEL_ERROR | APPLOGF_LEVEL_WARNING | APPLOGF_LEVEL_VERBOSE) elif g.args['logginglevel'] == 'debug': - applog_set_loggingFlags(APPLOGF_LEVEL_INFORMATIONAL | APPLOGF_LEVEL_ERROR | APPLOGF_LEVEL_WARNING | APPLOGF_LEVEL_VERBOSE | APPLOGF_LEVEL_DEBUG) - + applog_set_loggingFlags(APPLOGF_LEVEL_INFORMATIONAL | APPLOGF_LEVEL_ERROR | APPLOGF_LEVEL_WARNING | APPLOGF_LEVEL_VERBOSE | APPLOGF_LEVEL_DEBUG) + ccapi.ipAddress = g.args['ipaddress'] ccapi.port = g.args['port'] convertArgStrToBool('exitoncmderr') - + # # last step - process the operations specified by the user # @@ -1741,34 +1741,34 @@ def convertArgStrToBool(argName): exit(ERRNO_BAD_CMD_LINE_ARG) if not g.args['opfile'] and not g.args['op']: applog_e("Either --opfile or --op must specified on command line") - exit(ERRNO_BAD_CMD_LINE_ARG) + exit(ERRNO_BAD_CMD_LINE_ARG) if g.args['opfile']: g.mainAutomationGroup = processOpFile(g.args['opfile']) else: strList = g.args['op'].split('|') # multiple ops can be specified, separated by a | character g.mainAutomationGroup = processOpStrList(strList) - + # log the cmd line arguments applog_d("Orig cmd line: {:s}".format(str(sys.argv))) applog_d("Processed cmd line: {:s}".format(str(g.args))) - + # # sets app-level globals related to the platform we're running under and # creates path to app directories, creating them if necessary -# +# def establishAppEnvironment(): - g.appStartTime = time.time() + g.appStartTime = time.time() g.isWin32 = (platform.system() == 'Windows') - g.isOSX = (platform.system() == 'Darwin') - + g.isOSX = (platform.system() == 'Darwin') + # # determine the directory our script resides in, in case the # user is executing from a different working directory. # g.appDir = os.path.dirname(os.path.realpath(sys.argv[0])) - + # # setup our app's data directory, where we store our # logs and temporary files. By default we create it off @@ -1780,14 +1780,14 @@ def establishAppEnvironment(): g.appDataDir = os.path.join(g.appDir, appDataDir) else: g.appDataDir = os.path.join(str(pathlib.Path.home()), appDataDir) - # create the data directory if it doesn't already exist + # create the data directory if it doesn't already exist if not os.path.exists(g.appDataDir): os.makedirs(g.appDataDir) # # invoked near exit, issues a final log message which is used by utils to signify a gracefully # shutdown and then tells the applog module to shut itself down -# +# def exit(errno): applog(">>>> {:s} session over (exit={:d}), logs at \"{:s}\"".format(APP_NAME, errno, g.appDataDir)) applog_shutdown() @@ -1805,7 +1805,7 @@ def main(): # Log filenames are canomate-log-last.txt (most recent session) and # canomate-log-lifetime.txt (all sessions) # - establishAppEnvironment() + establishAppEnvironment() errno = applog_init(APPLOGF_LEVEL_INFORMATIONAL | APPLOGF_LEVEL_ERROR, os.path.join(g.appDataDir, "{:s}-log-last.txt".format(APP_NAME)),\ os.path.join(g.appDataDir, "{:s}-log-lifetime.txt".format(APP_NAME))) if errno: @@ -1826,7 +1826,7 @@ def main(): processCmdLine() g.mainAutomationGroup.execute() except KeyboardInterrupt as e: - try: + try: applog_e("\n>>>> Terminated by ctrl-c user keypress") exit(ERRNO_CTRL_C_INTERRUPT) except KeyboardInterrupt as e: @@ -1838,11 +1838,11 @@ def main(): exit(ERRNO_CODE_EXCEPTION) exit(0) - + # # program entry point -# +# if __name__ == "__main__": if sys.version_info.major < 3 and sys.version_info.minor < 5: print("Python version {:d}.{:d}.{:d} detected. Requires Python 3.5 or above".format(sys.version_info.major, sys.version_info.minor, sys.version_info.micro)) diff --git a/strutil.py b/strutil.py index 7d75808..0bafa48 100644 --- a/strutil.py +++ b/strutil.py @@ -19,7 +19,7 @@ # returns a date/time string in mm/dd/yy hh:mm:ss format for specified # epoch time - if epoch time is None then returns date/time string for # current time -# +# def getDateTimeStr(timeEpoch=None, fMilitaryTime=True): if timeEpoch == None: timeEpoch = time.time() @@ -29,7 +29,7 @@ def getDateTimeStr(timeEpoch=None, fMilitaryTime=True): else: timeStr = time.strftime("%m/%d/%y %H:%M:%S", timeStruct) return timeStr - + # # Generates string containing hex dump of a bytearray. Format is: @@ -58,7 +58,7 @@ def hexdump(data, bytesPerField=1, includeASCII=1): raise AssertionError("hexdump: size of data (0x{:04x}) is not a multiple of bytesPerField ({:d})".format(len(data), bytesPerField)) for offset in xrange(0,len(data),bytesPerField): offsetThisFieldInLine = (offset % 16) # byte offset into data for this field of current line - endingOffsetThisFieldInLine = offsetThisFieldInLine + bytesPerField + endingOffsetThisFieldInLine = offsetThisFieldInLine + bytesPerField if (offsetThisFieldInLine == 0): strHexDump += "{:04x}: ".format(offset) (thisField,) = struct.unpack(bytesPerFieldToUnpackStr[bytesPerField], data[offset:offset+bytesPerField]) @@ -77,7 +77,7 @@ def hexdump(data, bytesPerField=1, includeASCII=1): # values before start ASCII dump seciton fieldsNotPrintedInFinalLine = (16-endingOffsetThisFieldInLine) * bytesPerField charactersPerFieldIncludingSpace = bytesPerField*2 + 1 - strHexDump += " " * (fieldsNotPrintedInFinalLine*charactersPerFieldIncludingSpace) # add spaces for each missing field + strHexDump += " " * (fieldsNotPrintedInFinalLine*charactersPerFieldIncludingSpace) # add spaces for each missing field if (endingOffsetThisFieldInLine < 8): strHexDump += " " # add spaces for missing middle separator for asciiOffset in range(offsetThisFieldInLine+1): @@ -91,4 +91,4 @@ def hexdump(data, bytesPerField=1, includeASCII=1): if not bIsFinalLine: # don't put newline after final line strHexDump += "\n" return strHexDump - +