4.7. Plugins - Messaging

SmartMES hat ein eingebautes Messaging-System, um über verschiedene Aktionen oder auch auch andere Events informiert zu werden. Auch eigene Nachrichten können gesendet werden.

Inhalt dieser Beschreibung basiert auf Version 17.2.273

Einführung

Das Message-System ist über den zentralen ActionService verfügbar:

SmartMES.getActionService()

Alle Events werden über ein Listener-Muster verfügbar gemacht. 

Ein Event, dass durch das System geschickt wird, ist immer ein ActionEvent und hat mindestens folgende Felder:

public interface ActionEvent {
 public ActionEventType getType();
 public String getTitle();
 public String getDescription();
 public String getTopic();
 public ActionEventPriority getPriority();
}

  • Ein Titel beschreibt das Event. Z.B.: "Fertigungsauftrag angelegt"
  • Eine Beschreibung, die genauere Details gibt "Ein neuer Fertigungsauftrag wurde angelegt"
  • Ein Topic, das hauptsächlich für die Publish-Subscribe-Funktion in Bluelytics genutzt wird. Details weiter unten.
  • Eine Priorität, um z.B. einfache Info-Nachrichten von Warnungen unterscheiden zu können.
  • Einen ActionEventType, aufgrund dessen dieses Event erzeugt wurde

Das ActionEventType gibt an, was für eine Art von ActionEvent diese Nachricht ist. Dieser ActionEventType entspricht dem Typen, den man auch abonniert.

Möchte man z.B. über alle Events informiert werden, wenn ein Fertigungsauftrag erstellt wurde, dann abonniert man entsprechend den Event-Typen:

ProductionOrderAction.ProductionOrderCreated

Wird nun ein Fertigungsauftrag erstellt, so erzeugt das System ein Event mit dem ActionEventType "ProductionOrderAction.ProductionOrderCreated".

Somit gibt es für verschiedene Arten von Events auch verschiedene Event-Typen. Event-Typen können dabei unterschiedliche Arten von spezifischen Events erzeugen. 

So erzeugen z.B. alle zu ProductionOrderAction gehörigen Events (z.B. ProductionOrderAction.ProductionOrderCreated oder ProductionOrderAction.ProductionOrderApproved) konkret Events vom Typ "ProductionOrderActionEvent".

Ein ProductionOrderActionEvent ist dabei dann immer ein ActionEvent (s.o.), stellt aber zusätzlich weitere Informationen bereit. Wie z.B. den erstellten Fertigungsauftrag. Beispiele hierzu weiter unten.

Des Weiteren ist es möglich, beliebige Nachrichten (CustomEvents) zu senden oder zu empfangen.

Nachrichten abonnieren

Um Nachrichten zu abonnieren, muss das Interface 

ActionEventListener

implementiert werden und es dann beim ActionService registrieren. Hierbei gibt man den Event-Typen, den man abonnieren möchte. Treten dann Events zu diesem Typen auf, wird die actionPerformed-Methode mit dem zu dem Event-Typen gehörigen Event aufgerufen. 

Beispiel, bei dem z.B. ein Device registriert wird, dass den Event-Typen "ProductionOrderAction.ProductionOrderCreated" abonniert:

public class DeviceSmartwatch extends DeviceInstance implements ActionEventListener {
 
	public DeviceSmartwatch(){
		// registriere den Listener, um über Fertigungsaufträge informiert zu werden
		SmartMES.getActionService().addListener(this, ProductionOrderAction.ProductionOrderCreated);
	}
 
	@Override
	public void actionPerformed(ActionEvent event) {
		System.out.println("Event received: "+event.getTitle());
		// event ist immer vom Typ ProductionOrderActionEvent, da wir nur ProductionOrderAction-Typen abonniert haben
        ProductionOrderActionEvent productionEvent = (ProductionOrderActionEvent)event;
		System.out.println("Erstellt wurde: "+productionEvent.getProductionOrder().getNumber());
	}
}

Da alle Event-Typen von ProductionOrderAction, also auch ProductionOrderAction.ProductionOrderCreated, Events vom Typ ProductionOrderActionEvent erzeugen, wird actionPerformed entsprechend mit einem "ProductionOrderActionEvent" als "event" aufgerufen.

Man kann dabei mehrere Typen gleichzeitig abonnieren. 

public class DeviceSmartwatch extends DeviceInstance implements ActionEventListener {
 
