def get_node_platform_and_mac(port): """ Get Node Platform and Mac Addres from device :param port: Serial Port :type port: str :return: Node Platform and MAC Address on Success :rtype: str """ if not port: sys.exit( "<port> argument not provided. Cannot read MAC address from node.") sys.stdout = mystdout = StringIO() command = ['--port', port, 'chip_id'] log.info("Running esptool command to get node\ platform and mac from device") esptool.main(command) sys.stdout = sys.__stdout__ # Finding chip type from output. node_platform = next( filter(lambda line: 'Detecting chip type' in line, mystdout.getvalue().splitlines())) # Finds the first occurence of the line # with the MAC Address from the output. mac = next( filter(lambda line: 'MAC: ' in line, mystdout.getvalue().splitlines())) mac_addr = mac.split('MAC: ')[1].replace(':', '').upper() platform = node_platform.split()[-1].lower().replace('-', '') print("Node platform detected is: ", platform) print("MAC address is: ", mac_addr) log.debug("MAC address received: " + mac_addr) log.debug("Node platform is: " + platform) return platform, mac_addr
def get_node_config(self): """ Get node configuration. :raises NetworkError: If there is a network connection issue while getting node configuration :raises Exception: If there is an HTTP issue while getting node config :return: Configuration of node on Success :rtype: dict """ log.info("Getting node config for node : " + self.__nodeid) path = 'user/nodes/config' query_parameters = 'nodeid=' + self.__nodeid getnodeconfig_url = serverconfig.HOST + path + '?' + query_parameters try: log.debug("Get node config request url : " + getnodeconfig_url) response = requests.get(url=getnodeconfig_url, headers=self.__request_header, verify=configmanager.CERT_FILE) log.debug("Get node config response : " + response.text) response.raise_for_status() except requests.exceptions.SSLError: raise SSLError except requests.exceptions.ConnectionError: raise NetworkError except Exception: raise Exception(response.text) log.info("Received node config successfully.") return response.json()
def remove_node(vars=None): """ Removes the user node mapping. :param vars: `nodeid` as key - Node ID for the node, defaults to `None` :type vars: dict | None :raises NetworkError: If there is a network connection issue during HTTP request for removing node :raises Exception: If there is an HTTP issue while removing node or JSON format issue in HTTP response :return: None on Success :rtype: None """ log.info('Removing user node mapping for node ' + vars['nodeid']) try: n = node.Node(vars['nodeid'], session.Session()) params = n.remove_user_node_mapping() except Exception as remove_node_err: log.error(remove_node_err) else: log.debug('Removed the user node mapping successfully.') print('Removed node ' + vars['nodeid'] + ' successfully.') return
def flash_nvs_partition_bin(port, bin_to_flash, address): """ Flash binary onto node :param port: Serial Port :type port: str :param bin_to_flash: Filname of binary to flash :type bin_to_flash: str :raises Exception: If there is any issue when flashing binary onto node :return: None on Success :rtype: None """ try: if not port: sys.exit( "If you want to write the claim data to flash, please provide the <port> argument." ) print("Flashing binary onto node") log.info("Flashing binary onto node") command = ['--port', port, 'write_flash', address, bin_to_flash] esptool.main(command) except Exception as err: log.error(err) sys.exit(1)
def claim_initiate(claim_init_data, header): print("Claim initiate started") claim_initiate_url = CLAIM_INITIATE_URL try: # Claim Initiate Request log.info("Claim initiate started. Sending claim/initiate POST request") log.debug("Claim Initiate POST Request: url: " + claim_initiate_url + "data: " + str(claim_init_data) + "headers: " + str(header) + "verify: " + CERT_FILE) claim_initiate_response = requests.post(url=claim_initiate_url, data=claim_init_data, headers=header, verify=CERT_FILE) if claim_initiate_response.status_code != 200: log.error("Claim initiate failed.\n" + claim_initiate_response.text) exit(0) print("Claim initiate done") log.debug("Claim Initiate POST Response: status code: " + str(claim_initiate_response.status_code) + " and response text: " + claim_initiate_response.text) log.info("Claim initiate done") return claim_initiate_response except requests.exceptions.SSLError: raise SSLError except requests.ConnectionError: log.error("Please check the Internet connection.") exit(0)
def get_node_platform_and_mac(esptool, port): """ Get Node Platform and Mac Addres from device :param esptool: esptool module :type esptool: module :param port: Serial Port :type port: str :return: Node Platform and Mac Address on Success :rtype: str """ sys.stdout = mystdout = StringIO() command = ['--port', port, 'chip_id'] log.info("Running esptool command to get node\ platform and mac from device") esptool.main(command) sys.stdout = sys.__stdout__ # Finding chip type from output. node_platform = next( filter(lambda line: 'Detecting chip type' in line, mystdout.getvalue().splitlines())) # Finds the first occurence of the line # with the MAC Address from the output. mac = next( filter(lambda line: 'MAC: ' in line, mystdout.getvalue().splitlines())) mac_addr = mac.split('MAC: ')[1].replace(':', '').upper() platform = node_platform.split()[-1].lower() return platform, mac_addr
def signup_request(self, password): """ Sign up request of new User for ESP Rainmaker. :param password: Password to set for new user :type password: str :raises NetworkError: If there is a network connection issue during signup request :raises Exception: If there is an HTTP issue during signup request :return: True on Success :rtype: bool """ log.info("Creating new user with username : "******"password": password} signup_url = serverconfig.HOST + path try: log.debug("Signup request url : " + signup_url) response = requests.post(url=signup_url, data=json.dumps(signup_info), headers=self.__request_header, verify=configmanager.CERT_FILE) log.debug("Signup request response : " + response.text) response.raise_for_status() except requests.exceptions.SSLError: raise SSLError except requests.exceptions.ConnectionError: raise NetworkError except Exception: raise Exception(response.text) log.info("Signup request sent successfully.") return True
def get_access_token(self): """ Get Access Token for User :raises InvalidConfigError: If there is an issue in getting config from file :return: Access Token on Success :rtype: str """ _, _, access_token = self.get_config() if access_token is None: raise InvalidConfigError if self.__is_valid_token() is False: print('Previous Session expired. Initialising new session...') log.info('Previous Session expired. Initialising new session...') username = self.get_token_attribute('email') refresh_token = self.get_refresh_token() access_token, id_token = self.__get_new_token( username, refresh_token) self.update_config(access_token, id_token) print('Previous Session expired. Initialising new session...' 'Success') log.info('Previous Session expired. Initialising new session...' 'Success') return access_token
def start_claim_process(mac_addr, node_platform, private_key): log.info("Creating session") curr_session = session.Session() header = curr_session.request_header try: # Set claim initiate data claim_init_data = set_claim_initiate_data(mac_addr, node_platform) # Perform claim initiate request claim_init_resp = claim_initiate(claim_init_data, header) # Set claim verify data claim_verify_data, node_info = set_claim_verify_data( claim_init_resp, private_key) # Perform claim verify request claim_verify_resp = claim_verify(claim_verify_data, header) # Get certificate from claim verify response node_cert = json.loads(claim_verify_resp.text)['certificate'] print("Claim certificate received") log.info("Claim certificate received") return node_info, node_cert except requests.exceptions.SSLError: raise SSLError except requests.ConnectionError: log.error("Please check the Internet connection.") exit(0)
def signup(vars=None): """ User signup to the ESP Rainmaker. :param vars: `email` as key - Email address of the user, defaults to `None` :type vars: dict :raises Exception: If there is any issue in signup for user :return: None on Success :rtype: None """ log.info('Signing up the user ' + vars['email']) u = user.User(vars['email']) password = get_password() try: status = u.signup_request(password) except Exception as signup_err: log.error(signup_err) else: if status is True: verification_code = input('Enter verification code sent on your' 'Email.\n Verification Code : ') try: status = u.signup(verification_code) except Exception as signup_err: log.error(signup_err) return print('Signup Successful\n' 'Please login to continue with ESP Rainmaker CLI') else: log.error('Signup failed. Please try again.') return
def get_nodes(self): """ Get list of all nodes associated with the user. :raises NetworkError: If there is a network connection issue while getting nodes associated with user :raises Exception: If there is an HTTP issue while getting nodes :return: Nodes associated with user on Success :rtype: dict """ log.info("Getting nodes associated with the user.") path = 'user/nodes' getnodes_url = serverconfig.HOST + path try: log.debug("Get nodes request url : " + getnodes_url) response = requests.get(url=getnodes_url, headers=self.request_header, verify=configmanager.CERT_FILE) log.debug("Get nodes request response : " + response.text) response.raise_for_status() except requests.exceptions.SSLError: raise SSLError except requests.exceptions.ConnectionError: raise NetworkError except Exception: raise Exception(response.text) node_map = {} for nodeid in json.loads(response.text)['nodes']: node_map[nodeid] = node.Node(nodeid, self) log.info("Received nodes for user successfully.") return node_map
def get_password(): """ Get Password as input and perform basic password validation checks. :raises SystemExit: If there is an issue in getting password :return: Password for User on Success :rtype: str """ log.info('Doing basic password confirmation checks.') password_policy = '8 characters, 1 digit, 1 uppercase and 1 lowercase.' password_change_attempt = 0 print('Choose a password') while password_change_attempt < MAX_PASSWORD_CHANGE_ATTEMPTS: log.debug('Password change attempt number ' + str(password_change_attempt + 1)) password = getpass.getpass('Password : '******'Password should contain at least', password_policy) password_change_attempt += 1 continue confirm_password = getpass.getpass('Confirm Password : '******'Passwords do not match!\n' 'Please enter the password again ..') password_change_attempt += 1 log.error('Maximum attempts to change password over. Please try again.') sys.exit(1)
def start_ota(self, node_obj, node_params, service_name, service_write_params, url_to_set): """ Start OTA Service Set new url as node params :param node_obj: Node Object :type node_obj: object :param node_params: Node Params :type node_params: dict :param service_name: Service Name :type service_name: str :param url_to_set: New URL to set in node params :type url_to_set: str :return: True on Success, False on Failure :type: bool """ global start_time start_time = None ota_url_key = service_write_params[OTA_PARAMS['url']] log.debug("OTA URL Key : " + str(ota_url_key)) log.debug("Setting new url: " + str(url_to_set)) params_to_set = {service_name: {ota_url_key: url_to_set}} log.debug("New node params after setting url: " + json.dumps(params_to_set)) set_node_status = node_obj.set_node_params(params_to_set) if not set_node_status: return False start_time = time.time() log.debug("Start time set to: " + str(start_time)) print("OTA Upgrade Started. This may take time.") log.info("OTA Upgrade Started. This may take time.") return True
def signup(self, code): """ Sign up of new User for ESP Rainmaker. :param code: Verification code received in signup request for user :type code: int :raises NetworkError: If there is a network connection issue during signup :raises Exception: If there is an HTTP issue during signup :return: True on Success :rtype: bool """ log.info("Confirming user with username : "******"verification_code": code} signup_url = serverconfig.HOST + path try: log.debug("Confirm user request url : " + signup_url) response = requests.post(url=signup_url, data=json.dumps(signup_info), headers=self.__request_header, verify=configmanager.CERT_FILE) log.debug("Confirm user response : " + response.text) response.raise_for_status() except requests.exceptions.SSLError: raise SSLError except requests.exceptions.ConnectionError: raise NetworkError except Exception: raise Exception(response.text) log.info("Signup successful.") return True
def get_node_status(self): """ Get online/offline status of the node. :raises NetworkError: If there is a network connection issue while getting node status :raises Exception: If there is an HTTP issue while getting node status :return: Status of node on Success :rtype: dict """ log.info("Getting online/offline status of the node : " + self.__nodeid) path = 'user/nodes/status' query_parameters = 'nodeid=' + self.__nodeid getnodestatus_url = serverconfig.HOST + path + '?' + query_parameters try: log.debug("Get node status request url : " + getnodestatus_url) response = requests.get(url=getnodestatus_url, headers=self.request_header, verify=configmanager.CERT_FILE) log.debug("Get node status response : " + response.text) response.raise_for_status() except requests.exceptions.SSLError: raise SSLError except requests.exceptions.ConnectionError: raise NetworkError except Timeout as time_err: log.debug(time_err) raise RequestTimeoutError except Exception: raise Exception(response.text) log.info("Received node status successfully.") return response.json()
def claim_verify(claim_verify_data, header): claim_verify_url = CLAIM_VERIFY_URL user_whitelist_err_msg = ('User is not allowed to claim esp32 device.' ' please contact administrator') claim_verify_enc_data = str(claim_verify_data).replace("'", '"') log.debug("Claim Verify POST Request: url: " + claim_verify_url + "data: " + str(claim_verify_enc_data) + "headers: " + str(header) + "verify: " + CERT_FILE) claim_verify_response = requests.post(url=claim_verify_url, data=claim_verify_enc_data, headers=header, verify=CERT_FILE) if claim_verify_response.status_code != 200: claim_verify_response_json = json.loads( claim_verify_response.text.lower()) if (claim_verify_response_json["description"] in user_whitelist_err_msg): log.error('Claim verification failed.\n' + claim_verify_response.text) print('\nYour account isn\'t whitelisted for ESP32.' ' Please send your registered email address to' ' [email protected] for whitelisting') else: log.error('Claim verification failed.\n' + claim_verify_response.text) exit(0) print("Claim verify done") log.debug("Claim Verify POST Response: status code: " + str(claim_verify_response.status_code) + " and response text: " + claim_verify_response.text) log.info("Claim verify done") return claim_verify_response
def get_mqtt_endpoint(): # Set node claim data sys.stdout = StringIO() log.info("Getting MQTT Host") endpointinfo = node.get_mqtt_host(None) log.debug("Endpoint info received: " + endpointinfo) sys.stdout = sys.__stdout__ return endpointinfo
def __init__(self, username): """ Instantiate user with username. """ log.info("Initialising user " + username) self.__username = username self.__passwd_change_token = '' self.__request_header = {'content-type': 'application/json'}
def set_params(vars=None): """ Set parameters of the node. :param vars: `nodeid` as key - Node ID for the node,\n `data` as key - JSON data containing parameters to be set `or`\n `filepath` as key - Path of the JSON file containing parameters to be set,\n defaults to `None` :type vars: dict | None :raises Exception: If there is an HTTP issue while setting params or JSON format issue in HTTP response :return: None on Success :rtype: None """ log.info('Setting params of the node with nodeid : ' + vars['nodeid']) data = vars['data'] filepath = vars['filepath'] if data is not None: log.debug('Setting node parameters using JSON data.') # Trimming white spaces except the ones between two strings data = re.sub( r"(?<![a-z]|[A-Z])\s(?![a-z]|[A-Z])|\ (?<=[a-z]|[A-Z])\s(?![a-z]|[A-Z])|\ (?<![a-z]|[A-Z])\s(?=[a-z]|[A-Z])", "", data) try: log.debug('JSON data : ' + data) data = json.loads(data) except Exception: raise InvalidJSONError return elif filepath is not None: log.debug('Setting node parameters using JSON file.') file = Path(filepath) if not file.exists(): log.error('File %s does not exist!' % file.name) return with open(file) as fh: try: data = json.load(fh) log.debug('JSON filename :' + file.name) except Exception: raise InvalidJSONError return try: n = node.Node(vars['nodeid'], session.Session()) status = n.set_node_params(data) except Exception as set_params_err: log.error(set_params_err) else: print('Node state updated successfully.') return
def flash_existing_data(port, bin_to_flash, address): # Flashing existing binary onto node if not port: sys.exit( "If you want to write the claim data to flash, please provide the <port> argument." ) log.info("Using existing claiming data") print("Using existing claiming data") flash_nvs_partition_bin(port, bin_to_flash, address) log.info("Binary flashed onto node")
def __init__(self, node, device): """ Instantiate device object. """ log.info("Initialising device " + device['name']) self.__node = node self.__name = device['name'] self.__params = {} for param in device['params']: self.__params[param["name"]] = ''
def logout(self): """ Logout current logged-in user :raises SSLError: If there is an SSL issue :raises HTTPError: If the HTTP response is an HTTPError :raises NetworkError: If there is a network connection issue :raises Timeout: If there is a timeout issue :raises RequestException: If there is an issue during the HTTP request :raises Exception: If there is an HTTP issue while logging out or JSON format issue in HTTP response :return: HTTP response on Success :rtype: dict """ socket.setdefaulttimeout(10) log.info('Logging out current logged-in user') version = serverconfig.VERSION path = '/logout' # Logout only from current session query_params = 'logout_all=false' logout_url = serverconfig.HOST.rstrip('/') + path + '?' + query_params try: log.debug("Logout request url : " + logout_url) log.debug("Logout headers: {}".format(self.request_header)) response = requests.post(url=logout_url, headers=self.request_header, verify=configmanager.CERT_FILE, timeout=(5.0, 5.0)) log.debug("Logout request response : " + response.text) except requests.exceptions.HTTPError as http_err: log.debug(http_err) return json.loads(http_err.response.text) except requests.exceptions.SSLError: raise SSLError except requests.exceptions.ConnectionError: raise NetworkError except requests.exceptions.Timeout as time_err: log.debug(time_err) raise RequestTimeoutError except requests.exceptions.RequestException as req_err: log.debug(req_err) raise req_err except Exception: raise Exception(response.text) try: log.info("Logout API call successful") return json.loads(response.text) except Exception as resp_err: raise resp_err
def login(self, password=None): """ User login to the ESP Rainmaker. :param password: Password of user, defaults to `None` :type password: str :raises NetworkError: If there is a network connection issue during login :raises Exception: If there is an HTTP issue during login or JSON format issue in HTTP response :raises AuthenticationError: If login failed with the given parameters :return: :class:`rmaker_lib.session.Session` on Success :rtype: object """ log.info("User login with username : "******"Login request url : " + login_url) response = requests.post(url=login_url, data=json.dumps(login_info), headers=self.__request_header, verify=configmanager.CERT_FILE) log.debug("Login response : " + response.text) response.raise_for_status() except requests.exceptions.SSLError: raise SSLError except requests.exceptions.ConnectionError: raise NetworkError except Exception: raise Exception(response.text) try: result = json.loads(response.text) except Exception as json_decode_err: raise json_decode_err if 'status' in result and result['status'] == 'success': log.info("Login successful.") config_data = {} config_data['idtoken'] = result['idtoken'] config_data['refreshtoken'] = result['refreshtoken'] config_data['accesstoken'] = result['accesstoken'] configmanager.Config().set_config(config_data) return session.Session() raise AuthenticationError
def logout(vars=None): """ Logout of the current session. :raises Exception: If there is any issue in logout for user :return: None on Success and Failure :rtype: None """ log.info('Logging out current logged-in user') # Removing the creds stored locally log.debug("Removing creds stored locally, invalidating token") config = configmanager.Config() # Get email-id of current logged-in user email_id = config.get_token_attribute('email') log.info('Logging out user: {}'.format(email_id)) # Ask user for ending current session # Get user input input_resp = config.get_input_to_end_session(email_id) if not input_resp: return # Call Logout API try: curr_session = session.Session() status_resp = curr_session.logout() log.debug("Logout API successful") except Exception as logout_err: log.error(logout_err) return # Check API status in response if 'status' in status_resp and status_resp['status'] == 'failure': print("Logout from ESP RainMaker Failed. Exiting.") print("[{}]:{}".format(status_resp['error_code'], status_resp['description'])) return # Remove current login creds ret_val = config.remove_curr_login_creds() if ret_val is None: print("Logout from ESP RainMaker Failed. Exiting.") return # Logout is successful print("Logged out from ESP RainMaker") log.debug('Logout Successful') log.debug("Local creds removed successfully") return
def __is_valid_token(self): """ Check if access token is valid i.e. login session is still active or session is expired :return True on Success and False on Failure :rtype: bool """ log.info("Checking for session timeout.") exp_timestamp = self.get_token_attribute('exp', is_access_token=True) current_timestamp = int(time.time()) if exp_timestamp > current_timestamp: return True return False
def set_claim_verify_data(claim_init_resp, private_key): # Generate CSR with common_name=node_id received in response node_id = str(json.loads(claim_init_resp.text)['node_id']) print("Generating CSR") log.info("Generating CSR") csr = gen_host_csr(private_key, common_name=node_id) if not csr: raise Exception("CSR Not Generated. Claiming Failed") log.info("CSR generated") claim_verify_data = {"csr": csr} # Save node id as node info to use while saving claim data # in csv file node_info = node_id return claim_verify_data, node_info
def __init__(self, nodeid, session): """ Instantiate node with nodeid and session object. """ log.info("Initialising node with nodeid : " + str(nodeid)) self.__nodeid = nodeid self.__session = session try: self.request_header = {'content-type': 'application/json', 'Authorization': session.id_token} except AttributeError: raise InvalidClassInput(session, 'Invalid Session Input.\ Expected: type <session object>.\ Received: ')
def get_user_details(self): """ Get details of current logged-in user :raises SSLError: If there is an SSL issue :raises HTTPError: If the HTTP response is an HTTPError :raises NetworkError: If there is a network connection issue :raises Timeout: If there is a timeout issue :raises RequestException: If there is an issue during the HTTP request :raises Exception: If there is an HTTP issue while getting user details or JSON format issue in HTTP response :return: HTTP response on Success :rtype: dict """ socket.setdefaulttimeout(10) log.info('Getting details of current logged-in user') version = serverconfig.VERSION path = '/user' getdetails_url = serverconfig.HOST.rstrip('/') + path try: log.debug("Get user details request url : " + getdetails_url) response = requests.get(url=getdetails_url, headers=self.request_header, verify=configmanager.CERT_FILE, timeout=(5.0, 5.0)) log.debug("Get user details request response : " + response.text) except requests.exceptions.HTTPError as http_err: log.debug(http_err) return json.loads(http_err.response.text) except requests.exceptions.SSLError: raise SSLError except requests.exceptions.ConnectionError: raise NetworkError except requests.exceptions.Timeout as time_err: log.debug(time_err) raise RequestTimeoutError except requests.exceptions.RequestException as req_err: log.debug(req_err) raise req_err except Exception: raise Exception(response.text) log.info("Received user details successfully.") try: return json.loads(response.text) except Exception as resp_err: raise resp_err
def __init__(self): """ Instantiate session for logged in user. """ config = configmanager.Config() log.info("Initialising session for user " + config.get_token_attribute('email')) self.id_token = config.get_access_token() if self.id_token is None: raise InvalidConfigError self.request_header = { 'Content-Type': 'application/json', 'Authorization': self.id_token }
def __is_valid_version(self): """ Check if API Version is valid :raises NetworkError: If there is a network connection issue during HTTP request for getting version :raises Exception: If there is an HTTP issue or JSON format issue in HTTP response :return: True on Success, False on Failure :rtype: bool """ socket.setdefaulttimeout(10) log.info("Checking for supported version.") path = 'apiversions' request_url = serverconfig.HOST.split(serverconfig.VERSION)[0] + path try: log.debug("Version check request url : " + request_url) response = requests.get(url=request_url, verify=CERT_FILE, timeout=(5.0, 5.0)) log.debug("Version check response : " + response.text) response.raise_for_status() except requests.exceptions.SSLError: raise SSLError except requests.exceptions.Timeout: raise RequestTimeoutError except requests.exceptions.ConnectionError: raise NetworkError except Exception as ver_err: raise ver_err try: response = json.loads(response.text) except Exception as json_decode_err: raise json_decode_err if 'supported_versions' in response: supported_versions = response['supported_versions'] if serverconfig.VERSION in supported_versions: supported_versions.sort() latest_version = supported_versions[len(supported_versions) - 1] if serverconfig.VERSION < latest_version: print('Please check the updates on GitHub for newer' 'functionality enabled by ' + latest_version + ' APIs.') return True return False