element14 Community
element14 Community
    Register Log In
  • Site
  • Search
  • Log In Register
  • About Us
  • Community Hub
    Community Hub
    • What's New on element14
    • Feedback and Support
    • Benefits of Membership
    • Personal Blogs
    • Members Area
    • Achievement Levels
  • Learn
    Learn
    • Ask an Expert
    • eBooks
    • element14 presents
    • Learning Center
    • Tech Spotlight
    • STEM Academy
    • Webinars, Training and Events
    • Learning Groups
  • Technologies
    Technologies
    • 3D Printing
    • FPGA
    • Industrial Automation
    • Internet of Things
    • Power & Energy
    • Sensors
    • Technology Groups
  • Challenges & Projects
    Challenges & Projects
    • Design Challenges
    • element14 presents Projects
    • Project14
    • Arduino Projects
    • Raspberry Pi Projects
    • Project Groups
  • Products
    Products
    • Arduino
    • Avnet Boards Community
    • Dev Tools
    • Manufacturers
    • Multicomp Pro
    • Product Groups
    • Raspberry Pi
    • RoadTests & Reviews
  • Store
    Store
    • Visit Your Store
    • Choose another store...
      • Europe
      •  Austria (German)
      •  Belgium (Dutch, French)
      •  Bulgaria (Bulgarian)
      •  Czech Republic (Czech)
      •  Denmark (Danish)
      •  Estonia (Estonian)
      •  Finland (Finnish)
      •  France (French)
      •  Germany (German)
      •  Hungary (Hungarian)
      •  Ireland
      •  Israel
      •  Italy (Italian)
      •  Latvia (Latvian)
      •  
      •  Lithuania (Lithuanian)
      •  Netherlands (Dutch)
      •  Norway (Norwegian)
      •  Poland (Polish)
      •  Portugal (Portuguese)
      •  Romania (Romanian)
      •  Russia (Russian)
      •  Slovakia (Slovak)
      •  Slovenia (Slovenian)
      •  Spain (Spanish)
      •  Sweden (Swedish)
      •  Switzerland(German, French)
      •  Turkey (Turkish)
      •  United Kingdom
      • Asia Pacific
      •  Australia
      •  China
      •  Hong Kong
      •  India
      •  Korea (Korean)
      •  Malaysia
      •  New Zealand
      •  Philippines
      •  Singapore
      •  Taiwan
      •  Thailand (Thai)
      • Americas
      •  Brazil (Portuguese)
      •  Canada
      •  Mexico (Spanish)
      •  United States
      Can't find the country/region you're looking for? Visit our export site or find a local distributor.
  • Translate
  • Profile
  • Settings
Pi IoT
  • Challenges & Projects
  • Design Challenges
  • Pi IoT
  • More
  • Cancel
Pi IoT
Blog [Pi IoT] Thuis #8: Core v2: A Java EE application
  • Blog
  • Forum
  • Documents
  • Polls
  • Files
  • Events
  • Mentions
  • Sub-Groups
  • Tags
  • More
  • Cancel
  • New
  • Share
  • More
  • Cancel
Group Actions
  • Group RSS
  • More
  • Cancel
Engagement
  • Author Author: rhe123
  • Date Created: 5 Jul 2016 1:43 PM Date Created
  • Views 531 views
  • Likes 3 likes
  • Comments 0 comments
  • mqtt
  • home automation
  • piiot
  • java ee
  • wildfly
  • raspberrypi3
  • thuis
  • java
Related
Recommended

[Pi IoT] Thuis #8: Core v2: A Java EE application

rhe123
rhe123
5 Jul 2016

Since quite some time my home automation is build around a core app made in Java EE. As part of this challenge I'm improving the core and integrating it more tightly through MQTT. The core is responsible for coordinating all nodes and enforcing the business rules for our home.

 

Introduction

The Thuis core runs on the Raspberry Pi 3 in a WildFly container. Like all nodes it's connected to the MQTT broker for communication with the rest of the system. It also takes care of communication with some external applications, like Plex (media server) and Netatmo (weather station). In this blog I'll focus on the integration of MQTT, Z-Way devices and the rules.image

 

The building blocks of the application are as follows:

  • Model:
    • Devices
    • Rooms
    • Scenes
    • Rules
  • Commands
  • Controller
  • MQTT-CDI

 

Each of these will be described in this blog. Currently all models and rules are defined as static objects in Java code, the goal is to put this in the database at some point and make it editable through a UI, that will however be after the challenge ends.

 

Device Model

The two base models are Device and Room. To be combined with Device there are different types of Actuator and Sensor. The following image and table show the interfaces. There are several implementations, for example there is MqttSwitch which implements Device and Switch and defines a switch which can be controlled through MQTT.

image

 

InterfaceDescription
DeviceGeneral definition of a (virtual) device: an identifier and it's status (type will be overridden by more specific interfaces)
ActuatorA device with controls
Switch
extends Actuator
A switch with on/off and toggle features
Dimmer
extends Switch
A switch which also has can have values between 0 and 100, where 0 is off
Thermostat
extends Switch
A thermostat supporting a set point for the temperature. It can be turned on/off, which will put it on predefined on and off temperatures
SensorA device that provides a (single) value of a sensor
BinarySensor
extends Sensor
A sensor with a value that can be true or false
MultilevelSensor
extends Sensor
A sensor with an arbitrary value and a configurable unit of measurement

 