	public DeviceSmartwatch(){
		// registriere den Listener, um über Fertigungsaufträge informiert zu werden
		SmartMES.getActionService().addListener(this, ProductionOrderAction.ProductionOrderCreated, ProductionOrderAction.ProductionOrderApproved);
	}
 
	@Override
	public void actionPerformed(ActionEvent event) {
		System.out.println("Event received: "+event.getTitle());
		// event ist immer vom Typ ProductionOrderActionEvent, da wir nur ProductionOrderAction-Typen abonniert haben
        ProductionOrderActionEvent productionEvent = (ProductionOrderActionEvent)event;
        if(event.getType().equals(ProductionOrderAction.ProductionOrderCreated)) {
    		System.out.println("Erstellt wurde: " + productionEvent.getProductionOrder().getNumber());
		}
		if(event.getType().equals(ProductionOrderAction.ProductionOrderApproved)) {
		    System.out.println("Bestätigt wurde: " + productionEvent.getProductionOrderId());
		}
	}
}

Natürlich kann das Abonnieren auch über eine anonyme Klasse gemacht werden:

SmartMES.getActionService().addListener(new ActionEventListener() {
    @Override
    public void actionPerformed(ActionEvent event) {
        System.out.println("Fertigungsauftrag bestätigt");
    }
}, ProductionOrderAction.ProductionOrderApproved);

Und mit einem Java 8 Lambda-Ausdruck entsprechend:

SmartMES.getActionService().addListener(event -> System.out.println("Fertigungsauftrag bestätigt"), ProductionOrderAction.ProductionOrderApproved);

Wenn nur ein Event-Type abonniert wird, kann man alternativ auch den Typen konkret verwenden, indem statt "addListener" die Methode "addTypedListener" verwendet wird. 

Damit erhält man in der actionPerformed-Methode bereits den richtigen Event-Typen (hier also ein ProductionOrderActionEvent):

