Adding a GUI to the Window-Heating-Control application

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

A general introduction into the OGEMA web framework and an overview on all availble widgets can be found in the widget framework reference. In this page we just show a potential GUI for the window-heatcontrol application developed before and the code needed to generate it.

Note that the GUI generated with the widget framework ist a so-called "developer GUI". It can be fully configured by Java classes and provides all dynamics and formatting with pre-defined Javascript libraries that need not to be touched by the developer. The development GUI allows full testing of the functionality also for motivated users without any programming / OGEMA skills, but for applications presented to the general public we recommend to provide a dedicated web application. This could possibly be based on the existing widgets page, but a tutorial for this step remains to be written.

Set up and result

Right-click on the project and select "Add OGEMA GUI page". Just keep the proposal "MainPage" and click OK. The project now has a new package org.smartrplace.external.windowheatcontrol.gui and you can find a pre-generated class MainPage there. Put the code given in the following Section there, build and and run the project to see the following GUI. To find the new application in the Home Screen put an icon file into the project under src/main/resources. The icon should be svg, png or jpeg. Rename the file to icon.svg, icon.png or icon.jpg To get started you may take a free icon from the www, e.g.

Another useful site: http://www.wpclipart.com

For instance, use a free battery icon from iconfinder.com :

1480000980_Battery-Charging.png

After building the app and restarting, the App appears in the Home Screen like this:

WindowHeatIcon.png

When you click on the tile the app opens like this:

template_apge2.png

Your project should now look like similar to this:

WindowHeatProjectV2.png

Preparing the app

We add some further functionality to the app, which will be needed to display all the relevant information on the GUI:

Create new methods in WindowHeatControlController:

public List<Room> getActiveRooms() {
   final List<Room> list = new ArrayList<>();
   for (RoomLogic c : roomControllers.values()) {
       if (c.isActive())
            list.add(c.getRoom());
   }
   return list;
}

and in RoomLogic, add the following methods:

public Room getRoom() {
   return room;
}

public boolean isActive() {
   return active;
}

public boolean isWindowOpen() {
   return windowOpen;
}

public int thermostatCount() {
   return thermostats.size();
}

public int windowSensorCount() {
   return windowSensors.size();
}

public float getCurrentTemperatureSetpoint() {
   final List<ThermostatPattern> list = thermostats.getElements();
   if (list.isEmpty())
       return Float.NaN;
   float value = 0;
   for (ThermostatPattern thermostat : list) {
        value += thermostat.getTemperatureSetpointCelsius();
   }
   return value/list.size();
}

public void setCurrentTemperatureSetpoint(float celsius) throws IllegalArgumentException {
   if (!Float.isFinite(celsius) || celsius < 0 || celsius > 35)
       throw new IllegalArgumentException("Invalid temperature " + celsius);
   for (ThermostatPattern thermostat : thermostats.getElements()) {
        thermostat.setpoint.setCelsius(celsius);
   }
}

public float getWindowOpenTemperatureSetpoint() {
   return 12;
}

public void setWindowOpenTemperatureSetpoint(float celsius) throws IllegalArgumentException {
   // TODO not yet supported
}

Code

You only have to make changes to MainPage.java except for one correction that needs to be made to the main App class: in the start method you have to exchange 'appMan' by 'controller'.

We do not go through all steps for the GUI generation, since creation of a useful user page is a lengthy process. Instead, the source code can be copied from the Github page: https://github.com/ogema/tutorial/blob/master/src/Window-Heat-Control/src/main/java/com/example/app/windowheatcontrol/gui/MainPage.java and some explanations are provided below. Detailed information on the OGEMA widgets framework are available on the concepts page and the reference page. See also the overview here.

Some minor changes are required after copying the source code to your class:

  • remove the defaultWindowOpenTemp widget (declaration and instantiation, plus remove the line
.setContent(1, 0, "Default window open temperature").setContent(1, 1, defaultWindowOpenTemp);

from the buildPage method)

  • remove unrecognized import packages and replace them by the correct ones; this should only affect classes from the app itself.

Structuring the page

First we remove the sample code from the constructor of the class MainPage. Then we add suitable widgets to the class for the elements we want on the page:

  • The header line
  • A dynamic widget showing the current batterySOC (no editing by the user)
  • A dynamic widget showing the current default window open temperature that also allows to edit the value
  • Another header for the table showing the rooms
  • A table showing some more values for each room controlled
  • An alert that is only shown when a message to the user of the page is available

The declaration for this looks like this (same order as above):

private final Header header;
private final ValueResourceLabel<FloatResource> batterySOC;
private final ValueResourceTextField<TemperatureResource> defaultWindowOpenTemp;
private final Header roomsHeader;
private final DynamicTable<Room> roomTable;
private final Alert alert;

Now we have to instantiate all these widgets. A link to the full code in the tutorial app is given below, here we only give some examples. As the header does not contain dynamic content, it can be instantiated with just two lines:

 header = new Header(page, "header", "Battery-extended Window-Heat Control");
 header.addDefaultStyle(HeaderData.CENTERED);

To show the batterySOC dynamically we have to override the onGET method. For a ValueResourceLabel we jsut have to select the resource for which the value shall be shown. If we would use the basis widget "Label" we would have to provide the actual text to be shown with "setText" instead of "selectItem".  If no battery is available we show an "n/a" value by setting the selected resource item to null:

