This repository contains the Java implementation of the Smart Premises Interoperable Neutral-message Exchange (SPINE) which represents the information layer of the EEBus Communication Standard, containing its data model. It is developed and maintained by the Team Smart Grid Communication at the Fraunhofer Institute for Solar Energy Systems ISE. For further information please refer to our website.
The SPINE Specification can be downloaded from the website of the EEBus Initiative e.V.
jEEBus.SPINE provides an easy-to-use API and features many abstractions and automations to make EEBus use case development more efficient.
NodeManagement
automatically handles subscription-, binding- or remote discovery requests.UseCaseListeners
that are automatically called only if valid use case partners are identified on remote devices.READ
/ WRITE
commands, SPINE protocol validation, filtering via Selectors and ElementTypes and the notification of subscribers.If you would like to contribute to this project, please use the contact form on our website to get in touch with the development team.
This is a Gradle Project that comes with a packaged Gradle Wrapper (use ./gradlew
on Linux and gradlew.bat
on Windows systems). You can run the following command to download all necessary dependencies and build the project:
To run all contained unit tests, run
There are three subprojects in the projects
folder. spine
contains the general implementation of SPINE, while spine-test-utilities
provides a framework to develop automatic tests for SPINE applications. demo
contains minimal example applications showing how jEEBus.SPINE can be configured and used.
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.
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.
implementation 'org.openmuc.jeebus:spine:3.0.0'
<dependency>
<groupId>org.openmuc.jeebus</groupId>
<artifactId>spine</artifactId>
<version>3.0.0</version>
</dependency>
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
.
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.
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.
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.
To implement a specific use case, create a new class implementing the UseCase
interface.
You need to implement getter
methods for the actor, name, version and supported scenarios of the use case as per its specification.
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.
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();
}
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
);
}
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)
)
))
);
}
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.