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

Download

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

Download

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

Download

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

Download

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\"
          }