SmartMES.getActionService().addTypedListener(new ActionEventListener<ProductionOrderActionEvent>() {
    @Override
    public void actionPerformed(ProductionOrderActionEvent event) {
        System.out.println("Fertigungsauftrag "+event.getProductionOrder().getNumber();
    }
}, ProductionOrderAction.ProductionOrderCreated);

Bzw. als Lambda-Ausdruck:

SmartMES.getActionService().addTypedListener(event -> System.out.println("Fertigungsauftrag "+event.getProductionOrder().getNumber()+" erstellt"), ProductionOrderAction.ProductionOrderCreated);

Hierbei müssen alle Event-Typen dieselben Events erzeugen, sodass hier nicht Typen von ProductionOrderAction (erzeugt ProductionOrderActionEvent) mit BomAction (erzeugt BomActionEvent) gemischt werden können!

Standard Aktionen

Das System erzeugt verschiedene Standard-Aktionen, die im Wesentlichen vor und nach dem Aufruf der meisten Services ausgeführt werden.

Wird z.B. über ProductionOrderService ein Auftrag erstellt, dann wird (auch durch die GUI) folgende Methode verwendet:

SmartMES.getService().productionOrders().createProductionOrder(aNewOrder);

Die meisten solcher Funktionen haben entsprechende ActionEventTypen, die abonniert werden können. Dabei gibt es jeweils eine, die vor der Methode aufgerufen werden und eine danach. Hier z.B. 

ProductionOrderAction.ProductionOrderBeforeCreated

bevor der Auftrag vom System erstellt und gespeichert wurde und nachdem das System diese gespeichert hat wird

 
ProductionOrderAction.ProductionOrderCreated

aufgerufen.

In der Regel heißen die Event-Typen analog zum Service. Z.B. BomVersionAction für den bomVersions-Service etc. Das Event ist in der Regel der Event-Typ mit angehängten "Event", also hier z.B. "BomVersionActionEvent"

Es werden nur Nachrichten erzeugt, wenn sich was ändert, das heißt, dass nur die create-, delete-, update- oder auch started- bzw. stopped- Methoden solche Standard-Events erzeugen. Die get-Methoden haben aus Performance-Gründen keine solche Events.

Achtung

Standard-Aktionen senden, wie oben beschrieben, eigene konkretere Events. So erzeugen alle vom Event-Typ ProductionOrderAction.* entsprechende "ProductionOrderActionEvent". Hierbei muss einem bewusst sein, dass die konkreten Events dann nicht immer dieselben Informationen enthalten, da einige Informationen beim Auftreten des Events gar kein Wissen über alle Informationen haben können. 

Z.B. kann ein ProductionOrderActionEvent für den Event-Typ ProductionOrderAction.ProductionOrderCreated das erstellte ProductionOrder enthalten. Hingegen hat ein ProductionOrderActionEvent, wenn es für den Event-Typ ProductionOrderAction.ProductionOrderDeleted erzeugt wurde kein ProductionOrder, da es bereits aus dem System gelöscht wurde. Es enthält aber z.B. die Id.

Heißt also, dass dies hier möglich ist:

SmartMES.getActionService().addTypedListener(event -> {
    ProductionOrderADO order = event.getProductionOrder(); // ist OK
}, ProductionOrderAction.ProductionOrderCreated);


Dies ist aber nicht möglich:


SmartMES.getActionService().addTypedListener(event -> {
    ProductionOrderADO order = event.getProductionOrder(); // order ist "null"
}, ProductionOrderAction.ProductionOrderDeleted);


Was auch nicht geht, da der Fertigungsauftrag gelöscht wurde, diesen nachträglich abzufragen:


SmartMES.getActionService().addTypedListener(event -> {
    Long orderId = event.getProductionOrderId(); // id existiert zwar und kann verwendet werden
    ProductionOrderADO order = SmartMES.getService().productionOrders().getProductionOrder(orderId); // dies liefert aber null, da der Auftrag zu diesem Zeitpunkt gelöscht ist
}, ProductionOrderAction.ProductionOrderDeleted);

Generische Nachrichten empfangen und senden

Neben den Standard-Nachrichten, die im Wesentlichen vor und nach den Service-Funktionen aufgerufen werden, kann es noch weitere Events geben. Diese können beliebig im System auftreten und dienen im Wesentlichen, um ein Titel mit Text als Event zu schicken.

Solche Nachrichten sind immer von EventTyp "GenericEventType" und können über den Typ 

GenericEventType.DEFAULT

abonniert werden und die Events sind dann vom Typ "GenericActionEvent":

SmartMES.getActionService().addTypedListener(event -> {
    String customTitle = event.getTitle();
}, GenericEventType.DEFAULT);

Hierbei sollte darauf geachtet werden, dass dort alle möglichen Events auftreten können, sodass hier selbst gegebenenfalls gefiltert werden muss.

Die Nachrichten, die hierüber empfangen werden, können über den ActionService gesendet werden:

SmartMES.getActionService().sendMessage("Mein Titel", "Meine Nachricht!");

Hierbei lassen sich auch Priorität und das Topic angeben. Ferner kann ein eigener Topic angegeben werden, um die Events dann danach filtern zu können. Zu Topics auch bitte weiter unten den Abschnitt beachten!

Diese Nachrichten werden beispielsweise für die Benachrichtigung von Personen verwendet, die je nach Priorität gefiltert werden. So werden beispielsweise GenericActionEvents mit der Priorität "Alert" nur an solche Personen weitergeleitet, die auch nur "Alert"-Nachrichten entsprechend abonniert haben (z.B. in der GUI oder über ein Plugin wie für die Smartwatches).

Eigene Nachrichten senden und empfangen

In Ergänzung zu den beliebigen Nachrichten, wie sie zuvor beschrieben wurden, können auch eigene Events erstellt und versendet werden. Hierbei gibt es zwei Möglichkeiten: 

  • Einen CustomActionEvent zu verweden, dass im Wesentlichen wir das GenericActionEvent ist, jedoch noch einen "event-identifier" hat, um nur Nachrichten mit entsprechenden Identifier zu erhalten.
  • Eine eigene Implementierung von ActionEvent und ActionEventType.

Der CustomEventType

CustomEvents verhalten sich ähnlich zu GenericEvents, müssen jedoch zusätzlich explizit mit einem Identifier versehen werden. Der Identifier sollte keine Umlaute, Leerzeichen oder ähnliches enthalten, ähnlich zu den Plugin-ID. Bei Plugins bietet es sich an, die Plugin-Id mit zusätzlicher weiterer Infos zu nehmen, z.B.

de.bluebiz.smartmes-example.myevent

Mit dieser Event-ID kann dann ein CustomEventType erstellt werden, der dann abonniert werden kann:

String eventId = "de.bluebiz.smartmes-example.myevent";
 
SmartMES.getActionService().addTypedListener(new ActionEventListener<CustomActionEvent>() {
    @Override
    public void actionPerformed(CustomActionEvent event) {
        String customTitle = event.getTitle();
    }
}, new CustomEventType(eventId));

Um Nachrichten an diesen Custom-Type zu senden, stellt der ActionService die Methoden "sendCustomMessage" bereit. Hierbei muss als erstes die entsprechende Event-ID angegeben werden:

SmartMES.getActionService().sendCustomMessage("de.bluebiz.smartmes-example.myevent", "Ein Titel", "Meine private Nachricht!");

Hiermit lässt sich z.B. auch eine Inter-Plugin-Kommunikation herstellen, indem dieselbe Event-ID für das Senden in einem Plugin und dann für das Empfangen in einem anderen Plugin abonniert wird!

Eigene EventTypen

Es können auch eigene EventTypen erstellt werden. Hierzu müssen die beiden Interfaces ActionEvent und ActionEventType implementiert werden. Hier ein Beispiel:

Der EventType muss das Interface ActionEventType implementieren und hierbei als generischen Typ das dazugehörige Event angeben (hier ist es SmartwatchActionEvent).

Der Name darf hierbei nicht leer und sollte wie bei den CustomEvents ein eindeutiger Identifier sein!

public class SmartwatchActionEventType implements ActionEventType<SmartwatchActionEvent>{

    @Override
    public String name() {
        return "de.bluebiz.smartwatch.custom-event";
    }
}

Ferner muss das Event selbst angegeben werden. Hier wird z.B. zusätzlich zu den notwendigen Felder   (type, titel etc.) noch das SmartwatchDevice in das Event gespeichert. Für Topic bitte unten den Abschnitt beachten!

public class SmartwatchActionEvent implements ActionEvent {


    private DeviceSmartwatch smartwatch;

    public SmartwatchActionEvent(DeviceSmartwatch smartwatch){
        this.smartwatch = smartwatch;
    }

    @Override
    public ActionEventType getType() {
        return new SmartwatchActionEventType();
    }

    @Override
    public String getTitle() {
        return "Mein Titel";
    }

    @Override
    public String getDescription() {
        return "Meine Beschreibung";
    }

    @Override
    public String getTopic() {
        return "smartmes/device-smartwatch";
    }

    @Override
    public ActionEventPriority getPriority() {
        return ActionEventPriority.Info;
    }

    public DeviceSmartwatch getSmartwatch() {
        return smartwatch;
    }
}

Zum Abonnieren wird dann der erstellte EventType, hier "SmartwatchActionEventType" verwendet:

SmartMES.getActionService().addTypedListener(new ActionEventListener<SmartwatchActionEvent>() {
    @Override
    public void actionPerformed(SmartwatchActionEvent event) {
        DeviceSmartwatch deviceSmartwatch = event.getSmartwatch();
    }
}, new SmartwatchActionEventType());

Um ein solches Event zu senden, muss es erstellt und dann über den ActionService gesendet werden. Hier gibt es die "broadcast"-Methode:

SmartwatchActionEvent smartwatchActionEvent = new SmartwatchActionEvent(deviceSmartwatch);
SmartMES.getActionService().broadcast(smartwatchActionEvent);

Hinweise

Wenn ein Event Daten enthält, die Plugin-spezifisch sind (wie oben z.B. "DeviceSmartwatch"), dann achten Sie darauf, dass diese nicht zwangsweise in anderen Plugins verwendet werden können!

Alternativen für eigene EventTypen mit Subtypen

Als Implementierung eines ActionEventType können alternativ auch enums verwendet werden, dadurch muss keine Instanz erzeugt werden und erlaubt z.B. noch das Unterteilen von "Sub-Typen":

public enum SmartwatchActionEventType implements ActionEventType<SmartwatchActionEvent>{

    SmartwatchActionEvent,
    SmartwatchActionOtherEvent

}

Das Event kann dann den Subtyp übernehmen

public class SmartwatchActionEvent implements ActionEvent {


    private final SmartwatchActionEventType subtype;
	private DeviceSmartwatch smartwatch;

	public SmartwatchActionEvent(DeviceSmartwatch smartwatch, SmartwatchActionEventType subtype){
    	this.smartwatch = smartwatch;
	    this.subtype = subtype;
	}

	@Override
	public ActionEventType getType() {
    	return subtype;
	}

    @Override
    public String getTitle() {
        return "Mein Titel";
    }

    @Override
    public String getDescription() {
        return "Meine Beschreibung";
    }

    @Override
    public String getTopic() {
        return "smartmes/device-smartwatch/"+subtype.name().toLowerCase();
    }

    @Override
    public ActionEventPriority getPriority() {
        return ActionEventPriority.Info;
    }

    public DeviceSmartwatch getSmartwatch() {
        return smartwatch;
    }
}

Entsprechend wird das Abonnieren wie folgt geändert:

SmartMES.getActionService().addTypedListener(new ActionEventListener<SmartwatchActionEvent>() {
    @Override
    public void actionPerformed(SmartwatchActionEvent event) {
        DeviceSmartwatch deviceSmartwatch = event.getSmartwatch();
    }
}, SmartwatchActionEventType.SmartwatchActionEvent, SmartwatchActionEventType.SmartwatchActionOtherEvent);

Und beim Senden kann der Subtyp angegeben werden:

SmartwatchActionEvent smartwatchActionEvent = new SmartwatchActionEvent(deviceSmartwatch, SmartwatchActionEventType.SmartwatchActionEvent);
SmartMES.getActionService().broadcast(smartwatchActionEvent);

Topics

Jedes Event, das erzeugt wird, hat ein Topic. Dieses Topic wird unter anderem verwendet, wenn SmartMES mit Bluelytics verbunden ist. Dann wird jedes Event an die Publish-Subscribe-Funktion von Bluelytics weitergeleitet. Die Publish-Subscribe-Funktion von Bluelytics baut auf MQTT auf. 

Entsprechend sind die Topics so gewählt, dass sie den Topics nach MQTT entsprechen und sind über ein Slash "/" hierarchisch angeordnet, um hier bestimmte Subscriptions leichter durchführen zu können.

Hier gibt es einige Infos zu MQTT-Topics http://public.dhe.ibm.com/software/dw/webservices/ws-mqtt/mqtt-v3r1.html#appendix-a 

Dies ist keine Auflistung aller Topics, die im System verwendet werden, enthalten für das Messaging jedoch die wichtigsten.

Topics der Standard-Aktionen

Alle Standard-Aktionen werden an folgendes Topic gesendet:

smartmes/actions/{eventName}/{eventType}

Konkret wird dann der Event-Typ angehängt.

Beispiele sind:

smartmes/actions/productionorderactionevent/productionordercreated
smartmes/actions/deviceactionevent/devicebeforeupdated
smartmes/actions/deviceactionevent/deviceupdated

Hinweis:

Das Event wird für MQTT als Payload zu einem JSON-Object serialisiert. Daher sollte auf Zyklen etc. verzichtet werden.

Topics der generischen Events

Alle generischen Events werden an folgendes Topic gesendet:

smartmes/events/generic/{priority}

Zusätzlich wird die Priorität des Events angehängt

Beispiele sind:

smartmes/events/generic/info
smartmes/events/generic/notice
smartmes/events/generic/warning
smartmes/events/generic/error
 

Topics der CustomEventTypes

Alle CustomActionEvents werden an folgendes Topic gesendet:

smartmes/events/{eventId}/{priority}

Im Vergleich zum generischen wird hier die eventId verwendet und mit der Priorität des Events ergänzt

Beispiele sind:

smartmes/events/de.bluebiz.smartmes-example.myevent/info
smartmes/events/device-smartwatch/notice
smartmes/events/device-energymeter.connection/warning
smartmes/events/system/error

Oben wäre dann ein Custom-Event, wie es oben erzeugt wurde und die event-Id "de.bluebiz.smartmes-example.myevent" hat.


Eigene Topics oder eigene EventTypen

Wenn eigene Events erstellt werden oder das Topic überschrieben werden, dann sollte sich an das Topic-Format gehalten werden. Im Wesentlichen sind dies:

  • Lower-Case. Topics sind case-sensitive, so dass als Konvention alle Topics komplett klein geschrieben werden sollten (natürlich sind Ausnahmen möglich)
  • Kein "/" am Anfang, da dieses einen "leeres" Anfangs-Topic erzeugt
  • Obwohl möglich, sollten keine Leerzeichen und keine Sonderzeichen verwendet werden, um die Lesbarkeit zu erhöhen und die Fehleranfälligkeit durch falsche Charakter-Codierung zu senken
  • Wenn komplett eigene Events erzeugt werden, die nicht in Zusammenhang mit anderen stehen, dann sollten diese auch eigene Topics definieren.
    Nicht notwendig, aber es macht jedoch Sinn, wenn alle Topics zumindest mit "smartmes/" beginnen. Damit können in Bluelytics alle Events aus dem SmartMES von denen unterschieden werden, die nicht vom SmartMES kommen (z.B. Sensormesswerte).

Die Empfehlung ist daher für eigene Events:

smartmes/{pluginId}/{eventType}