Adding a configuration resource

Last modified by David Nestle on 2017/02/18 12:32

In the previous steps of the tutorial, we have built an application, the window-heat-control app, which implements some energy savings functionality by reducing thermostat setpoints when a window is opened; we have learned how to set up a simulation environment to test the app; and we have created a simple user interface for it. So far, the app does not provide any configuration parameters, which is what we are going to add now. 

There are different possibilities to configure a Java/OSGi application, e.g. using a system property (which is done in OGEMA via the config/ogema.properties file in the rundir), an OSGi properties (which is done via the config/config.xml file in the rundir, section <properties>), a custom configuration file, the OSGi configuration admin, etc. The preferred method in OGEMA, however, is to use the resource database to store configurations. Typically, apps will declare their own custom resource type for this purpose. A useful pattern is to define one type for a global configuration, which will be instantiated only once, and another type for individual managed entities. In our example app, the basic managed units are rooms, so we could create one configuration resource per room. When we generate a new app the OGEMA plugin automatically creates these two resource type declaration for us, in the package ending on .config.

customTypes.png

The custom resource type WindowHeatControlConfig for the global app configuration, as generated by the OGEMA plugin.

Resource type declarations

As an example, we will use the temperature setpoint when a window is open as configuration parameter. The setpoint shall be configurable per room, but we also want to make the default setpoint editable. So far, we simply set the temperature down to 12°C when a window was opened. 

First of all, let us rename the specific config type from the clumsy WindowHeatControlProgramConfig to RoomConfig (either select the file in the package explorer and press F2, or open the class in the editor, place the cursor on the interface name, and press Alt+Shift+R - all references to the interface in other java files in the workspace will be automatically renamed too; this is called "refactoring"). Then define the interface as follows:

public interface RoomConfig extends Configuration {

   /**
     * A reference to the room to which this setting applies
     * @return
     */

    Room targetRoom();

   /**
     * Temperature to be set by room controller when a window is opened in the room.
     * @return
     */

    TemperatureResource windowOpenTemperature();

}

and the global configuration as

public interface WindowHeatControlConfig extends Configuration {

   /**
     * Temperature to be set by room controller when a window is opened,
     * if no special temperature is configured for a room.
     * @return
     */

    TemperatureResource defaultWindowOpenTemperature();

   /**
     * List of per-room settings.
     * @return
     */

    ResourceList<RoomConfig> roomConfigurations();

}

Note how the global configuration resource contains a list of the specific ones. 

Code modifications

We'll have to modify a few parts of our app in order take into account the configuration resources.

RoomLogic

So far, the RoomLogic has a reference to the Room resource it manages. Let's replace this by the corresponding RoomConfig (from which we can still access the actual Room, via the targetRoom subresource, which will be set as a reference to the room). In the variable declarations, delete

private final Room room;

and replace it by

private final RoomConfig roomConfig;

Similarly, replace the constructor:

public RoomLogic(RoomConfig roomConfig,
                          LinkingManagementAccess<Room, ThermostatPattern> thermostats,
                          LinkingManagementAccess<Room, WindowSensorPattern> windowSensors,
                          ElectricityStorageListener batteryListener) {
   this.roomConfig = roomConfig;
   this.thermostats = thermostats;
   this.windowSensors = windowSensors;
   this.batteryListener = batteryListener;
}

Eclipse should display two errors now in the class, because the field "room" is no longer available. We need to replace the getRoom() method by

public Room getRoom() {
   return roomConfig.targetRoom();
}

and, in addition, adapt the logic in the resource value listener. You may want to do this modification yourself, to get some experience in the use of OGEMA resources; in any case a possible solution could look like this:

private final ResourceValueListener<BooleanResource> windowListener = new ResourceValueListener<BooleanResource>() {
 
   // called whenever a window is opened or closed
   @Override
   public void resourceChanged(BooleanResource resource) {
       // there may be more than one window in the room, therefore it is not enough to evaluate the value of the changed resource only
       final boolean windowOpenNew = getWindowOpenStatus();
        logger.debug("Window status in room {} changed. Was open: {}, is open: {}", roomConfig.targetRoom().getLocation(), windowOpen, windowOpenNew);
       // state did not change; may happen, for instance, if a second window is opened, in which case we do not want to change the thermostat setpoints
       if (windowOpenNew == windowOpen)
           return;
       final ElectricityStoragePattern activeBattery = batteryListener.getActiveBattery();
       final boolean fullBatteryDetected = (activeBattery != null && activeBattery.soc.getValue() > 0.95F);
       for (ThermostatPattern th: thermostats.getElements()) {
           if (windowOpenNew && !fullBatteryDetected) {
                temperaturesBeforeWindowOpen.put(th.model, th.getTemperatureSetpointCelsius());
               float value = roomConfig.windowOpenTemperature().getCelsius();
               if (value <= 0 || value > 35) { // check if value is sensible
                   logger.warn("Extreme temperature value {}°C found in configuration. Ignoring this and using the default temperature {}°C instead.",
                            value, 12);
                    value = 12;
               }
                th.setpoint.setCelsius(value);
           } else {
                Float celsius = temperaturesBeforeWindowOpen.remove(th.model);
               if (celsius == null)
                   continue;
                th.setpoint.setCelsius(celsius);
           }
       }
    windowOpen = windowOpenNew;
   }
};

