Skip to Content

Part 2.2: Remote temperature and humidity control

Link to part 1 of the Blog Series – https://blogs.sap.com/2018/06/19/iot-prototype-with-business-rules-and-workflow-blog-series-14/

Link to part 2.1  of the Blog Series – https://blogs.sap.com/2018/06/28/iot-prototype-with-business-rules-and-workflow-blog-series-24/

Let us continue with the configurations to complete the first scenario.

As per the process, if the temperature/humidity exceeds the value maintained as part of the business rules, a workflow is triggered to the user. The user receives a notification in his/her inbox and takes appropriate action. This is the simplest scenario. The workflow can be designed to send messages directly to appropriate devices which in turn can control the temperature/humidity.

Step 3: Workflow and User interfaces

User Interface – UI5 Application for Temperature Control

Let us first create the user interface which will be displayed to the user when the workflow is triggered.

Open SAP Web IDE (Full Stack) [ URL – https://webidecp-sXXXXXXXXXXtrial.dispatcher.hanatrial.ondemand.com/ ]

Create a simple UI5 application with an XML view as below. Set the page title to ‘Alert for {/devicename}’ and place a text field in the user area with the text set to ‘{/text}’. The values for the variables ‘devicename’ and ‘text’ will be set in the context of the UI5 application by the workflow.

Code mentioned below (in Component.js) is used to set the context variables in the model of the UI5 application.

This piece of code also adds an action button which can be used to complete the workflow task. Code lines to complete the current task in the workflow are as below.

 

Workflow for temperature control

Make sure that the workflow editor feature is enabled in SAP Web IDE.

 

Create a Workflow project ‘TempAlertProcess’ and create a simple workflow with a single user task – ‘Check Temperature Settings’. This workflow will be triggered from the IoT Java application.

 

 

This user task is linked to the UI5 application created in the step above through the task properties shown below. Choose the HTML5 App Name from the drop down and populate the SAP UI5 Component ID.

 

Assign your S-ID as the recipient to the user task.

 

Similar to Business Rules, the workflow can be tested by using one of the following methods

1.       Use the default apps available in the Fiori Launchpad to create a new instance. Make sure that the context parameters – device name, warning text – are passed.

2.       Use Postman application to trigger the workflow.

Let us use the second option.

Testing Workflow using Postman

As we did in the Business Rules, first fetch XSRF token.

Get the XSRF token

Send HTTP request to (URL – https://bpmworkflowruntimewfs-s000xxxxxxxtrial.hanatrial.ondemand.com/workflow-service/rest/v1/xsrf-token ) get the XSRF token required to invoke the Workflow Service. Do not forget to set the authorisation to basic and give your SCP trial account user/password.

Trigger the workflow

Create the workflow instance by passing the XSRF token in the header.

URL – https://bpmworkflowruntimewfs-s000xxxxxxxtrial.hanatrial.ondemand.com/workflow-service/rest/v1/workflow-instances

 

Pass the context parameters as shown below.

Note that the ‘definition Id’ which is passed as input can be found in the workflow properties as shown below.

 

Workflow instance gets triggered and ‘Instance Id’ is returned.

 

You can see the triggered instance in the ‘Workflow Instances’ app of the Fiori launchpad [ https://flpportal-s00xxxxxxxtrial.dispatcher.hanatrial.ondemand.com ].

Same steps (User Interface and Workflow) can be repeated to create user interface and workflow for Humidity control process.

Step 4: IoT Application

Python code snippet written in Raspberry Pi

This is my favourite step in the prototype. Here is the picture of the circuit which I set up. I have used a temperature/humidity sensor, a button and a LED.

The temperature and humidity sensor send the values to SCP using the code below. Note that you would require the following values from the IoT cockpit to send sensor values from the device to SCP.

 SCP account ID, Host name, Device ID, message type id, Authorization token for the device 

http = urllib3.PoolManager()
headers = urllib3.util.make_headers(user_agent=None)
headers['Authorization'] = 'Bearer ' + config.oauth_credentials_for_device
headers['Content-Type'] = 'application/json;charset=utf-8'
url='https://iotmms' + config.hcp_account_id + config.hcp_landscape_host + '/com.sap.iotservices.mms/v1/api/http/data/'+ str(config.device_id)
urllib3.disable_warnings()
    while True:
        humidity, temperature = Adafruit_DHT.read_retry(11, 4)
        print (temperature, humidity)
        time = datetime.datetime.now().time()
        date = datetime.datetime.now().date()
        body= '{"mode":"async", "messageType":"' + str(config.message_type_id_TH) + '", "messages":[{"Date":'+ ' "' + str(date) + '"'+ ',"Time":'+ ' "' + str(time) + '"'+ ',"Temperature":'+ ' "' + str(temperature) + '"'+ ',"Humidity":'+ ' "' + str(humidity) + '" }]}'
        r = http.urlopen('POST', url, body=body, headers=headers)
        print(body)
        print(r.data)

IoT application

This is the ‘glue’ application which integrates Step 1, Step 2 and Step 3. The application performs the following steps.

a.       Read the Temperature/Humidity from the device [use the device ID that we got while registering the device in Step 1]

b.       Sends the value to Business rules to check if they exceed the set limit.

c.       If the result from the business rules is true (that is, the values have exceeded the set limit), trigger an instance of the corresponding workflow definition. This creates a user task in the inbox.

 Steps b. & c. are very similar to the process steps that were done in Postman except that they are done using Java code.

Code snippet for Step a (for Temperature control).

The temperature and humidity data which comes from the Raspberry Pi is written into table T_TH_IOTMESSAGES as per the service mappings which were done towards the end of Step 1.

Note: if you have not done the process service mappings, then use the default message table name created by the IoT device.

private Double getTempfromDevice(String deviceid) {
		InitialContext ctx = null;
		DataSource ds = null;
		Connection con = null;
		Double result = null;
		try {
			ctx = new InitialContext();
			ds = (DataSource) ctx.lookup("java:comp/env/jdbc/default");
			con = ds.getConnection();
			PreparedStatement query = con.prepareStatement(
					"SELECT  TOP 1 \"C_TEMPERATURE\" FROM \"SYSTEM\".\"T_TH_IOTMESSAGES\" WHERE \"G_DEVICE\" = '"+deviceid+"' ORDER BY \"G_CREATED\" desc");
			ResultSet rs = query.executeQuery();
			while (rs.next()) {
				result = rs.getDouble(1);
			}
		} catch (SQLException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (NamingException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		return result;

	}

 

Code snippet for Step b.

The code lines below send the temperature value read from Step a. to the Business Rule Engine to run it through the rules.

public Boolean checkTemp(Double temp) throws ClientProtocolException, IOException {
		HttpContext httpContext = new BasicHttpContext();
		httpContext.setAttribute(HttpClientContext.COOKIE_STORE, new BasicCookieStore());

		HttpPost httpPost = null;
		CloseableHttpResponse response = null;
		CloseableHttpClient httpClient = null;
		try {
			httpClient = getHTTPClient();
			String rulesRuntimeUrl = "https://bpmrulesruntimebpm-s000xxxxxtrial.hanatrial.ondemand.com/";
			String xsrfTokenUrl = rulesRuntimeUrl + "rules-service/v1/rules/xsrf-token";
			String invokeUrl = rulesRuntimeUrl
					+ "rules-service/v1/rules/invoke?rule_service_name=TemperatureHumidityCheck::TExceededService";
			httpPost = new HttpPost(invokeUrl);

			httpPost.addHeader("Content-type", "application/json");
			String xsrfToken = getXSRFToken(xsrfTokenUrl, httpClient, httpContext);
			if (xsrfToken != null) {
				httpPost.addHeader("X-CSRF-Token", xsrfToken);
			}
			// replace value of authorizationHeader with base64 encoded value of
			// “<user-name>:<password>”
			String authorizationHeader = "<base64 encoded value of <user-name>:<password>>";
			httpPost.addHeader("Authorization", "Basic " + authorizationHeader);

			// construct input data to the rules service
			String fact = "{ \"__type__\":\"measures\",\"temperature\":" + temp + ",\"humidity\":" + 50 + "}";
			StringEntity stringEntity = new StringEntity(fact);
			httpPost.setEntity(stringEntity);

			response = httpClient.execute(httpPost, httpContext);
			// process your response here
			ByteArrayOutputStream bytes = new ByteArrayOutputStream();
			InputStream inputStream = response.getEntity().getContent();
			byte[] data = new byte[1024];
			int length = 0;
			while ((length = inputStream.read(data)) > 0) {
				bytes.write(data, 0, length);
			}
			String respBody = new String(bytes.toByteArray(), "UTF-8");
			// The respBody is a JSON and parse is to get discount
			ObjectMapper objectMapper = new ObjectMapper();
			JsonNode jsonObject = objectMapper.readValue(respBody, JsonNode.class);
			return jsonObject.get("exceeded").asBoolean();
		} finally {
			if (httpPost != null) {
				httpPost.releaseConnection();
			}
			if (response != null) {
				response.close();
			}
			if (httpClient != null) {
				httpClient.close();
			}
		}
	}

/**
	 * Gets the xsrf token for business rules/workflow
	 * 
	 * @param requestURL
	 * @param client
	 * @param httpContext
	 * @return
	 * @throws ClientProtocolException
	 * @throws IOException
	 */
	private String getXSRFToken(String requestURL, CloseableHttpClient client, HttpContext httpContext)
			throws ClientProtocolException, IOException {
		HttpGet httpGet = null;
		CloseableHttpResponse response = null;
		String xsrfToken = null;
		String authorizationHeader = null;
		try {
			httpGet = new HttpGet(requestURL);
			// replace value of authorizationHeader with base64 encoded value of
			// “<user-name>:<password>”
			authorizationHeader = "<base64 encoded value of <user-name>:<password>>";
			httpGet.addHeader("Authorization", "Basic " + authorizationHeader);
			httpGet.addHeader("X-CSRF-Token", "Fetch");
			response = client.execute(httpGet, httpContext);
			// Fetch the token from response and return
			Header xsrfTokenheader = response.getFirstHeader("X-CSRF-Token");
			if (xsrfTokenheader != null) {
				xsrfToken = xsrfTokenheader.getValue();
			}
		} finally {
			if (httpGet != null) {
				httpGet.releaseConnection();
			}
			if (response != null) {
				response.close();
			}
		}
		return xsrfToken;
	}

 

Code for Snippet c.

The code lines mentioned below are used to trigger the workflow. Make sure that you pass the correct workflow definition ID in the context parameters. Both steps b. and c. use the same function ‘getXSRFToken()’ to get XSRF token before sending the actual request.

I have used the IoT APIs to read the device name for the given device ID. This is used to display the device name which has caused this alert to the user in the workflow task. The Java function to get the device name is given below.

/**
 * Checks whether the temp alert message needs to be triggered by reading
 * the business rules
 * 
 * @param temp
 * @return
 * @throws ClientProtocolException
 * @throws IOException
 */
	public String triggerTempAlertProcess(String devicename, Double temp) throws ClientProtocolException, IOException {
		HttpContext httpContext = new BasicHttpContext();
		httpContext.setAttribute(HttpClientContext.COOKIE_STORE, new BasicCookieStore());

		HttpPost httpPost = null;
		CloseableHttpResponse response = null;
		CloseableHttpClient httpClient = null;
		try {
			httpClient = getHTTPClient();
			String rulesRuntimeUrl = "https://bpmworkflowruntimewfs-s000xxxxtrial.hanatrial.ondemand.com/";
			String xsrfTokenUrl = rulesRuntimeUrl + "workflow-service/rest/v1/xsrf-token";
			String invokeUrl = rulesRuntimeUrl + "workflow-service/rest/v1/workflow-instances";
			httpPost = new HttpPost(invokeUrl);

			httpPost.addHeader("Content-type", "application/json");
			String xsrfToken = getXSRFToken(xsrfTokenUrl, httpClient, httpContext);
			if (xsrfToken != null) {
				httpPost.addHeader("X-CSRF-Token", xsrfToken);
			}
			// replace value of authorizationHeader with base64 encoded value of
			// “<user-name>:<password>”
			String authorizationHeader = "<base64 encoded value of <user-name>:<password>>";
			httpPost.addHeader("Authorization", "Basic " + authorizationHeader);

			// construct input data to the workflow service to create instance
			String fact = "{ \"definitionId\":\"temperaturealert\",\"context\":{ \"devicename\":\"" + devicename
					+ "\",\"text\":\"Temperature has risen above the set limit. Please check the device\"}}";

			StringEntity stringEntity = new StringEntity(fact);
			httpPost.setEntity(stringEntity);

			response = httpClient.execute(httpPost, httpContext);
			// process your response here
			ByteArrayOutputStream bytes = new ByteArrayOutputStream();
			InputStream inputStream = response.getEntity().getContent();
			byte[] data = new byte[1024];
			int length = 0;
			while ((length = inputStream.read(data)) > 0) {
				bytes.write(data, 0, length);
			}
			String respBody = new String(bytes.toByteArray(), "UTF-8");
			// The respBody is a JSON and parse is to get discount
			ObjectMapper objectMapper = new ObjectMapper();
			JsonNode jsonObject = objectMapper.readValue(respBody, JsonNode.class);
			return jsonObject.get("id").asText();

		} finally {
			if (httpPost != null) {
				httpPost.releaseConnection();
			}
			if (response != null) {
				response.close();
			}
			if (httpClient != null) {
				httpClient.close();
			}
		}
	}
	
/**
	 * Reads device name for the device id using Remote Device management API
	 * 
	 * @param deviceid
	 * @return
	 */
	private String getDevicename(String deviceid) throws ClientProtocolException, IOException {
		String devicename = null;
		HttpGet httpGet = null;
		String authorizationHeader = null;
		CloseableHttpResponse response = null;
		CloseableHttpClient httpClient = null;
		HttpContext httpContext = new BasicHttpContext();
		httpContext.setAttribute(HttpClientContext.COOKIE_STORE, new BasicCookieStore());

		try {

			httpClient = getHTTPClient();
			String RDMSUrl = "https://iotrdmsiotservices-s00xxxxxxtrial.hanatrial.ondemand.com/com.sap.iotservices.dms/v2/api";
			String invokeUrl = RDMSUrl + "/devices/" + deviceID;

			httpGet = new HttpGet(invokeUrl);
			// replace value of authorizationHeader with base64 encoded value of
			// “<user-name>:<password>”

			authorizationHeader = "<base64 encoded value of <user-name>:<password>>";			httpGet.addHeader("Authorization", "Basic " + authorizationHeader);
			httpGet.addHeader("X-CSRF-Token", "Fetch");
			response = httpClient.execute(httpGet, httpContext);

			ByteArrayOutputStream bytes = new ByteArrayOutputStream();
			InputStream inputStream = response.getEntity().getContent();
			byte[] data = new byte[1024];
			int length = 0;
			while ((length = inputStream.read(data)) > 0) {
				bytes.write(data, 0, length);
			}
			String respBody = new String(bytes.toByteArray(), "UTF-8");

			ObjectMapper objectMapper = new ObjectMapper();
			JsonNode jsonObject = objectMapper.readValue(respBody, JsonNode.class);
			return jsonObject.get("name").asText();

		} finally {
			if (response != null) {
				response.close();
			}
			if (httpClient != null) {
				httpClient.close();
			}
		}
	}

Finally, the glue code which calls all these functions is as below.

Double temp = getTempfromDevice(deviceID);
if (checkTemp(temp)) {
// 1. Get device name from where the alert has come
	String devicename = getDevicename(deviceID);
// 2.trigger alert process
	String instanceID = triggerTempAlertProcess(devicename, temp);
	response.getWriter().println("Temperature alert process triggered with instance " + instanceID);
		} else {
	response.getWriter().println("Temperature under control " + getTempfromDevice(deviceID));
		}

Do not forget to deploy the Java application in SCP.

You can replicate the same code for the humidity control process as well. I have uploaded the complete java application for Temperature and Humidity control in the link below.

 Java Code for the IoT Prototype – Temperature and Humidity

 

Prototype in action

Now comes the moment of truth.

1.       Switch on the Raspberry Pi and send the temperature values to SCP.

2.       You can see the values getting stored in the table which is configured in the Service Mappings – T_TH_IOTMESSAGES

3.       Run the Java servlet with the glue code. In an Ideal prototype this part will be scheduled to run in the background. In this prototype, it reads the most recent value from the table and takes action accordingly.

URL – https://iotapplications000XXXXtrial.hanatrial.ondemand.com/IoTApplication/trackTH

 

4.       You will see the Workflow Instance for the workflow definition  in the Fiori Launch Pad.

 

You will see a workflow task in the users inbox with the alert.

Open the task to view the details

First scenario is now complete. Please feel free to comment if you have any questions. See you in Part 3 with the next scenario.

To report this post you need to login first.

Be the first to leave a comment

You must be Logged on to comment or reply to a post.

Leave a Reply