batterySOC = new ValueResourceLabel<FloatResource>(page, "batterySOC") {
 
   private static final long serialVersionUID = 1L;

   @Override
   public void onGET(OgemaHttpRequest req) {
       final ElectricityStoragePattern activeBattery = appController.batteryListener.getActiveBattery();
   if(activeBattery != null)
        selectItem(activeBattery.soc, req);
   else
        selectItem(null, req);
   }
};

Generation of the table is a bit more complex. The rows of a DynamicTable widget are modeled on a class, which is set as generic parameter of the table. Here, the model class for our rows is the Resource type Room, so we have a DynamicTable<Room>. The content of each row of the table is determined by a RowTemplate, that must be implemented. It defines 

  • a line id for each object for which a row is created. The line id must be unique, so there are some common choices, such as the resource path, if the generic type is a resource type. There is also a set of abstract util classes which implement the getLineId(T object) method by returning an id derived from the resource path (DefaultResourceRowTemplate), or the pattern model path (DefaultPatternRowTemplate). A generic catch-all implementation (DefaultObjectRowTemplate) uses the resource location for resources and patterns, and the toString()-method for generic objects. 
  • a header (optional, this may return null): a map, whose keys are used as column id, and the values (usually Strings) are displayed as column headers. 
  • an addRow(T object) method: this is the core method. Typically in this method we first create a new Row object to be returned, get the lineId and then add the widgets for each cell of the row:
RowTemplate<Room> roomTemplate = new DefaultObjectRowTemplate<Room>() {
  
   @Override
   public Map<String, Object> getHeader() {
       final Map<String,Object> header = new LinkedHashMap<>();
       // keys must be chosen in agreement with cells added in addRow method below
       header.put("roomname", "Room name");
        header.put("temperaturesetpoint", "Active temperature setpoint");
        header.put("windowopensetpoint", "Window open setpoint");
        header.put("nrWindowSensors", "Window sensors");
        header.put("nrThermostats", "Thermostats");
        header.put("windowstatus", "Window open");
       return header;
   }

   @Override
   public Row addRow(final Room room, final OgemaHttpRequest req) {
       final Row row = new Row();
       final String lineId = getLineId(room);
       // this widget displays the name of the room; since the content cannot change, we
       // simply set a default text in the constructor, and do not overwrite the onGET method
       Label name = new Label(page, "name_"+lineId, ResourceUtils.getHumanReadableName(room));
       // set first column content
       row.addCell("roomname",name);
   
       // this widget displays the current temperature setpoint for the room (onGET), and allows the user to change it (onPOST)
       final RoomLogic controller = appController.getController(room);
        ValueInputField<Float> setpoint = new ValueInputField<Float>(page, "setpoint_" + lineId, Float.TYPE) {

           private static final long serialVersionUID = 1L;

           @Override
           public void onPOSTComplete(String data, OgemaHttpRequest req) {
                Float value = getNumericalValue(req);
               if (value == null) {
                    alert.showAlert("Please enter a valid temperature", false, req);
                   return;
               }
               try {
                    controller.setCurrentTemperatureSetpoint(value);
                    alert.showAlert("New temperature setpoint for room " + ResourceUtils.getHumanReadableName(room) + ": " + value + "°C", true, req);
               } catch (IllegalArgumentException e) {
                    alert.showAlert(e.getMessage(), false, req);
               }
           }
        
           @Override
           public void onGET(OgemaHttpRequest req) {      
               float temp = controller.getCurrentTemperatureSetpoint();
                setNumericalValue(temp, req);
           }
    
       };
        setpoint.setDefaultUnit("°C");
        setpoint.setDefaultPollingInterval(UPDATE_RATE);
        row.addCell("temperaturesetpoint",setpoint);
       //we have to make sure the value shown in the client is updated according to what the server accepted
       setpoint.triggerAction(setpoint, TriggeringAction.POST_REQUEST, TriggeredAction.GET_REQUEST);
       // in the onPOSTComplete method of setpoint, we set a message to be displayed by alert, hence we need to reload the alert after the POST
       setpoint.triggerAction(alert, TriggeringAction.POST_REQUEST, TriggeredAction.GET_REQUEST);
     
       ...
   
       return row;
   }
};

We still have to create the table itself, attach the template and make sure the rows are updated when the table is loaded:

roomTable = new DynamicTable<Room>(page, "roomTable", true) {
  
   private static final long serialVersionUID = 1L;
  
   @Override
   public void onGET(OgemaHttpRequest req) {
       //find all managed rooms
       updateRows(appController.getActiveRooms(), req);
   }
};
roomTable.setRowTemplate(roomTemplate);

We finish setting up the page with building everything into a static table providing a "grid":

private final void buildPage() {
    page.append(header).linebreak().append(alert).linebreak();
    StaticTable table1 = new StaticTable(2, 2, new int[] {3,3});
    page.append(table1);
    table1.setContent(0, 0, "Battery SOC:").setContent(0, 1, batterySOC)
         .setContent(1, 0, "Default window open temperature").setContent(1, 1, defaultWindowOpenTemp);
    page.linebreak().append(roomsHeader).append(roomTable);
}

The full code of file MainPage.html is availble on https://github.com/ogema/tutorial/blob/master/src/Window-Heat-Control/src/main/java/com/example/app/windowheatcontrol/gui/MainPage.java .

Next


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