Example for server and client functions

Server Functionality

With jEEBus.SPINE the developer extends the abstract class FeatureFunction to represent each of these functions.

If a Use Case does not use write messages only the read method needs to be implemented and changes in data must be notified to any subscribers. Every time a client sends a read message to this funtion, jEEBus.SPINE executes the read method and replies with the returned cmd.

The deviceDiagnosisStateData function could be represented as following in jEEBus.SPINE (omitting import statements):

public class DeviceDiagnosisStateDataFunction extends FeatureFunction {
    private DeviceDiagnosisStateDataType stateData;  // holds the data

    public DeviceDiagnosisStateDataFunction() {
        super(FunctionEnumType.DEVICE_DIAGNOSIS_STATE_DATA.value());

        // marks function as readable in DetailedDiscovery, but not as partially readable
        setReadable(true, false);
    }

    // arguments can be ignored here
    // filter would be used for partial reads
    @Override
    public CmdType read(FilterType filter, FeatureAddressType sourceAddress) {
        CmdType replyCmd = new CmdType();
        replyCmd.setDeviceDiagnosisStateData(stateData);
        return replyCmd;
        /* replyCmd represents this XML (with example data):
        <cmd>
          <deviceDiagnosisStateData>
            <operatingState>failure</operatingState>
            <lastErrorCode>EV exploded</lastErrorCode>
          </deviceDiagnosisStateData>
        </cmd>
        */
    }

    @Override
    public SpineAcknowledgment write(CmdType cmd, FeatureAddressType sourceAddress) {
        throw new UnsupportedOperationException();  // function is not writable
    }

    @Override
    public SpineAcknowledgment call(FeatureAddressType sourceAddress) {
        throw new UnsupportedOperationException();  // function is not callable
    }

    // simplified; this would update the stateData even if no actual change occured
    public void updateStateData(DeviceDiagnosisStateDataType stateData) {
        this.stateData = stateData;
        // notifies any subscribers about the change
        feature.notifySubscribers(FunctionEnumType.DEVICE_DIAGNOSIS_STATE_DATA, null);
    }
}

Note that the UnsupportedOperationException in the above code example is actually never thrown as jEEBus.SPINE would deny write and call requests before executing the related methods. The default IDE behaviour of returning null when overriding these methods is fine, but throwing the exception makes the behaviour more obvious and helps readability.

An instance of the FeatureFunction can then be added to a SPINE Feature when building the Feature with jEEBus.SPINE.

SPINE Device creation

To build a SPINE Device with jEEBus.SPINE the DeviceBuilder class is used. The Device is firstly prepared by setting required information like a communication implementation – e.g. ShipCommunication using jEEBus.SHIP, an ID (i.e. the SPINE Device address) and the Device type. Afterwards, the UseCase specific data is set (UseCase metadata, UseCase specific data model).

DeviceBuilder db = Device.getBuilder();

// required information
Communication comm = new ShipCommunication(...);  // constructor arguments omitted for simplicity
db.setCommunication(comm)  // implementation is protocol dependent, e.g. jSHIP
  .setId("d:_n:EVSECC-Demo_EVSE")  // should be unique and include the IANA PEN
  .setDeviceType(DeviceTypeEnumType.GENERIC);

// EVSECC UseCase specific data

// UseCase interface implementation to make the UseCase discoverable via the UseCaseDiscovery
// Details omitted for simplicity (provides simple data like the UseCase name)
UseCase evsecc = new UseCase(...);
db.addUseCase(evsecc);

// bindingAllowed override omitted here as no bindings are used
FeaturePermission subsAllowed = new FeaturePermission() {
    @Override
    boolean subscriptionAllowed(SubscriptionRequest request) {
        return true;
    }
};

// following could also be put into UseCase#setup(DeviceBuilder)
EntityBuilder eb = db.addEntity().setType(EntityTypeEnumType.EVSE);

eb.addFeature()
  .setRole(RoleType.SERVER)  // the Feature provides data
  .setType(FeatureTypeEnumType.DEVICE_CLASSIFICATION)
  .addFunction(new DeviceClassificationManufacturerDataFunction())
  .setFeaturePermission(subsAllowed)
  .apply();

eb.addFeature()
  .setRole(RoleType.SERVER)
  .setType(FeatureTypeEnumType.DEVICE_DIAGNOSIS)
  .addFunction(new DeviceDiagnosisStateDataFunction())
  .setFeaturePermission(subsAllowed)
  .apply();

// Finalize build
eb.applyToDevice();

db.build();  // Device connects and is reachable now

After db.build(); was executed the SPINE Device is started and listens for messages on the communication protocol. Requests are handled by jEEBus.SPINE and the SPINE Device can be discovered via the DetailedDiscovery on the NodeManagement Feature.

Client Functionality

The energy manager requests data from the EVSE by sending read messages to the EVSE functions and subscribes to the EVSE Features to get notified about any runtime changes:

// cmd contains the function which shall be read on the Feature
CmdType dcReadCmd = new CmdType();
dcReadCmd.setDeviceClassificationManufacturerData(
    new DeviceClassificationManufacturerData());

// parse methods extract the Function data and process it
clientFeature.requestSubscription(evseDeviceClassificationAddress,
    FeatureTypeEnumType.DEVICE_CLASSIFICATION, this::parseDCUpdate);
clientFeature.requestRead(evseDeviceClassificationAddress, dcReadCmd)
    .whenComplete(this::parseDCReply);

CmdType ddReadCmd = new CmdType();
ddReadCmd.setDeviceDiagnosisStateData(new DeviceDiagnosisStateData());

clientFeature.requestSubscription(evseDeviceDiagnosisAddress,
    FeatureTypeEnumType.DEVICE_DIAGNOSIS, this::parseDDUpdate);
clientFeature.requestRead(evseDeviceDiagnosisAddress, ddReadCmd)
    .whenComplete(this::parseDDReply);
jEEBus.SPINE then handles all messages, parses notifications and manages acknowledgements.