All devices are virtual devices, each with a single function. For example a sensor that can measure both temperature and trigger on movement will be implemented as two sensors: a MultilevelSensor and a BinarySensor.

 

In this blog post we'll describe two of our rooms: the living room and the kitchen. This gives us the following definitions:

package nl.edubits.thuis.server.devices;

public class Rooms {
  public static Room living = new Room("living");
  public static Room office = new Room("office");
  public static Room kitchen = new Room("kitchen");
}

 

package nl.edubits.thuis.server.devices;

import static nl.edubits.thuis.server.devices.Rooms.living;
import static nl.edubits.thuis.server.devices.Rooms.kitchen;
/* other imports */

public class Devices {
  public static MqttSwitch livingMoodTop = new MqttSwitch(living, "moodTop");
  public static MqttSwitch livingMoodBottom = new MqttSwitch(living, "moodBottom");
  public static MqttSwitch livingMoodChristmas = new MqttSwitch(living, "moodChristmas");
  public static MqttDimmer livingMain = new MqttDimmer(living, "main");

  public static MqttBinarySensor kitchenMovement = new MqttBinarySensor(kitchen, "movement");
  public static MqttMultiLevelSensor kitchenTemperature = new MqttMultiLevelSensor(kitchen, "temperature", Units.CELSIUS);
  public static MqttMultiLevelSensor kitchenIlluminance = new MqttMultiLevelSensor(kitchen, "illuminance", Units.LUX);
  public static MqttSwitch kitchenMicrowave = new MqttSwitch(kitchen, "microwave");
  public static MqttSwitch kitchenCounter = new MqttSwitch(kitchen, "counter");
  public static MqttDimmer kitchenMain = new MqttDimmer(kitchen, "main");
}

 

Command Model

As you might have noticed in the interfaces above, several methods return Command. A command is a runnable class that can be executed to fulfill a task, for example turning on a Switch with Switch.on(). For each type of command there is an implementation. The one used most by the devices defined above is the MqttCommand which publishes a MQTT message, so for example Z-Way will receive it and take action. The implementation is quite straight forward:

package nl.edubits.thuis.server.automation.commands;

/* imports */

public class MqttCommand implements Command {
  String topic;
  String content;

  public MqttCommand(String topic, String content) {
    this.topic = topic;
    this.content = content;
  }

  @Override
  public void runSingle() {
    MqttService mqttService = CDI.current().select(MqttService.class).get();
    mqttService.publishMessage(topic, content);
  }
}

 

image

 

Commands can be encapsulated in other commands. By encapsulating you can either compose several commands into a single one, or add a condition to the command. The following commands of this type exist:

CommandDescription
ConditionalCommand

A command that executes the encapsulated command when a certain condition is met. The condition is defined using a Predicate lambda. Three default conditions are available:

  • illuminance – shortcut for predicates based on illuminance, for example execute when illuminance is below 100lux
  • whenOn/whenOff – execute a command when a given device is turned on/off
ListCommandExecute several commands in order
PrioritizedCommandExecute a command with a different priority, for example USER_INITIATED: the highest priority which will let the command jump in front of the execution queue
WaitForCommandWait for a condition to be true before executing another command. Two default conditions are available: waitForOn and waitForOff which wait for the status of a device to turn on or off before executing another command

 

Another way of combining commands is using a Scene. This is an object that contains two ListCommand, one for activating the scene and one for deactivating it. A scene for turning on and off the mood lighting in the living room is defined like:

package nl.edubits.thuis.server.devices;

import static nl.edubits.thuis.server.devices.Devices.livingMoodBottom;
import static nl.edubits.thuis.server.devices.Devices.livingMoodChristmas;
import static nl.edubits.thuis.server.devices.Devices.livingMoodTop;
/* other imports */

public class Scenes {
  public static Scene mood = new Scene("mood",
    asList(
      livingMoodTop.on(),
      livingMoodBottom.on(),
      livingMoodChristmas.on()
    ),
    asList(
      livingMoodTop.off(),
      livingMoodBottom.off(),
      livingMoodChristmas.off()
    )
  );
}

 

Observing MQTT messages

The Core observes MQTT messages arriving on basically any topic. It then checks if there are any devices (or better ObserveMqttStatus implementations) matching this topic. The status of these devices is then updated. When a sensor gets a new value an event is emitted. These events (and individual MQTT messages) can triggered rules. This all happens in the MqttObserverBean. This bean also takes care of updating the status of any scenes or rooms including this device.

 

The connection with the MQTT broker is handled by the MQTT-CDI extension made by Alexis Hassler, to which I contributed some improvements in the past. This CDI extension abstracts the actual connection away. When MQTT messages arrive on a subscribed topic they are fired as CDI events which can be observed using the @MqttTopic annotation. This way you can very easily observe any messages arriving:

