3.2. Events and Callbacks¶
Bridge API allows applications and third party systems to be notified about events via HTTP or MQTT. For event notifications, applications or third party systems must register. To do this, a callback via POST must be created, specifying the type of event about which the application or third party system is to be notified and the URL at which it can be reached. For notification via MQTT, a topic has to be defined that must always begin with “external” in order to be distinguished from the topics used internally in the IoT platform:
"url": "mqtt://{host}:1883/external/{any topic}"
Third party systems that are not to be informed about events via MQTT, but via HTTP, require their own HTTP server. In this case, the notification callback itself is an HTTP POST request with JSON data that is sent to the URL of the HTTP server to be specified as follows:
"url": "http://{host}:{port}/{path}"
3.2.1. Callbacks¶
3.2.1.1. Notification Request to the External Webservice¶
Whenever an event which matches a existing registration occurs, the event data is sent via a HTTP POST request to the given URL.
3.2.1.2. Retries¶
If a callback notification fails (for example if the system which provides the registered endpoint is down), retries will be triggered every 10 seconds until the maximum amount of retries is reached. When the maximum amount of retries is reached the callback notification will be discarded and no further retries for this notification will be sent. Retries implicate that notifications might not be sent in order. Therefore the timestamp of the event which triggered the callback notification has to be considered by the receiving system. When the maximum amount of failed callback notifications is reached, the oldest notification will be discarded even if the maximum amount of retries for the notification is not reached yet.
3.2.1.3. Creating a Callback¶
In order to be notified about events, a callback must be created. This is done by calling POST callbacks with the following content in the request body:
{
"objectFilter": [
{
"name": "string",
"value": "string"
}
],
"eventType": "COMMAND",
"eventName": "string",
"url": "string",
"maxRedeliverAttempts": 0,
"maxUnconfirmedMessages": 0
}
At least two specifications must be passed in the body of the request: The event type to be informed about and the URL to which the information about an event is to be sent.
Events that refer to a particular type of primary resource can be restricted to a subset or a single primary resource when creating the callback. For this purpose, object filters are used, which are passed as an array consisting of name-value pairs. The “name” specifies a property of the reference object. “Value” is a value that the property must have in order for the filter criterion to apply. In the current version 2, only the property “id” is supported, i.e. the reference objects of an event type can only be filtered by UUIDs.
Optionally, an event name can be given to the events specified by the callback.
Java
Example using HTTP show/hide
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 final ICallbackClient callbackClient = api.getCallbackClient(); callbackClient.addListener(new AbstractOperationPhaseCommandListener() { @Override public void handle(EventModel<OperationPhaseCommandDataWsModel> eventModel) { OperationPhaseCommandDataWsModel data = eventModel.getData(); /* process received data */ } }); try { callbackClient.startCallbackHttpServer(8081); } catch (IOException e) { LOGGER.error(e.getMessage()); } try { CreateCallbackRequest callbackRequest = new CreateCallbackRequest(); callbackRequest.setEvent(Event.COMMAND__OPERATION_PHASE); callbackRequest.setMaxRedeliveryAttempts(5); callbackRequest.setMaxUnconfirmedMessages(5); callbackRequest.setUrl("http://localhost:8081/callbacks"); callbackClient.createCallback(callbackRequest); } catch (ForceAPIException e) { LOGGER.error(e.getMessage()); }
Example using MQTT show/hide
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 String callbackId = null; final ICallbackClient callbackClient = api.getCallbackClient(); callbackClient.addListener(new AbstractOperationPhaseCommandListener() { @Override public void handle(EventModel<OperationPhaseCommandDataWsModel> eventModel) { OperationPhaseCommandDataWsModel data = eventModel.getData(); /* process received data */ String operationId = data.getOperationId(); OperationPhase operationPhaseId = data.getOperationPhaseId(); switch (operationPhaseId) { case RELEASED: case DISPATCHED: case SETUP: case TRAINING: case PROCESSING: case INTERRUPTED: case COMPLETED: case CLOSED: LOGGER.info("Operation [{}] is in phase {}", operationId, operationPhaseId); break; default: LOGGER.warn("Unknown Operation Phase!"); } } @Override public void connectionLost(CallbackProvider callbackProvider) { /* The connection lost event can only be captured with the stateful mqtt, remember http is stateless! */ switch (callbackProvider) { case MQTT: break; default: LOGGER.warn("Unknown Callback Provider!"); } } }); try { MqttConfiguration config = new MqttConfiguration("tcp://localhost:1883"); callbackClient.connectToMqttBroker(config); } catch (MqttException e) { LOGGER.error(e.getMessage()); } CreateCallbackRequest callbackRequest = new CreateCallbackRequest(); callbackRequest.setEvent(Event.COMMAND__OPERATION_PHASE); final CallbackObjectFilterWSModel objectFilter = new CallbackObjectFilterWSModel(); objectFilter.setName("id"); objectFilter.setValue("DA892519507444768080F0E81879513"); request.setObjectFilter(Collections.singletonList(objectFilter)); callbackRequest.setUrl("mqtt://third-party-system:1883/external/OperationPhaseChanged"); CallbackResponse callbackResponse = null; try { callbackResponse = callbackClient.createCallback(callbackRequest); } catch (ForceAPIException e) { LOGGER.error(e.getMessage()); } assert callbackResponse != null; callbackId = callbackResponse .getProperties() .getId();
CURL
Example using CURL show/hide
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | curl -X POST http://$HOST:$PORT/ffwebservice/api/v2/callbacks \
--header "Content-Type: application/json" \
--header "Accept: application/json" \
--header "Authorization: Bearer $TOKEN" \
-d '{
"objectFilter": [
{
"name": "id",
"value": "DA892519507444768080F0E81879513”"
}
],
"eventName": "Operation 100 has changed phase",
"eventType": "OPERATION_PHASE_CHANGED",
"url": “http://third-party-system:24080/webserver”
}
|
Example using MQTT show/hide
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 curl -X POST http://$HOST:$PORT/ffwebservice/api/v2/callbacks \ --header "Content-Type: application/json" \ --header "Accept: application/json" \ --header "Authorization: Bearer $TOKEN" \ -d '{ "objectFilter": [ { "name": "id", "value": "DA892519507444768080F0E81879513" } ], "eventName": "Operation 100 has changed phase", "eventType": "OPERATION_PHASE_CHANGED", "url": "mqtt://third-party-system:1883/external/OperationPhaseChanged" }
3.2.1.4. Get all Registered Callbacks¶
To get all registered callbacks a request has to be send to callbacks/ via GET
Example: show/hide
Java
1 2 3 | CollectionRequest getCallBackRequest = new CollectionRequest();
//FIXME Apparently there is no GetCallbackRequest Object. CollectionRequest is to generic to set any 'body'!
Page<CallbackResponse> callbackResponsePage = callbackClient.getCallbacks(getCallBackRequest);
|
CURL
1 2 3 | curl -X GET "http://$HOST:$PORT/ffwebservice/api/v1/callbacks/$CALLBACK_UUID" \
--header "Content-Type: application/json" \
--header "Accept: application/json" \
|
3.2.1.5. Delete a Registered Callback¶
To delete a registered callback a request has to be send to
callbacks/{callbackId} via DELETE
where callbackId
is the ID of the callback.
Example: show/hide
Java
1 2 | DeleteCallbackRequest deleteCallbackRequest = new DeleteCallbackRequest(callbackUUID);
callbackClient.deleteCallback(deleteCallbackRequest);
|
CURL
1 2 3 | curl -X DELETE "http://$HOST:$PORT/ffwebservice/api/v1/callbacks/$CALLBACK_UUID" \
--header "Content-Type: application/json" \
--header "Accept: application/json" \
|
3.2.2. Event Types¶
Below you can find all event types for which a callback registration is possible.
Applications usually only need the following events, which are provided as condensed information by the IoT platform as a result of certain individual events:
Event Type | Object Reference | Description |
OBSOLETE_PREDICTED_MALFUNCTION_SCENARIOS | The predicted malfunction scenarios are obsolete,
i.e. no longer up to date due to other changes.
|
|
OBSOLETE_OPERATION_PLANNING_SCENARIOS | All planning scenarios are obsolete
due to other events and changes that have occurred.
|
|
OBSOLETE_OPERATION_FORECAST_RESULTS | The forecast result is obsolete,
i.e. can no longer be realized due to other changes.
|
|
RESOURCES_UPDATED | A change has occurred in the primary resources so that
either a primary resource has been created or deleted
or its characteristic properties have been changed.
|
In addition, numerous events are generated, which inform about details:
Event Type | Object Reference | Description |
WORKPLACE_SHIFTS_UPDATED | Workplace | One or more work shifts have been created,
deleted or changed.
|
SCHEDULED_MAINTENANCE_CREATED | Workplace | A new scheduled maintenance task was planned. |
SCHEDULED_MAINTENANCE_UPDATED | Workplace | A scheduled maintenance task
has been changed.
|
SCHEDULED_MAINTENANCE_DELETED | Workplace | A scheduled maintenance task was deleted. |
OPERATION_PLANNING_RESULT_CREATED | Operation | A planning result was created for
newly released operations.
|
OPERATION_PLANNING_RESULT_UPDATED | Operation | The planning result for one or more operations
has been changed.
|
OPERATION_PLANNING_RESULT_DELETED | Operation | The planning result for an operation was deleted. |
OPERATION_FORECAST_RESULT_CREATED | Operation | A forecast result was created for
newly released operations.
|
OPERATION_FORECAST_RESULT_UPDATED | Operation | The forecast result of one or more operations
has been changed.
|
OPERATION_FORECAST_RESULT_DELETED | Operation | The forecast result of an operation was deleted. |
STAFF_MEMBER_PLANNING_RESULT_CREATED | Staff Member | One or more staff members have been
dispatched to a workplace for the first time
during one of their scheduled working times.
|
STAFF_MEMBER_PLANNING_RESULT_UPDATED | Staff Member | The dispatching of one or more staff members
to a workplace has been changed.
|
STAFF_MEMBER_PLANNING_RESULT_DELETED | Staff Member | The dispatching of a staff members to a
workplace has been deleted.
|
OPERATION_PLANNING_SCENARIO_CREATED | A new planning scenario for operations
has been created.
|
|
STAFF_MEMBER_PLANNING_SCENARIO_CREATED | A new planning scenario for staff members
has been created.
|
|
PREDICTED_MALFUNCTION_SCENARIO_CREATED | Workplace | A new predicted malfunction scenario
has been created.
|
OPERATION_PHASE_CHANGED | Operation | An operation has changed phase. |
WORKPLACE_OPERATING_STATE_CHANGED | Workplace | A workplace has changed operating state. |
STAFF_MEMBER_ACTIVITY_CHANGED | Staff Member | A staff member has finished an activity,
interrupted it, or started a new activity.
|
TOOL_ASSEMBLY_ORDER_STATE_CHANGED | Tool Assembly Order | The state of a tool assembly order has changed. |
WORKPLACE_SHIFT_CHANGED | Workplace Shift | A workplace shift has changed
(e.g. from early shift to late shift)
|
Event Type | Object Reference | Description |
OPERATION_PHASE_CHANGED | Operation | An operation has changed phase |
WORKPLACE_OPERATING_STATE_CHANGED | Workplace | A workplace has changed operating state |
STAFF_MEMBER_ACTIVITY_CHANGED | Staff Member | A staff member has finished an activity,
interrupted it, or started a new activity.
|
TOOL_ASSEMBLY_ORDER_STATE_CHANGED | Tool Assembly Order | The state of a tool assembly order has changed. |
Event Type | Object Reference | Description |
OPERATION_CREATED | A new operation has been released. Usually, this is the operation of a
production order released by the ERP system, but it can also be
a new operation created in order management.
|
|
Operation | The characteristic properties of an operation have been changed.
This includes, among other things, information for the functional
identification of the operation and its specification, but not the phase
change of an operation.
|
|
OPERATION_DELETED | Operation | An operation was deleted |
PRODUCTION_ORDER_CREATED | A new production order has been created or released by the ERP system. | |
PRODUCTION_ORDER_UPDATED | Production Order | The characteristic properties of a production order have been changed. |
PRODUCTION_ORDER_DELETED | Production Order | A production order has been deleted |
WORKPLACE_CREATED | A new workplace has been created | |
WORKPLACE_UPDATED | Workplace | The characteristic properties of a workplace have been changed.
Exceptions to this are changes to the planned operating times or the
operating state of a workplace.
|
WORKPLACE_DELETED | Workplace | A workplace has been deleted. |
STAFF_MEMBER_CREATED | A new staff member has been created. | |
STAFF_MEMBER_UPDATED | Staff Member | The characteristics of a staff member have been changed. This includes,
for example, that a staff member’s skills or assigned workplace have
changed, but not that the employee has finished his or her current
activity or started a new one.
|
STAFF_MEMBER_DELETED | Staff Member | A staff member has been deleted |
TOOL_CREATED | A new tool has been created | |
TOOL_UPDATED | Staff Member | The characteristic properties of a tool have been changed. Exceptions to
this are changes to the tool condition, its location and its remaining
service life.
|
TOOL_DELETED | Staff Member | A tool has been deleted |
DOCUMENT_CREATED | Document | A new document has been created. |
DOCUMENT_UPDATED | Document | The document has been modified. |
DOCUMENT_DELETED | Document | The document has been deleted. |
The event types COMMAND
, SHOP_FLOOR_TERMINAL
and INTERNAL
are
reserved for internal events of the IoT platform.
3.2.3. Receiving Events¶
If an event occurs (in this case an OPERATION_PHASE _CHANGED event) for which a third party system has registered via HTTP callback, the HTTP server of the third party system receives, e.g., the following POST request:
<curl -X POST
--header 'Content-Type: application/json'
-d '{
"properties" : {
"callbackId" : "B1B1BFB60ECF4DB7B095F096992FB9FA",
"timestamp" : "2019-03-06T11:04:16.786Z",
"data" : {
"currentOperationPhase" : "PROCESSING",
"previousOperationPhase" : "INTERRUPTED",
"operationId" : "2A05AAEF15FD41A8A4A36C45516871AA"
},
"objectId" : "DA892519507444768080F0E81879513",
"objectType" : "OPERATION",
"eventType" : "OPERATION_PHASE_CHANGED",
"eventName" : "Operation 100 has changed phase"
},
"_links" : {
"callback" : {
"method" : "GET",
"embeddable" : true,
"href" : "http://localhost:24080/ffwebservices/api/v2/callbacks/B1B1BFB60ECF4DB7B095F096992FB9FA"
}
}
}' 'CALLBACK_URL'>
It is possible to configure Basic Auth authentication for callbacks. If an event occurs for which an application has registered via MQTT, the application receives a MQTT message.
Example in Java:
show/hide
JAVA
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 | ////////////////////////////////////////////////////////////////////////////////
//
// Title: Simple detailed order planning
// Created: 24.09.2018
// Author: David Zeise
// Source: https://forcebridge.io
// License: CC BY 3.0 (https://creativecommons.org/licenses/by/3.0/)
//
////////////////////////////////////////////////////////////////////////////////
package com.forcam.usage.callback;
import com.forcam.common.extension.BridgeAPIProvider;
import com.forcam.na.ffwebservices.client.BridgeAPI;
import com.forcam.na.ffwebservices.client.api.callback.CallbackProvider;
import com.forcam.na.ffwebservices.client.api.callback.EventModel;
import com.forcam.na.ffwebservices.client.api.callback.ICallbackClient;
import com.forcam.na.ffwebservices.client.api.callback.MqttConfiguration;
import com.forcam.na.ffwebservices.client.api.callback.listener.AbstractOperationPhaseCommandListener;
import com.forcam.na.ffwebservices.client.api.callback.request.CreateCallbackRequest;
import com.forcam.na.ffwebservices.client.api.exception.ForceAPIException;
import com.forcam.na.ffwebservices.model.callback.CallbackResponse;
import com.forcam.na.ffwebservices.model.command.operation.OperationPhaseCommandDataWsModel;
import com.forcam.na.ffwebservices.model.definition.OperationPhase;
import com.forcam.na.ffwebservices.model.event.Event;
import org.eclipse.paho.client.mqttv3.MqttException;
import org.junit.jupiter.api.*;
import org.junit.jupiter.api.extension.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
*
*/
@ExtendWith(BridgeAPIProvider.class)
class CallbackCreateMQTTTest {
// ------------------------------------------------------------------------
// constants
// ------------------------------------------------------------------------
private static final Logger LOGGER = LoggerFactory.getLogger(CallbackCreateMQTTTest.class);
// ------------------------------------------------------------------------
// tests
// ------------------------------------------------------------------------
@Test
void test(BridgeAPI api) {
//<editor-fold example="1" desc="Create a callback, that uses MQTT">
String callbackId = null;
final ICallbackClient callbackClient = api.getCallbackClient();
callbackClient.addListener(new AbstractOperationPhaseCommandListener() {
@Override
public void handle(EventModel<OperationPhaseCommandDataWsModel> eventModel) {
OperationPhaseCommandDataWsModel data = eventModel.getData();
/* process received data */
String operationId = data.getOperationId();
OperationPhase operationPhaseId = data.getOperationPhaseId();
switch (operationPhaseId) {
case RELEASED:
case DISPATCHED:
case SETUP:
case TRAINING:
case PROCESSING:
case INTERRUPTED:
case COMPLETED:
case CLOSED:
LOGGER.info("Operation [{}] is in phase {}", operationId, operationPhaseId);
break;
default:
LOGGER.warn("Unknown Operation Phase!");
}
}
@Override
public void connectionLost(CallbackProvider callbackProvider) {
/* The connection lost event can only be captured with the stateful mqtt, remember http is stateless! */
switch (callbackProvider) {
case MQTT:
break;
default:
LOGGER.warn("Unknown Callback Provider!");
}
}
});
try {
MqttConfiguration config = new MqttConfiguration("tcp://localhost:1883");
callbackClient.connectToMqttBroker(config);
} catch (MqttException e) {
LOGGER.error(e.getMessage());
}
CreateCallbackRequest callbackRequest = new CreateCallbackRequest();
callbackRequest.setEvent(Event.COMMAND__OPERATION_PHASE);
final CallbackObjectFilterWSModel objectFilter = new CallbackObjectFilterWSModel();
objectFilter.setName("id");
objectFilter.setValue("DA892519507444768080F0E81879513");
request.setObjectFilter(Collections.singletonList(objectFilter));
callbackRequest.setUrl("mqtt://third-party-system:1883/external/OperationPhaseChanged");
CallbackResponse callbackResponse = null;
try {
callbackResponse = callbackClient.createCallback(callbackRequest);
} catch (ForceAPIException e) {
LOGGER.error(e.getMessage());
}
assert callbackResponse != null;
callbackId = callbackResponse
.getProperties()
.getId();
//</editor-fold>
}
}
|
3.2.4. Sending Self-Defined Events to Applications or Third Party Systems¶
An application can send events to other applications or third-party systems. To do this, POST events must be called with the following content in the request body:
{
"data": {},
"eventName": "string",
"eventType": "EXTERNAL",
"objectId": "string",
"objectType": "OPERATION",
"timestamp": "2019-02-23T17:42:59.429Z"}
In the body of the request, the event type must always be EXTERNAL and the object type must have one of the following values: OPERATION, PRODUCTION_ORDER, WORKPLACE, STAFF_MEMBER, TOOL or can be empty.
Any amount of information can be transferred with the event in the Data field. The event name and the object ID are optional.
Example: show/hide
Java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | BridgeAPI bridgeAPI = new BridgeAPI(url, user,secret);
IEventClient eventClient = bridgeAPI.getEventClient();
final Map<String, String> data = new HashMap<>();
data.put("myData1", "123");
data.put("myData2", "456");
final EventWSModel eventData = new EventWSModel();
eventData.setData(data);
eventData.setEventName("MY_EVENT");
eventData.setEventType(EventType.EXTERNAL);
eventData.setObjectId("4DDE054C036C4F8CAB424C7FB70E86B9");
eventData.setObjectType(EventObjectType.TOOL);
final PostEventRequest request = new PostEventRequest(eventData);
eventClient.postEvent(request);
|
CURL
1 2 3 4 5 6 7 8 9 10 11 12 13 | curl -X POST https://$HOST:$PORT/ffwebservice/api/v2/events" -H "accept: */*" -H "authorization: Bearer 0ffd19e6-15a5-42fc-b080-86ccadacc168" -H "Content-Type: application/json" -d "{ \"data\": {\"myData1\" : \"123\", \"myData2\" : \"456\"}, \"eventName\": \"MY_EVENT\", \"eventType\": \"INTERNAL\", \"objectId\": \"4DDE054C036C4F8CAB424C7FB70E86B9\", \"objectType\": \"TOOL\", \"timestamp\": \"2019-03-01T07:24:37.756Z\"}events
-H "accept: */*"
-H "authorization: Bearer $TOKEN
-H "Content-Type: application/json"
-d "{
\"data\": {\"myData1\" : \"123\", \"myData2\" : \"456\"},
\"eventName\": \"MY_EVENT\",
\"eventType\": \"EXTERNAL\",
\"objectId\": \"4DDE054C036C4F8CAB424C7FB70E86B9\",
\"objectType\": \"TOOL\",
\"timestamp\": \"2019-03-01T07:24:37.756Z\"
}
|