[docs]classHTTPAssetConnection(AssetConnection):""" This class implements the asset connection for HTTP protocol. """def__init__(self):super().__init__()self.architecture_style=AssetConnection.ArchitectureStyle.CLIENTSERVER# Other dataself.interface_title=Noneself.base=Noneself.endpoint_metadata_elem=Noneself.security_scheme_elem=None# Data of each requestself.request_uri=Noneself.request_headers={}self.request_params=Noneself.request_method=Noneself.request_body=None
[docs]asyncdefconfigure_connection_by_aas_model(self,interface_aas_elem):# The Interface element need to be checkedawaitself.check_interface_element(interface_aas_elem)# Vamos a conseguir los datos necesarios del modelo AAS para configurar la conexion HTTPself.interface_title=interface_aas_elem.get_sm_element_by_semantic_id(AssetInterfacesInfo.SEMANTICID_INTERFACE_TITLE)# La informacion general de la conexion con el activo se define en el SMC 'EndpointMetadata'self.endpoint_metadata_elem=interface_aas_elem.get_sm_element_by_semantic_id(AssetInterfacesInfo.SEMANTICID_ENDPOINT_METADATA)# The endpointMetadata element need to be checkedawaitself.check_endpoint_metadata()self.base=self.endpoint_metadata_elem.get_sm_element_by_semantic_id(AssetInterfacesInfo.SEMANTICID_INTERFACE_BASE)content_type_elem=self.endpoint_metadata_elem.get_sm_element_by_semantic_id(AssetInterfacesInfo.SEMANTICID_INTERFACE_CONTENT_TYPE)ifcontent_type_elemisnotNone:self.request_headers['Content-Type']=content_type_elem.valuesecurity_definitions_elem=self.endpoint_metadata_elem.get_sm_element_by_semantic_id(AssetInterfacesInfo.SEMANTICID_INTERFACE_SECURITY_DEFINITIONS)ifsecurity_definitions_elemisnotNone:self.security_scheme_elem=security_definitions_elem.value# TODO: pensar como añadir el resto , p.e. tema de seguridad o autentificacion (bearer).# De momento se ha dejado sin seguridad (nosec_sc)# The InteractionMetadata elements also need to be checkedinteraction_metadata_elem=interface_aas_elem.get_sm_element_by_semantic_id(AssetInterfacesInfo.SEMANTICID_INTERACTION_METADATA)forinteraction_metadata_typeininteraction_metadata_elem:# Interaction metadata can be properties, actions or eventsforinteraction_elementininteraction_metadata_type:awaitself.check_interaction_metadata(interaction_element)
[docs]asyncdefexecute_asset_service(self,interaction_metadata,service_input_data=None):ifinteraction_metadataisNone:raiseAssetConnectionError("The skill cannot be executed by asset service because the given ""InteractionMetadata object is None","invalid method parameter","InteractionMetadata object is None")awaitself.extract_general_interaction_metadata(interaction_metadata)# Then, the data of the skill is added in the required field. To do that, the 'SkillParameterExposedThrough'# relationship should be obtained, which indicates where the parameter data should be addedifservice_input_dataisnotNone:awaitself.add_asset_service_data(interaction_metadata,service_input_data)# At this point, the HTTP request is performedhttp_response=awaitself.send_http_request()ifhttp_response:ifhttp_response.status!=200:_logger.warning("The HTTP request has not been answered correctly. ""Response: {}".format(awaithttp_response.text()))returnawaitself.get_response_content(interaction_metadata,awaithttp_response.text())returnNone
# ---------------------# HTTP specific methods# ---------------------
[docs]asyncdefextract_general_interaction_metadata(self,interaction_metadata):""" This method extracts the general interaction information from the interaction metadata object. Since this is an HTTP Asset Connection, information about the URI, headers and method name is obtained. All information is saved in the global variables of the class. Args: interaction_metadata (basyx.aas.model.SubmodelElementCollection): SubmodelElement of interactionMetadata. """# The interaction_metada element will be an SMC of the HTTP interface.awaitself.check_interaction_metadata(interaction_metadata)# First, the full URI of the HTTP request is obtained.forms_elem=interaction_metadata.get_sm_element_by_semantic_id(AssetInterfacesInfo.SEMANTICID_INTERFACE_FORMS)awaitself.get_complete_request_uri(forms_elem)# Then, headers are obtainedawaitself.get_headers(forms_elem)# Eventually, the method name is also obtainedawaitself.get_method_name(forms_elem)
[docs]asyncdefget_complete_request_uri(self,forms_elem):""" This method gets the complete request URI from the forms element within the InteractionMetadata element. The information is saved in the global variables of the class. Args: forms_elem (basyx.aas.model.submodelElementCollection): SubmodelElement of forms within InteractionMetadata. """href_elem=forms_elem.get_sm_element_by_semantic_id(AssetInterfacesInfo.SEMANTICID_INTERFACE_HREF)if('http://'inhref_elem.value)or('https://'inhref_elem.value):self.request_uri=href_elem.valueelse:self.request_uri=self.base.value+href_elem.value
[docs]asyncdefget_headers(self,forms_elem):""" This method gets the headers for the request from the forms element within the InteractionMetadata element. The information is saved in the global variables of the class. Args: forms_elem (basyx.aas.model.submodelElementCollection): SubmodelElement of forms within InteractionMetadata. """headers_elem=forms_elem.get_sm_element_by_semantic_id(HTTPAssetInterfaceSemantics.SEMANTICID_HTTP_INTERFACE_HEADERS)ifnotheaders_elem:# This Interaction element does not have headersreturn# The old request headers are updatedself.request_headers={}forheader_smcinheaders_elem:field_name=header_smc.get_sm_element_by_semantic_id(HTTPAssetInterfaceSemantics.SEMANTICID_HTTP_INTERFACE_FIELD_NAME).valuefield_value=header_smc.get_sm_element_by_semantic_id(HTTPAssetInterfaceSemantics.SEMANTICID_HTTP_INTERFACE_FIELD_VALUE).valueself.request_headers[field_name]=field_value
[docs]asyncdefget_method_name(self,forms_elem):""" This method gets the method name of the request from the forms element within the InteractionMetadata element. The information is saved in the global variables of the class. Args: forms_elem (basyx.aas.model.submodelElementCollection): SubmodelElement of forms within InteractionMetadata. """method_name_elem=forms_elem.get_sm_element_by_semantic_id(HTTPAssetInterfaceSemantics.SEMANTICID_HTTP_INTERFACE_METHOD_NAME)ifmethod_name_elem:self.request_method=method_name_elem.value
[docs]asyncdefadd_asset_service_data(self,interaction_metadata,service_input_data):""" This method adds the required data of the asset service, using the skill params information (exposure element and skill input data). The information is saved in the global variables of the class. Args: interaction_metadata (basyx.aas.model.SubmodelElementCollection): SubmodelElement of interactionMetadata. service_input_data (dict): dictionary containing the input data of the asset service. """forsubmodel_elementintraversal.walk_submodel(interaction_metadata):# TODO AQUI QUEDA COMPROBAR QUE EXISTE EL SEMANTICID PARA DETERMINAR DONDE HAY QUE AÑADIR LOS DATOS (hay que# pensar el nombre, pero algo estilo 'InputDataLocation')# TODO DE MOMENTO SE DEJA CON SOLO PARAMS DE HTTPifsubmodel_element.check_semantic_id_exist(HTTPAssetInterfaceSemantics.SEMANTICID_HTTP_INTERFACE_PARAMS):request_params={}param_name=submodel_element.get_sm_element_by_semantic_id(HTTPAssetInterfaceSemantics.SEMANTICID_HTTP_INTERFACE_PARAM_NAME).valuerequest_params[param_name]=service_input_data[param_name]self.request_params=request_paramsbreak# TODO pensar como se haria si no hay que añadirlo en los parametros (p.e. en el body)# TODO PENSAR EN MAS OPCIONES DE AÑADIR LOS PARAMETROS (P.E. EN LA URI)else:AssetConnectionError("The interface need input data but there is no location defined for it.",'Invalid interface SubmodelElement','MissingAttribute')
[docs]asyncdefsend_http_request(self):""" This method sends the required HTTP request message to the asset. All the required information is obtained from the global variables of the class. Returns: aiohttp.ClientResponse: response of the asset. """asyncwithaiohttp.ClientSession(headers=self.request_headers)assession:try:# async with session.get(url=self.request_uri, params=self.request_params) as resp:# await resp.text() # The content is saved in ClientResponse object# return respifself.request_method=='GET':response=awaitsession.get(url=self.request_uri,params=self.request_params)elifself.request_method=='DELETE':# TODO a probarresponse=awaitsession.delete(url=self.request_uri,params=self.request_params)elifself.request_method=='HEAD':# TODO a probarresponse=awaitsession.head(url=self.request_uri,params=self.request_params)elifself.request_method=='PATCH':# TODO a probarresponse=awaitsession.patch(url=self.request_uri,params=self.request_params)elifself.request_method=='POST':# TODO a probarresponse=awaitsession.post(url=self.request_uri,params=self.request_params)elifself.request_method=='PUT':# TODO a probarresponse=awaitsession.put(url=self.request_uri,params=self.request_params)awaitresponse.text()# The content is saved in ClientResponse objectreturnresponseexcept(ClientConnectorError,ClientConnectionError)asconnection_error:ifisinstance(connection_error,ClientConnectorError):raiseAssetConnectionError("The request to asset timed out, so the asset is not available.","AssetConnectTimeout","The asset connection timed out")ifisinstance(connection_error,ClientConnectionError):raiseAssetConnectionError("The connection with the asset has raised an exception.",connection_error.__class__.__name__,connection_error.args[0].reason)
[docs]classHTTPAssetInterfaceSemantics:""" This class contains the specific semanticIDs of HTTP interfaces. """SEMANTICID_HTTP_INTERFACE_METHOD_NAME='https://www.w3.org/2011/http#methodName'SEMANTICID_HTTP_INTERFACE_HEADERS='https://www.w3.org/2011/http#headers'SEMANTICID_HTTP_INTERFACE_FIELD_NAME='https://www.w3.org/2011/http#fieldName'SEMANTICID_HTTP_INTERFACE_FIELD_VALUE='https://www.w3.org/2011/http#fieldValue'# TODO nuevoSEMANTICID_HTTP_INTERFACE_PARAMS='https://www.w3.org/2011/http#params'SEMANTICID_HTTP_INTERFACE_PARAM_NAME='https://www.w3.org/2011/http#paramName'SEMANTICID_HTTP_INTERFACE_PARAM_VALUE='https://www.w3.org/2011/http#paramValue'