public void onMessageLivingMain(@Observes @MqttTopic("Thuis/device/living/main") MqttMessage message) {
  logger.log("Light in the living was turned "+message.asText());
}

 

For publishing messages a service method is available.

 

Rules

To enable sensors (or other events) to trigger commands there are rules. A rule is an Observer of either an MQTT topic or a SensorChanged event. As result one or more commands are executed. An example of a rule is the following:

package nl.edubits.thuis.server.automation;

/* imports */

@ApplicationScoped
public class Rules {

  @Inject
  private Controller controller;

  public void onKitchenMovement(@Observes @SensorChange("kitchen/movement") BinarySensor sensor) {
    LocalTime now = LocalTime.now();

    if (sensor.getStatus() && Devices.kitchenIlluminance.isLowerOrEqual(80)) {
      if (TimeUtils.isBetween(now, LocalTime.of(6, 0), LocalTime.of(10, 0))
          || TimeUtils.isBetween(now, LocalTime.of(12, 30), LocalTime.of(13, 30))
          || TimeUtils.isBetween(now, LocalTime.of(20, 30), LocalTime.of(21, 30))) {
        // Breakfast/Lunch/After dinner
        controller.run(userInitiatedPriority(Devices.kitchenMicrowave.on()));
        controller.run(userInitiatedPriority(Devices.kitchenCounter.on()));
      } else if (TimeUtils.isBetween(now, LocalTime.of(17, 30), LocalTime.of(20, 30))) {
        // Dinner
        controller.run(userInitiatedPriority(Devices.kitchenMicrowave.on()));
        controller.run(userInitiatedPriority(Devices.kitchenCounter.on()));
        controller.run(userInitiatedPriority(Devices.kitchenMain.on()));
      } else {
        controller.run(userInitiatedPriority(Devices.kitchenMicrowave.on()));
      }
    } else {
      controller.run(Devices.kitchenMicrowave.off());
      controller.run(Devices.kitchenCounter.off());
      controller.run(Devices.kitchenMain.off());
    }
  }
}

 

This example covers most of the basic options. It observes the movement sensor in the kitchen. When it's status becomes true and it's not very light it will check 3 timeframes. Depending in which timeframe the current time fits a combination of lights is turned on. This way you always have the most useful lights for the task ahead. The timing might need some optimization, but this is a good start.

 

Controller

Taking care of the actual execution of commands and scenes are the Controller and the CommandExecutor. Take the example rule above: it triggers several lights to be turned on or off. These commands are passed on to the controller. The controller takes the command, determines it's priority and then puts it on a JMS queue:

package nl.edubits.thuis.server.controller;

/* imports */

@Stateless
public class Controller {

  @Inject
  private JMSContext context;

  @Resource(mappedName = Resources.COMMAND_QUEUE)
  private Queue commandQueue;

  public void run(Command command) {
    run(command, 0);
  }

  public void run(Command command, long deliveryDelay) {
    context.createProducer()
           .setPriority(command.getPriority().getValue())
           .setDeliveryDelay(deliveryDelay)
           .send(commandQueue, command);
  }
}

(for the purpose of this blogpost the code is simplified a bit)

 

A JMS MessageListener, the CommandExecutor, is used to listen to the commands which are added to the queue and execute them. Because of the way the JMS queue works, commands are executed in chronological order while respecting the priorities. This means that all commands with the same priority are executed exactly in order they were added (FIFO), but when a command of a higher priority is added to the queue it's moved in front. This is used for situations where for example the home theater is starting up (which takes a few minutes in total) and someone triggers a motion sensor. This command gets the USER_INITIATED priority and is therefore executed at the first possible moment, in front of all other steps of starting up the home theater. Something that's not time sensitive (for example automatically turning off the heating at night) gets a LOW priority and will therefore never block any more important commands.

 

Some commands can take a long time and you don't want them to block the queue. For example a WaitForCommand takes until the condition becomes true. In this case the condition is tested once and when the result isn't true yet the command is added to the queue again with a small timeout.

 

Concluding

The most important part of the Core is now done, but most rules still have to be implemented. In a later stage some more external systems will be added to the core, for example for controlling the Home Theatre. I'm also aware that, to keep this blog post from growing too much, I have simplified some code samples and didn't cover every detail. If you're interested in a certain detail, please let me know and I'll explain it more!

  • Sign in to reply
element14 Community

element14 is the first online community specifically for engineers. Connect with your peers and get expert answers to your questions.

  • Members
  • Learn
  • Technologies
  • Challenges & Projects
  • Products
  • Store
  • About Us
  • Feedback & Support
  • FAQs
  • Terms of Use
  • Privacy Policy
  • Legal and Copyright Notices
  • Sitemap
  • Cookies

An Avnet Company © 2025 Premier Farnell Limited. All Rights Reserved.

Premier Farnell Ltd, registered in England and Wales (no 00876412), registered office: Farnell House, Forge Lane, Leeds LS12 2NE.

ICP 备案号 10220084.

Follow element14

  • X
  • Facebook
  • linkedin
  • YouTube