def __init__(self, logger, auth_token, gcp, model=None, privet_port=None): """Initialize a device object. Args: logger: initialized logger object. auth_token: string, auth_token of authenticated user. gcp: initialized GCPService object model: string, unique model or name of device. privet_port: integer, tcp port devices uses for Privet protocol. """ if model: self.model = model else: self.model = Constants.PRINTER['MODEL'] self.auth_token = auth_token self.logger = logger self.transport = Transport(logger) self.ipv4 = Constants.PRINTER['IP'] if privet_port: self.port = privet_port else: self.port = Constants.PRINTER['PORT'] self.dev_id = None self.name = Constants.PRINTER['NAME'] self.gcp = gcp self.status = None self.messages = {} self.details = {} self.error_state = False self.warning_state = False self.cdd = {} self.supported_types = None self.info = None self.url = 'http://%s:%s' % (self.ipv4, self.port) self.logger.info('Device URL: %s', self.url) self.transport = Transport(logger) self.headers = None self.privet = Privet(logger) self.privet_url = self.privet.SetPrivetUrls(self.ipv4, self.port) self.GetPrivetInfo()
def __init__(self, logger, chromedriver, model=None, privet_port=None): """Initialize a device object. Args: logger: initialized logger object. chromedriver: initialized chromedriver object. model: string, unique model or name of device. privet_port: integer, tcp port devices uses for Privet protocol. """ if model: self.model = model else: self.model = Constants.PRINTER['MODEL'] self.logger = logger self.cd = chromedriver self.cloudprintmgr = CloudPrintMgr(logger, chromedriver) self.ipv4 = Constants.PRINTER['IP'] if privet_port: self.port = privet_port else: self.port = Constants.PRINTER['PORT'] self.name = Constants.PRINTER['NAME'] self.status = None self.messages = {} self.details = {} self.error_state = False self.warning_state = False self.cdd = {} self.info = None self.url = 'http://%s:%s' % (self.ipv4, self.port) self.logger.info('Device URL: %s', self.url) self.transport = Transport(logger) self.jparser = JsonParser(logger) self.headers = None self.privet = Privet(logger) self.privet_url = self.privet.SetPrivetUrls(self.ipv4, self.port) self.GetPrivetInfo()
class Device(object): """The basic device object.""" def __init__(self, logger, auth_token, gcp, model=None, privet_port=None): """Initialize a device object. Args: logger: initialized logger object. auth_token: string, auth_token of authenticated user. gcp: initialized GCPService object model: string, unique model or name of device. privet_port: integer, tcp port devices uses for Privet protocol. """ if model: self.model = model else: self.model = Constants.PRINTER['MODEL'] self.auth_token = auth_token self.logger = logger self.transport = Transport(logger) self.ipv4 = Constants.PRINTER['IP'] if privet_port: self.port = privet_port else: self.port = Constants.PRINTER['PORT'] self.dev_id = None self.name = Constants.PRINTER['NAME'] self.gcp = gcp self.status = None self.messages = {} self.details = {} self.error_state = False self.warning_state = False self.cdd = {} self.supported_types = None self.info = None self.url = 'http://%s:%s' % (self.ipv4, self.port) self.logger.info('Device URL: %s', self.url) self.transport = Transport(logger) self.headers = None self.privet = Privet(logger) self.privet_url = self.privet.SetPrivetUrls(self.ipv4, self.port) self.GetPrivetInfo() def GetPrivetInfo(self): self.privet_info = {} info = self.Info() if info is not None: for key in info: self.privet_info[key] = info[key] self.logger.debug('Privet Key: %s', key) self.logger.debug('Value: %s', info[key]) self.logger.debug('--------------------------') if 'x-privet-token' in info: self.headers = {'X-Privet-Token': str(info['x-privet-token'])} def Register(self, msg, user=Constants.USER['EMAIL'], use_token=True, no_action=False, wait_for_user=True): """Register device using Privet. Args: msg: string, the instruction for the user about the registration confirmation dialog on the printer user: string, the user to register for use_token: boolean, use auth_token if True no_action: boolean, if True, do not wait wait_for_user: boolean, if True, wait for user to press UI button Returns: boolean: True = device registered, False = device not registered. Note, devices a required user input to accept or deny a registration request, so manual intervention is required. """ if self.StartPrivetRegister(user=user): if no_action: print msg else: PromptUserAction(msg) if self.GetPrivetClaimToken(user=user, wait_for_user=wait_for_user): auth_token = self.auth_token if use_token else None if self.ConfirmRegistration(auth_token): self.FinishPrivetRegister() return True return False def GetDeviceCDDLocally(self): """Get device cdd and populate device object with the details via privet. Args: device_id: string, Cloud Print device id. Returns: boolean: True = cdd details populated, False = cdd details not populated. """ r = self.transport.HTTPGet(self.privet_url['capabilities'], headers=self.headers) if r is None: return False response = r.json() if 'printer' in response: self.cdd['caps'] = {} for k in response['printer']: self.cdd['caps'][k] = response['printer'][k] self.supported_types = [ type['content_type'] for type in self.cdd['caps']['supported_content_type'] ] return True else: self.logger.error('Could not find printers in cdd.') return False def __parseDeviceDetails(self, printer): """Parse through the printer device object and extract information Args: printer: dict, object containing printer device details.""" for k in printer: if k == 'name': self.name = printer[k] elif k == 'connectionStatus': self.status = printer[k] elif k == 'id': self.dev_id = printer[k] else: self.details[k] = printer[k] def GetDeviceDetails(self): """Get the device details from our management page through the cloud. Returns: boolean: True = device populated, False = errors. This will populate a Device object with device name, status, state messages, and device details. """ response = self.gcp.Search(self.name) if not response['printers']: print( '%s not found as registered printer under the /search gcp api' % self.name) print 'Update PRINTER["NAME"] in _config.py if misconfigured' full_response = self.gcp.Search() print "Below is the list of registered printers:" for printer in full_response['printers']: print printer['name'] raise self.__parseDeviceDetails(response['printers'][0]) if self.dev_id: if self.GetDeviceCDD(self.dev_id): self.supported_types = [ type['content_type'] for type in self.cdd['caps']['supported_content_type'] ] return True return False def GetDeviceCDD(self, device_id): """Get device cdd and populate device object with the details via the cloud. Args: device_id: string, Cloud Print device id. Returns: boolean: True = cdd details populated, False = cdd details not populated. """ info = self.gcp.Printer(device_id) if 'printers' in info: self.__parseCDD(info['printers'][0]) if ('num_issues' in self.cdd['uiState'] and self.cdd['uiState']['num_issues'] > 0): self.error_state = True else: self.error_state = False return True else: self.logger.error('Could not find printers in cdd.') return False def __parseCDD(self, printer): """Parse the CDD json string into a logical dictionary. Args: printer: formatted data from /printer interface. Returns: boolean: True = CDD parsed, False = CDD not parsed. """ for k in printer: if k == 'capabilities': self.cdd['caps'] = {} else: self.cdd[k] = printer[k] for k in printer['capabilities']['printer']: self.cdd['caps'][k] = printer['capabilities']['printer'][k] return True def CancelRegistration(self): """Cancel Privet Registration that is in progress. Returns: return code from HTTP request. """ self.logger.debug('Sending request to cancel Privet Registration.') url = self.privet_url['register']['cancel'] params = {'user': Constants.USER['EMAIL']} r = self.transport.HTTPPost(url, headers=self.headers, params=params) if r is None: raise Sleep('REG_CANCEL') return r.status_code def StartPrivetRegister(self, user=Constants.USER['EMAIL']): """Start a device registration using the Privet protocol. Returns: boolean: True = success, False = errors. """ self.logger.debug('Registering device %s with Privet', self.ipv4) url = self.privet_url['register']['start'] params = {'user': user} r = self.transport.HTTPPost(url, headers=self.headers, params=params) if r is None: return False return r.status_code == requests.codes.ok def GetPrivetClaimToken(self, user=Constants.USER['EMAIL'], wait_for_user=True): """Wait for user interaction with the Printer's UI and get a Privet Claim Token. Raises EnvironmentError if the printer keeps returning 'pending_user_action' Args: user: string, email address to register under. wait_for_user: boolean, True if user is expected to interact with printer Returns: boolean: True = success, False = errors. """ print( 'Waiting up to 60 seconds for printer UI interaction ' 'then getting Privet Claim Token.') t_end = time.time() + 60 while time.time() < t_end: url = self.privet_url['register']['getClaimToken'] params = {'user': user} r = self.transport.HTTPPost(url, headers=self.headers, params=params) if r is None: raise response = r.json() if 'token' in response: self.claim_token = response['token'] self.automated_claim_url = response['automated_claim_url'] self.claim_url = response['claim_url'] print 'Successfully got Claim Token for %s' % user return True if 'error' in response: if response['error'] == 'pending_user_action': if not wait_for_user: # Should not return 'pending_user_action' when printer is not # waiting for user interaction print( "ERROR: getClaimToken() should not return " "'pending_user_action when user input is not expected'" ) raise EnvironmentError else: return False # Keep polling for user interaction at a configurable interval time.sleep(Constants.SLEEP['POLL']) print 'GetPrivetClaimToken() timed out from waiting for printer interaction' return False def SendClaimToken(self, auth_token=None): """Send a claim token to the Cloud Print service. Args: auth_token: string, auth token of user registering printer. Returns: boolean: True = success, False = errors. """ if not auth_token: auth_token = self.auth_token if not self.claim_token: self.logger.error('Error: device does not have claim token.') self.logger.error( 'Cannot send empty token to Cloud Print Service.') return False if not self.automated_claim_url: self.logger.error('Error: expected automated_claim_url.') self.logger.error('Aborting SendClaimToken()') return False url = self.automated_claim_url params = {'user': Constants.USER['EMAIL']} headers = {'Authorization': 'Bearer %s' % auth_token} r = self.transport.HTTPPost(url, headers=headers, params=params) if r is None: return False if r.status_code == requests.codes.ok and r.json()['success']: return True return False def ConfirmRegistration(self, auth_token): """Register printer with GCP Service using claim token. Returns: boolean: True = printer registered, False = printer not registered. This method should only be called once self.claim_token is populated. """ if not self.claim_token: self.logger.error('No claim token has been set yet.') self.logger.error('Execute GetClaimToken() before this method.') return False url = '%s/confirm?token=%s' % (Constants.GCP['MGT'], self.claim_token) params = {'user': Constants.USER['EMAIL']} headers = self.headers headers['Authorization'] = 'Bearer %s' % auth_token r = self.transport.HTTPPost(url, headers=headers, params=params) if r is None: return False if r.status_code == requests.codes.ok and r.json()['success']: return True return False def FinishPrivetRegister(self): """Complete printer registration using Privet. Returns: boolean: True = success, False = errors. """ self.logger.debug('Finishing printer registration.') url = self.privet_url['register']['complete'] params = {'user': Constants.USER['EMAIL']} r = self.transport.HTTPPost(url, headers=self.headers, params=params) if r is None: return False # Add the device id from the Cloud Print Service. try: info = r.json() except ValueError: self.logger.info('No JSON object in response') else: if 'device_id' in info: self.dev_id = info['device_id'] self.logger.debug('Registered with device id: %s', self.dev_id) return r.status_code == requests.codes.ok def UnRegister(self, auth_token): """Remove device from Google Cloud Service. Args: auth_token: string, auth token of device owner. Returns: boolean: True = success, False = errors. """ if self.dev_id: delete_url = '%s/delete?printerid=%s' % (Constants.GCP['MGT'], self.dev_id) headers = {'Authorization': 'Bearer %s' % auth_token} r = self.transport.HTTPPost(delete_url, headers=headers) else: self.logger.warning('Cannot delete device, not registered.') return False if r is None: return False if r.status_code == requests.codes.ok and r.json()['success']: self.logger.debug('Successfully deleted printer from service.') self.dev_id = None return True self.logger.error('Unable to delete printer from service.') return False def LocalPrint(self, title, content, cjt, content_type): """Submit a local print job to the printer Args: title: string, title of the print job content: string, url or absolute filepath of the item to print. cjt: CloudJobTicket, object that defines the options of the print job content_type: string, MIME type of the print data Returns: int, the job id of the local print job that succeeded, else None """ print '\nWait for idle state before starting a local print job' success = self.WaitForPrinterState('idle') if not success: print 'Idle state not observed\n' return None job_id = self.CreateJob(cjt) if job_id is None: print 'Error creating a local print job.\n' return None output = self.SubmitDoc(job_id, title, content, content_type) if output is None: # Cancel the job creation to get back to a normal state self.CancelJob(job_id) print 'Error printing a local print job.' print( 'Printer may be in an unstable state if the job isn\'t cancelled ' 'correctly, may need to reboot printer') return output def CreateJob(self, cjt=None): """First step required to submit a local print job. Keep trying to obtain the job id for 60 seconds if the printer returns busy status Args: cjt: CloudJobTicket, object that defines the options of the print job Returns: string, the newly created job_id if successful, else None """ if cjt is None: cjt = {} else: cjt = cjt.val url = self.privet_url['createjob'] print 'Attempt to get a local job id for up to 30 seconds' t_end = time.time() + 30 while time.time() < t_end: r = self.transport.HTTPPost(url, data=dumps(cjt), headers=self.headers) if r is None or requests.codes.ok != r.status_code: return None res = r.json() if 'job_id' not in res: if 'error' in res and 'printer_busy' in res['error'].lower(): print( 'Printer is still busy, will try again in %s second(s)' % Constants.SLEEP['POLL']) Sleep('POLL') else: print 'Error: ', res['error'] return None else: print 'Got a job id\n' return res['job_id'] return None def SubmitDoc(self, job_id, title, content, content_type): """Second step for printing locally, submit a local print job to the printer Args: job_id: string, local job id that was returned by /createjob title: string, title of the print job content: string, url or absolute filepath of the item to print. content_type: string, MIME type of the print data Returns: int, the job id of the print job if successful, else None """ with open(content, 'rb') as f: content = f.read() url = (self.privet_url['submitdoc'] + '?job_id=%s&job_name=%s' % (job_id, title)) if content_type not in self.supported_types: print( 'This printer does not support the following content type: %s' % (content_type)) print 'List of supported types are: ', self.supported_types return None headers = self.headers # Get X-Privet_Token headers['Content-Type'] = content_type r = self.transport.HTTPPost(url, data=content, headers=headers) if r is None: return None res = r.json() return job_id if 'job_id' in res else None def JobState(self, job_id): """Optional Api that printers can implement to track job states Args: job_id: string, local job id Returns: dict, The response of the API call if succeeded, else None """ url = self.privet_url['jobstate'] + '?job_id=%s' % job_id r = self.transport.HTTPGet(url, headers=self.headers) if r is None or requests.codes.ok != r.status_code: return None return r.json() def Info(self): """Make call to the privet/info API to get the latest printer info Returns: dict, the info object if successful, else None """ response = self.transport.HTTPGet(self.privet_url['info'], headers=self.privet.headers_empty) if response is None: return None try: info = response.json() except ValueError: self.logger.error( 'Privet Info response does not contain JSON object') self.logger.debug('HTTP device return code: %s', response.status_code) self.logger.debug('HTTP Headers: ') for key in response.headers: self.logger.debug('%s: %s', key, response.headers[key]) return None else: return info def WaitForPrinterState(self, state, timeout=Constants.TIMEOUT['PRINTER_STATUS']): """Wait until the printer state becomes the specified status Args: state: string, printer state to wait for timeout: integer, number of seconds to wait. Returns: boolean, True if state is observed within timeout; otherwise, False. """ print '[Configurable timeout] PRINTER_STATUS:' print('Waiting up to %s seconds for the printer to have status: %s' % (timeout, state)) end = time.time() + timeout while time.time() < end: info = self.Info() if info is not None: if info['device_state'] == state: print 'Device state observed to be: %s' % state return True Sleep('POLL') return False def isPrinterRegistered(self): """Use the /privet/info interface to see if printer is registered Returns: boolean, True if registered, False if not registered, None if /privet/info failed """ info = self.Info() if info is not None: return info['id'] and info['connection_state'] == 'online' return None def assertPrinterIsRegistered(self): """Raise exception if printer is unregistered""" if not self.isPrinterRegistered(): print RedText('ERROR: Printer needs to be registered before this ' 'suite runs') raise EnvironmentError def assertPrinterIsUnregistered(self): """Raise exception if printer is registered""" if self.isPrinterRegistered(): print RedText( 'ERROR: Printer needs to be unregistered before this ' 'suite runs') raise EnvironmentError def CancelJob(self, job_id): #TODO: Posting a mismatch job seems to cancel the created job, # find a better way to do this url = self.privet_url['submitdoc'] + '?job_id=%s' % (job_id) headers = self.headers headers['Content-Type'] = 'image/pwg-raster' self.transport.HTTPPost(url, data=Constants.IMAGES['PDF13'], headers=headers)
class Device(object): """The basic device object.""" def __init__(self, logger, chromedriver, model=None, privet_port=None): """Initialize a device object. Args: logger: initialized logger object. chromedriver: initialized chromedriver object. model: string, unique model or name of device. privet_port: integer, tcp port devices uses for Privet protocol. """ if model: self.model = model else: self.model = Constants.PRINTER['MODEL'] self.logger = logger self.cd = chromedriver self.cloudprintmgr = CloudPrintMgr(logger, chromedriver) self.ipv4 = Constants.PRINTER['IP'] if privet_port: self.port = privet_port else: self.port = Constants.PRINTER['PORT'] self.name = Constants.PRINTER['NAME'] self.status = None self.messages = {} self.details = {} self.error_state = False self.warning_state = False self.cdd = {} self.info = None self.url = 'http://%s:%s' % (self.ipv4, self.port) self.logger.info('Device URL: %s', self.url) self.transport = Transport(logger) self.jparser = JsonParser(logger) self.headers = None self.privet = Privet(logger) self.privet_url = self.privet.SetPrivetUrls(self.ipv4, self.port) self.GetPrivetInfo() def GetPrivetInfo(self): self.privet_info = {} response = self.transport.HTTPReq(self.privet_url['info'], headers=self.privet.headers_empty) info = self.jparser.Read(response['data']) if info['json']: for key in info: self.privet_info[key] = info[key] self.logger.debug('Privet Key: %s', key) self.logger.debug('Value: %s', info[key]) self.logger.debug('--------------------------') if 'x-privet-token' in info: self.headers = {'X-Privet-Token': str(info['x-privet-token'])} else: if response['code']: self.logger.info('HTTP device return code: %s', response['code']) if response['headers']: self.logger.debug('HTTP Headers: ') for key in response['headers']: self.logger.debug('%s: %s', key, response['headers'][key]) if response['data']: self.logger.info('Data from response: %s', response['data']) def GetDeviceDetails(self): """Get the device details from our management page. This will populate a Device object with device name, status, state messages, and device details. """ RETRY_COUNT = 3 self.error_state = None self.warning_state = None self.status = None self.messages = None self.details = None for i in range(-1, RETRY_COUNT): self.cd.page_id = None if not self.error_state: self.error_state = self.cloudprintmgr.GetPrinterErrorState( self.name) if not self.warning_state: self.warning_state = self.cloudprintmgr.GetPrinterWarningState( self.name) if not self.status: self.status = self.cloudprintmgr.GetPrinterState(self.name) if not self.messages: self.messages = self.cloudprintmgr.GetPrinterStateMessages( self.name) if not self.details: self.details = self.cloudprintmgr.GetPrinterDetails(self.name) def GetDeviceCDD(self, device_id): """Get device cdd and populate device object with the details. Args: device_id: string, Cloud Print device id. Returns: boolean: True = cdd details populated, False = cdd details not populated. """ self.cd.Get(Constants.GCP['SIMULATE']) printer_lookup = self.cd.FindID('printer_printerid') if not printer_lookup: return False if not self.cd.SendKeys(device_id, printer_lookup): return False printer_submit = self.cd.FindID('printer_submit') if not self.cd.ClickElement(printer_submit): return False printer_info = self.cd.FindXPath('html') if not printer_info: return False self.info = printer_info.text self.ParseCDD() return True def ParseCDD(self): """Parse the CDD json string into a logical dictionary. Returns: boolean: True = CDD parsed, False = CDD not parsed. """ cdd = {} if self.info: cdd = json.loads(self.info) else: self.logger.warning('Device info is empty.') return False if 'printers' in cdd: for k in cdd['printers'][0]: if k == 'capabilities': self.cdd['caps'] = {} else: self.cdd[k] = cdd['printers'][0][k] else: self.logger.error('Could not find printers in cdd.') return False for k in cdd['printers'][0]['capabilities']['printer']: self.cdd['caps'][k] = cdd['printers'][0]['capabilities'][ 'printer'][k] return True def CancelRegistration(self): """Cancel Privet Registration that is in progress. Returns: return code from HTTP request. """ cancel_url = self.privet_url['register']['cancel'] self.logger.debug('Sending request to cancel Privet Registration.') response = self.transport.HTTPReq(cancel_url, data='', headers=self.headers, user=Constants.USER['EMAIL']) return response['code'] def StartPrivetRegister(self): """Start a device registration using the Privet protocol. Returns: boolean: True = success, False = errors. """ self.logger.debug('Registering device %s with Privet', self.ipv4) response = self.transport.HTTPReq(self.privet_url['register']['start'], data='', headers=self.headers, user=Constants.USER['EMAIL']) return self.transport.LogData(response) def GetPrivetClaimToken(self): """Attempt to get a Privet Claim Token. Returns: boolean: True = success, False = errors. """ self.logger.debug('Getting Privet Claim Token.') counter = 0 max_cycles = 5 # Don't loop more than this number of times. while counter < max_cycles: response = self.transport.HTTPReq( self.privet_url['register']['getClaimToken'], data='', headers=self.headers, user=Constants.USER['EMAIL']) self.transport.LogData(response) if 'token' in response['data']: self.claim_token = self.jparser.GetValue(response['data'], key='token') self.automated_claim_url = self.jparser.GetValue( response['data'], key='automated_claim_url') self.claim_url = self.jparser.GetValue(response['data'], key='claim_url') return True if 'error' in response['data']: self.logger.warning(response['data']) if 'pending_user_action' in response['data']: counter += 1 else: return False return False # If here, means unexpected condition, so return False. def SendClaimToken(self, auth_token): """Send a claim token to the Cloud Print service. Args: auth_token: string, auth token of user registering printer. Returns: boolean: True = success, False = errors. """ if not self.claim_token: self.logger.error('Error: device does not have claim token.') self.logger.error( 'Cannot send empty token to Cloud Print Service.') return False if not self.automated_claim_url: self.logger.error('Error: expected automated_claim_url.') self.logger.error('Aborting SendClaimToken()') return False response = self.transport.HTTPReq(self.automated_claim_url, auth_token=auth_token, data='', user=Constants.USER['EMAIL']) self.transport.LogData(response) info = self.jparser.Read(response['data']) if info['json']: if info['success']: return True else: return False else: return False def FinishPrivetRegister(self): """Complete printer registration using Privet. Returns: boolean: True = success, False = errors. """ self.logger.debug('Finishing printer registration.') response = self.transport.HTTPReq( self.privet_url['register']['complete'], data='', headers=self.headers, user=Constants.USER['EMAIL']) # Add the device id from the Cloud Print Service. info = self.jparser.Read(response['data']) if info['json']: for k in info: if 'device_id' in k: self.id = info[k] self.logger.debug('Registered with device id: %s', self.id) return self.transport.LogData(response) def UnRegister(self, auth_token): """Remove device from Google Cloud Service. Args: auth_token: string, auth token of device owner. Returns: boolean: True = success, False = errors. """ if self.id: delete_url = '%s/delete?printerid=%s' % ( Constants.AUTH['URL']['GCP'], self.id) response = self.transport.HTTPReq(delete_url, auth_token=auth_token, data='') else: self.logger.warning('Cannot delete device, not registered.') return False result = self.jparser.Validate(response['data']) if result: self.logger.debug('Successfully deleted printer from service.') self.id = None return True else: self.logger.error('Unable to delete printer from service.') return False def GetPrinterInfo(self, auth_token): """Get the printer capabilities stored on the service. Args: auth_token: string, auth token of device owner. Returns: boolean: True = success, False = errors. """ if self.id: printer_url = '%s/printer?printerid=%s&usecdd=True' % ( Constants.AUTH['URL']['GCP'], self.id) response = self.transport.HTTPReq(printer_url, auth_token=auth_token) else: self.logger.warning( 'Cannot get printer info, device not registered.') return False info = self.jparser.Read(response['data']) Extract(info, self.info) for k, v in self.info.iteritems(): self.logger.debug('%s: %s', k, v) self.logger.debug('=============================================') return True