Furthermore, we are now in a position to properly implement the methods get/setWindowOpenTemperatureSetpoint():

public float getWindowOpenTemperatureSetpoint() {
   return roomConfig.windowOpenTemperature().getCelsius();
}

public void setWindowOpenTemperatureSetpoint(float celsius) throws IllegalArgumentException {
   if (!Float.isFinite(celsius) || celsius < 0 || celsius > 35)
       throw new IllegalArgumentException("Invalid temperature " + celsius);
    roomConfig.windowOpenTemperature().setCelsius(celsius);
}

WindowHeatControlController

In the main controller, we need to adapt the initConfigurationResource method:

/*
 * This app uses a central configuration resource, which is accessed here
 */

private final WindowHeatControlConfig initConfigurationResource() {
    String configResourceDefaultName = WindowHeatControlConfig.class.getSimpleName().substring(0, 1).toLowerCase()+WindowHeatControlConfig.class.getSimpleName().substring(1);
    appConfigData = appMan.getResourceAccess().getResource(configResourceDefaultName);
   if (appConfigData != null) { // resource already exists (appears in case of non-clean start)
       appMan.getLogger().debug("{} started with previously-existing config resource", getClass().getName());
   }
   else {
        appConfigData = appMan.getResourceManagement().createResource(configResourceDefaultName, WindowHeatControlConfig.class);
        appConfigData.defaultWindowOpenTemperature().<TemperatureResource> create().setCelsius(12);
        appConfigData.roomConfigurations().create();
        appConfigData.activate(true);
        appMan.getLogger().debug("{} started with new config resource", getClass().getName());
   }
   return appConfigData;
}

and getRoomController:

public RoomLogic getController(Room room) {
     room = room.getLocationResource();
     RoomLogic controller = roomControllers.get(room);
    if (controller == null) {
       // persistent controller configuration (type RoomConfig) may exist already from previous start,
       // even if the controller is not active yet
       RoomConfig config = null;
       for (RoomConfig existingConfig: appConfigData.roomConfigurations().getAllElements()) {
           if (existingConfig.targetRoom().equalsLocation(room)) {
                config = existingConfig;
               break;
           }
        }
        if (config == null) {
             config = appConfigData.roomConfigurations().add();
             config.targetRoom().setAsReference(room);
            // initialize with default value
            config.windowOpenTemperature().<TemperatureResource> create().setCelsius(appConfigData.defaultWindowOpenTemperature().getCelsius());
             config.activate(true);
        }
         controller = new RoomLogic(config,
                                         thermostats.getSingleResourceManagement(room),
                                         windowSensors.getSingleResourceManagement(room),
                                         batteryListener);
         roomControllers.put(room, controller);
    }
    return controller;
}    

I.e., when a new room logic is created, we initialize its reduced temperature setpoint with the default value from the global configuration resource, but it may be changed individually later on. 

Let us also add a method that grants access to the temperature setpoint resource to the GUI:

public TemperatureResource getDefaultWindowOpenTemperatureSetting() {
   return appConfigData.defaultWindowOpenTemperature();
}

MainPage

In the main page, we can reintroduce a widget we deleted earlier:

private final ValueResourceTextField<TemperatureResource> defaultWindowOpenTemp;

which needs to be defined in the constructor as follows:

defaultWindowOpenTemp = new ValueResourceTextField<TemperatureResource>(page, "defaultWindowOpenTemp",
        appController.getDefaultWindowOpenTemperatureSetting());

and in the buildPage method, it is added as follows:

table1.setContent(0, 0, "Battery SOC:").setContent(0, 1, batterySOC)
     .setContent(1, 0, "Default window open temperature").setContent(1, 1, defaultWindowOpenTemp);

Run

Let's build the app once again, and restart the framework. The GUI looks almost the same as before:

windowheat_gui.png

but if we now change the window open setpoint for a room, this will actually take effect - it is not reset to its old value immediately, and if a window is opened, the modfied temperature setpoint will become active. So far, this effect could have been achieved by using a non-persistent parameter, as well; but due to using configuration resources, the modified settings will be preserved under a framework restart (unclean).

Next


Tags:
Created by Christoph Nölle on 2017/02/03 01:15