Skip to Content

Part 3: Automated re-ordering based on stock level

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/

Link to part 2.2 of the Blog Series – https://blogs.sap.com/2018/07/04/iot-prototype-with-business-rules-and-workflow-blog-series-2.24/

Let us get started with the configuration for the second scenario in the prototype. Configuration is required in various services of SCP for the completion of the IoT prototype.

Reordering of Material

This scenario covers the steps which can be used to set up automated reordering of material in a warehouse. There are numerous sensors available in the market to track the stock level of the raw material. When the stock goes below a certain level, the sensors can trigger a message to the IoT platform which in turn can decide the course of action depending on the rules set in the Business Rules. To demonstrate this, I have set up a couple of buttons in my Raspberry Pi and When the buttons are pressed (each button represents a different material type for simplicity), a message is sent to the SAP cloud platform and the order is generated automatically or sent to a user depending on the material type being sent.

Step 1- Device management: Set up message types, device types and device in Internet of things cockpit. => This step has been completed in the first part of the Blog series for all three scenarios.

The following 3 steps will be covered in this blog.

Step 2 – Business Rules: Create business rule services and set up rules.

Step 3 -Workflow: Create workflow and related user interfaces in Web IDE. (check part 2.2 for details)

Step 4 – IoT application: Create Java application to integrate all these services and Python code to send the sensor data from the device.

Make sure that the following services are enabled in SAP Cloud Platform Neo environment under your trial account.

  • Internet of Things
  • Business Rules
  • Workflow
  • Web IDE full stack

 

Step 2: Business Rules

Let us configure the business rules for this scenario.

Go to Business Rule Editor – https://bpmruleseditor-s000xxxxxxxtrial.dispatcher.hanatrial.ondemand.com/index.html

Create a project to hold the business rule service for the scenario.

 

Create two Data Objects

Input: with one attribute – materialtype

 

Output: with three attributes as below

ordermode – To indicate whether to place an order automatically or inform the user to place an order after confirmation.

qty – Quantity for reorder

ordertype – Type of Order

 

 

Create a Rule.

ReorderRule – This rule is constructed as a Decision Table. Depending on the material type received by the rule different order mode, order type and qty can be sent as output.

Create a RuleSet

ReorderSet – Add the Rule to the Rule set.

 

Create a Rule Service

ReorderService – This rule service takes the data object ‘Input’ as input and returns ‘Output’ as result.

 

Make sure that this rule service is assigned to the rule set. Activate all the objects created in the steps above. Do not forget to deploy the rule service.

The rule service will be called from the IoT application to validate the temperature/humidity from the sensors.

You can test the Business Rule Services by using Postman as shown in the part 2.1 of the blog series.

Step 3: Workflow and User Interfaces

User Interface – UI5 Application for Reorder

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 ‘Reorder Request for {/materialname}’ and design the screen as shown below. The values for all the variables 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 two action buttons which can be used to complete the workflow task. Code lines to complete the current task in the workflow are as below.

Workflow for Reorder

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

Create a Workflow project ‘ReOrderProcess’ and create a simple workflow with a single user task – ‘Re-Order Material’. 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.

Please refer part 2.2 of the blog series for the steps.

Step 4: IoT Application

Python code snippet written in Raspberry Pi

The reorder sensor sends 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()
try:
    
    while True:
        if GPIO.input(buttonPin) == False:
            print("low stock message sent")
            time = datetime.datetime.now().time()
            date = datetime.datetime.now().date()
            body= '{"mode":"async", "messageType":"' + str(config.message_type_id_LOW) + '", "messages":[{"Date":'+ ' "' + str(date) + '"'+ ',"Time":'+ ' "' + str(time) + '"'+ ',"Materialtype":'+ ' "M1" }]}'
            r = http.urlopen('POST', url, body=body, headers=headers)
         print(body)
        print(r.data)

           except KeyboardInterrupt:
GPIO.cleanup()

