User Guide

This section provides a short overview on how to use jEEBus.SPINE in your projects. A demo subproject containing example classes can be found in the downloadable Project in the Download section of our website.

Use jEEBus.SPINE as a library

This library is available on MavenCentral. The concrete way to add it as a dependency to your project depends on the build tool you are using. It is recommended to use the latest available version.

with Gradle

implementation 'org.openmuc.jeebus:spine:3.0.0'

with Maven

<dependency>
  <groupId>org.openmuc.jeebus</groupId>
  <artifactId>spine</artifactId>
  <version>3.0.0</version>
</dependency>

Set up a SPINE device

  1. Creating a SPINE device that is not connected to SHIP is not practical in most cases. Thus, it is recommended to start by configuring a SHIP node by creating a new instance of ShipNodeConfiguration.

  2. The jEEBus.SPINE package org.openmuc.jeebus.shipspine contains classes to connect it to jEEBus.SHIP. The easiest way to do this is to create a ShipCommunication instance as shown below. However, you can also cusomize this logic by implementing the Communication interface.

    ShipCommunication shipCommunication = new ShipCommunication(
        shipNodeConfiguration
    ).withTrustedSkis(
        // Here you can pre-trust remote SHIP devices per SKIs
    )
    // Enable the auto accept mode on the SHIP server if you want
    .withAutoAcceptMode(true);
    
  3. The final step consists of building the SPINE device itself. To do this, you can use a dedicated builder class like so:

    Device device = Device
        .getBuilder()
        // Set the SPINE device type
        .withDeviceType(DeviceTypeEnumType.GENERIC)
        // Set SHIP as the communication protocol
        .withCommunication(shipCommunication)
        // Set the SPINE device ID
        .withId("d:_n:MinimalExample_123")
        /* Enable the automatic SPINE DetailedDiscovery + UseCaseDiscovery of
         * remote devices */
        .withDiscoverDevices(true)
        .withUseCases(
            /* Here you can add supported EEBus Use Cases to the device.
             * These must implement the UseCase interface. */
            new ExampleUseCase()
        )
        .build();
    

    The created SPINE device will now be visible for EEBus communication in the specified network. It will automatically try to connect to other EEBus devices via SHIP and handle SPINE DetailedDiscovery and UseCaseDiscovery processes.

For the complete example Java class, please refer to the MinimalExampleDevice in the demo subproject.

Implement an EEBus use case

This section demonstrates how to implement a use case, how to listen for valid use case partners, and lastly how to communicate with remote SPINE devices.

  1. To implement a specific use case, create a new class implementing the UseCase interface.

  2. You need to implement getter methods for the actor, name, version and supported scenarios of the use case as per its specification.

  3. The getAddress method should return top-level address under which all scenarios of the UseCase can be accessed. Normally, this is the address of the entity the use case is registered on.

  4. There are setup methods that are called when building the SPINE device. Firstly, you can add or assign the entity address in the setup method for the DeviceBuilder:

     @Override
     public void setup(DeviceBuilder deviceBuilder) {
         // Add a new SPINE entity to the device
         EntityBuilder eb = deviceBuilder.addEntity()
             // Please check the allowed entity types in the use case specification
             .setType(EntityTypeEnumType.GENERIC);
    
         setup(eb);
         eb.applyToDevice();
    
         this.address = deviceBuilder.getLastAddedEntityAddress();
     }
    
  5. In the setup method for the EntityBuilder you can initate the SPINE features needed for the use case:

        @Override
     public void setup(EntityBuilder entityBuilder) {
         // Add a SPINE client feature to manage remote data
         entityBuilder
             .addFeature()
             .setType(FeatureTypeEnumType.GENERIC)
             .setRole(RoleType.CLIENT)
             .apply();
    
         // This is how you would add SPINE server features and functions
         DeviceDiagnosisFeature deviceDiagnosisFeature = new DeviceDiagnosisFeature();
         deviceDiagnosisFeature.addHeartBeatDataFunction(60);
         entityBuilder.addFeature(
             deviceDiagnosisFeature
         );
     }
    
  6. The setDevice method is called once the SPINE device is built. It is good practice to keep a reference to it and to use the automatic Discovery API to identify valid use case communication partners. Please refer to the JavaDoc of the method for detailed information on the paramaters. Here is an example:

    @Override
    public void setDevice(Device device) {
        this.device = device;
    
        /* Basically, we are listening for a remote device supporting our
         * use case as the "OtherExampleActor".*/
        this.device
            .getNodeManagement()
            .addUseCaseListener(
                this::startUseCase,
                this.getName(),
                "OtherExampleActor",
                // They SHALL support scenario 1 of our use case
                Map.of(
                    1L, PresenceIndication.MANDATORY
                ),
                /* Scenario 1 requires a DeviceDiagnosis server feature with a
                 * deviceDiagnosisHeartbeatData function. */
                Set.of(new FeatureFunctionRequirement(
                    FeatureTypeEnumType.DEVICE_DIAGNOSIS,
                    Map.of(
                        FunctionEnumType.DEVICE_DIAGNOSIS_HEARTBEAT_DATA,
                        Map.of(1L, PresenceIndication.MANDATORY)
                    )
                ))
            );
    }
    
  7. In the example above, we define that a method named startUseCase should be called when valid use case partners are identified. That means when the method is called, we can be sure there is a remote SPINE device connected to ours that supports the desired actor of our use case, and we can communicate with it. Here is how the method may look:

    private void startUseCase(List<UseCasePartner> partners) {
         // Assuming there is exactly one partner entity for simplicity
         UseCasePartner firstPartner = partners.get(0);
    
         /* This is where you would implement the Pre- Initial- and
          * RuntimeScenarioCommunication as described in the EEBus use case
          * specifications. */
    
         /* As an example, this is how you would subscribe to their
          * DeviceDiagnosisFeature.
          * Binding, read or write requests work like this as well. */
         CompletableFuture<RequestResult> request = device
             .getNodeManagement()
             .requestSubscription(
                 firstPartner.getCompleteFeatureAddress(
                     FeatureTypeEnumType.DEVICE_DIAGNOSIS
                 ),
                 FeatureTypeEnumType.DEVICE_DIAGNOSIS,
                 this::handleHeartbeat
             );
    
         // This is how you can wait for and evaluate request results.
         try {
             RequestResult result = request.get();
         }
         catch (InterruptedException | ExecutionException e) {
             // Handle exceptions here
             throw new RuntimeException(e);
         }
     }
    

For a complete example Java class, please refer to the ExampleUseCase class in the demo subproject. Lastly, you can refer to the JavaDoc of the UseCase interface.