IoT application

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

  1. Read the reorder sensor from the device [use the device ID that we got while registering the device in Step 1]
  2. Sends the material type to Business rules to get the reorder details depending on the material type. If the order mode from the business rules is ‘auto’ then the order is placed automatically to the relevant system for the qty and the order type received from the business rules. If the order type from the business rules is ‘Manual’, then it triggers an instance of the corresponding workflow definition. This creates a user task in the inbox with the qty and order type received from the business rules.

 

Code snippet for Step a (for Reorder)

The material type data which comes from the Raspberry Pi is written into table T_LS_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.

 

/**
	 * read data from device
	 * 
	 * @return
	 */
	private String getMaterialTypefromDevice(String deviceid) {
		InitialContext ctx = null;
		DataSource ds = null;
		Connection con = null;
		String 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_MATERIALTYPE\" FROM \"SYSTEM\".\"T_LS_IOTMESSAGES\" WHERE \"G_DEVICE\" = '"+deviceid+"' ORDER BY \"G_CREATED\" desc");
			ResultSet rs = query.executeQuery();
			while (rs.next()) {
				result = rs.getString(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 material type read from Step a. to the Business Rule Engine to run it through the rules and either places the order or triggers the workflow.

/**
	 * Get the reorder type and details
	 * 
	 * @param materialtype
	 * @return
	 * @throws ClientProtocolException
	 * @throws IOException
	 */
	public String getReorderDetails(String materialname, String materialtype)
			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-s000xxxxxxxtrial.hanatrial.ondemand.com/";
			String xsrfTokenUrl = rulesRuntimeUrl + "rules-service/v1/rules/xsrf-token";
			String invokeUrl = rulesRuntimeUrl
					+ "rules-service/v1/rules/invoke?rule_service_name=Reorder::ReorderService";
			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__\":\"Input\",\"materialtype\":\"" + materialtype + "\"}";
			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);
			String ordermode = jsonObject.get("ordermode").asText();
			int qty = jsonObject.get("qty").asInt();
			String ordertype = jsonObject.get("ordertype").asText();
			if (ordermode.equalsIgnoreCase("Auto")) {
				return "Order created - Material: " + materialname + ", Order Type: " + ordertype + ",Quantity: " + qty;
			} else {
				// trigger workflow
				String id = "Order sent to workflow. Workflow instance started with ID "
						+ triggerManualReOrder(materialname, materialtype, ordertype, qty);
				return id;
			}
		} 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;
	}

	
	/**
	 * trigger the reorder process
	 * 
	 * @param temp
	 * @return
	 * @throws ClientProtocolException
	 * @throws IOException
	 */
	public String triggerManualReOrder(String materialname, String materialtype, String ordertype, int qty)
			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-s000xxxxxxtrial.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\":\"reorder\",\"context\":{ \"materialname\":\"" + materialname
					+ "\",\"materialtype\":\"" + materialtype + "\",\"ordertype\":\"" + ordertype + "\",\"qty\":" + qty
					+ "}}";

			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();
			}
		}
		
	}

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

response.getWriter().println(getReorderDetails("Test", getMaterialTypefromDevice(deviceID)));

Note that I have hardcoded the material name as ‘Test’ (just because I was too lazy to add this parameter to the IoT device configuration and send it from the sensor.  Be aware that there is no way to edit the message type/device type etc once it is created in the IoT cockpit. You have to delete and re-create in case of modifications. This will regenerate all the IDs as well which triggers changes in multiple places ☹)

Do not forget to deploy the Java application in SCP.

I have uploaded the complete java application for Reorder scenario in the link below.

 Java Code for the IoT Prototype –  Re-Order

Prototype in action

1. Switch on the Raspberry Pi and press the button for the material type ‘M1.

2.You can see the values getting stored in the table which is configured in the Service Mappings – T_LS_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/trackStock

You will see that the order is placed automatically (as per the business rules) and the message is displayed.

4. Repeat the above steps for material type ‘M2’.

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

You will see a workflow task in the user’s inbox with the alert.

Open the task to view the details

 

Second scenario is now complete. Please feel free to comment if you have any questions. See you in Part 4 with the final 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