This February, I was an intern with ISD. Our team of four students from The Technical University of Moldova worked on a small R&D project. Being an intern at INTHER, during four weeks I achieved a lot, in both fields, professional and personal. Since the first day I step into the office a friendly atmosphere that persists in the company has struck my eyes. Although it was a new place, new people I felt very comfortable from the very beginning. I expected the adjustment period to be longer, but the next day I felt as if I would have known the whole team for a long time. Fortunately, I discovered a team full of very positive, optimists and always ready to help people.
I was pleasantly surprised in terms of communication within the INTHER team. Even though there is a hierarchy among company staff, the impression that was created to me is that all are equal, each of them is important and every contribution is enormous and very appreciated.
I got involved and worked at the most interesting project I’ve ever had, why? Because there wasn’t something trivial, rather it was a new field, which I never experienced with before at the university. I appreciated unfailing attention to details explained by Serghei Rusu and Dumitru Boldureanu during the times of stagnation, allowing us to go in the right direction.
I enjoyed working in an Agile Scrum environment. It allowed us to perfectly synchronize tasks within the team, especially during the first two sprints. Due to the medium complexity of the tasks we had a brief introduction in the world of microcontrollers. The third sprint was easier, I admit that we could accomplish it more quickly, perhaps because we weren’t under the pressure of a real deadline. However, it gave us the necessary time to fine tune some of the previous tasks.
1. Functional Requirements
The project itself represents a distributed system with the main purpose of remotely detecting people’s presence in a room. A server collects data through the network from an Arduino board with a light and motion sensor installed on it, it also provides a RESTful interface for different clients to remotely view the sensors status.
Here is the general topology of the product:
Fig. 1 The system general topology
The digital light and the PIR sensor (detects motion) are installed on an Arduino UNO board. Using a set of LEDs, the board shows the sensors status so that we can easily see if it is operational.
The sensor firmware and communication protocol ensure a minimal power usage, this way the sensor can use a battery pack for a power supply unit.
Unfortunately, we could not install the Wireless module on the Arduino board, that’s why we used a network cable to ensure communication with the server.
We used Raspberry PI 2 as server and it is in the same network with the Arduino board. It receives data from the Arduino board and stores it locally. For the database we used the H2 Database Engine since it’s an open source software and also an embeddable database for Java. The advantage of having the pure Java implementation is that it is easier to deploy and usually contains a single JAR library file, with consistency in its function call (API).
We installed a Web Server (Tomcat) on the Raspberry PI, it can be accessed from the LAN and has some WEB pages to display the status of the remote sensors. The WEB pages have a clean and responsive design due to the use of the Bootstrap framework. All data stored in the database is used to display the statistics for a specified period of time.
For the product’s ease-of-use, the server can be accessed with different clients via the REST interface. The clients we have are an Android application and an Android widget, a web site and a Windows desktop tray application.
2. Connecting the light sensor
To measure the illuminance in the room we used the BH1750FVI Breakout Board (GY-30). The value of light is converted to lux. Here is how we connect our Arduino with the (GY-30) breakout board:
Fig. 2 Measuring illuminance with a BH1750FVI Breakout Board (GY-30) and an Arduino
Arduino (Nano, Uno, Pro Mini) |
Arduino (Mega, Mega 2560 |
GY-30 BH1750FVI breakout board |
---|---|---|
5V oder 3.3V (VCC) | 5V oder 3.3V (VCC) | VCC |
GND | GND | GND |
A4 (SDA) | D20 (SDA) | SDA |
A5 (SCL) | D21 (SCL) | SCL |
3. Connecting the PIR sensor
To detect movement in the room we connected our HC-SR501 PIR (motion) sensor to the Arduino board. The Arduino powers the PIR sensor and when movement is detected, the PIR sensor sends a signal to the Digital Pin 2. The Arduino responds to this signal by flashing the LED attached to Pin 13.
We connect our Arduino and the PIR Motion Sensor like this:
Fig. 3 Motion detection with a PIR Motion sensor (HC-SR501) and an Arduino
4. Optimizing the energy consumption of Arduino
There are several ways to reduce the energy consumption of the Arduino board. We can:
- reduce the frequency the card works at;
- disable certain parts of the map;
- send the card “to sleep” during specific time intervals.
The second solution is simple to use, but only provides a small gain. The modules affected are the UART (Universal asynchronous receiver/transmitter), the built in timers, and the ADC converter for analog inputs. In our case, we cannot turn them off because we use the timers for interruptions and the analog inputs for measurements.
However, there are various power management modes that can reduce energy consumption. The different sleep modes are:
- SLEEP_MODE_IDLE;
- SLEEP_MODE_ADC;
- SLEEP_MODE_PWR_SAVE;
- SLEEP_MODE_STANDBY;
- SLEEP_MODE_PWR_DOWN.
Note that the less power the mode consumes, the fewer features it includes. Thus, the PWR_DOWN mode, which is the one that uses the least energy, cannot be awakened by an external interruption or by the WDT (watch dog timer). In IDLE mode, the timers, the ADC, and the UART remain active.
When the microcontroller is in one of these sleep modes, the code execution is paused. To resume, you must “wake up” the card. This is done by the timer or by the external interruption. The timer is used to check the status of the Arduino board every 60 seconds by sending a message to the server and flashing a LED. The external interruption is caused by the PIR sensor, when motion is detected the LED turns on and a message is sent to the server.
5. Implementation of the timer and external interruptions
According to the project specifications, when the status of the sensors changes, either motion is detected, or the light in the room was turned on or off, the Arduino must turn on the LED to indicate the change. The rest of the time, the device is in standby mode. To implement these features we need to specify the Arduino pins that will receive the signals:
[code language=”java”]
#define lightLedPin 8
#define motionLedPin 9
#define pirPin 2
// Calibration time for PIR sensor, 10-60ms according to datasheet.
#define pirSensorCalibrationTime 10
// Depends on the SLEEP_XS parameter in the LowPower.powerDown(SLEEP_4S, ADC_OFF, BOD_OFF)
// function. X * HBInterval = seconds after that, the control message is sent.
float HBInterval = 2;
…
void setupPins() {
// Set input & output pin for movement detection.
pinMode(motionLedPin,OUTPUT);
pinMode(pirPin,INPUT);
// Set output pin for light detection.
pinMode(lightLedPin,OUTPUT);
}
[/code]
Pin number 2 receives the signal when there is movement and the LED connected to pin number 9 turns on. When the light sensor captures data from the environment, it sends a signal to pin number 8 to turn the LED on, indicating that the measurements are done. Also we set up the timer to check the sensors status at regular intervals.
[code language=”java”]
void setupInterruptions() {
// Set external interruption for PIR sensor.
attachInterrupt(digitalPinToInterrupt(2), motionWakeUp, CHANGE);
}
[/code]
6. Sending the message from Arduino to Raspberry PI
The Arduino sends a message to the server when the sensors status changes. First you have to specify the MAC and Arduino IP address, as well as the Raspberry PI IP address.
[code language=”java”]
// The MAC address and IP address for the controller. They will depend on the local network.
byte mac[] = { 0x90, 0xA2, 0xDA, 0x0F, 0xB6, 0x47 };
IPAddress ip(172, 17, 41, 54);
unsigned int localPort = 9876;
IPAddress remoteIP(172, 17, 41, 71);
unsigned int remotePort = 9876;
// An EthernetUDP instance to let us send and receive packets over UDP.
EthernetUDP Udp;
[/code]
We use the UDP protocol to transmit the messages, because we do not need to establish a permanent connection and it’s not so important to check whether the message reached the destination or not.
[code language=”java”]
/**
* Setup device IP, MAC and local port.
*/
void setupEthernet() {
// Start the Ethernet and UDP.
Ethernet.begin(mac, ip);
Udp.begin(localPort);
}
[/code]
The data transmitted contains information about the motion and light sensors, and the control message that is transmitted every 60s indicates that the device is working.
[code language=”java”]
/*
* Send UDP message from Arduino to server.
*
* @param sensor_1 PIR Sensor ID, ex. "P"
* @param value_1 value from PIR Sensor
* @param sensor_2 Light Sensor ID, ex. "L"
* @param value_2 value from Light Sensor
* @param sensor_3 Heartbeat ID, ex. "H"
* @param value_3 value for HB
*/
void sendUDP(String sensor_1, String value_1, String sensor_2, String value_2, String hb, String value_3) {
String messageToServer = sensor_1 + " " + value_1 + "|" + sensor_2 + " " + value_2 + "|" + hb + " " + value_3;
Serial.println(messageToServer);
Udp.beginPacket(remoteIP, remotePort);
Udp.print(messageToServer);
Udp.endPacket();
digitalWrite(lightLedPin, HIGH);
delay(1000L);
digitalWrite(lightLedPin, LOW);
}
[/code]
We can change the frequency of the heartbeat sent from the Arduino board to the server using the REST interface:
[code language=”java”]
/**
* Retrieve the heartbeat frequency value through REST API and set it in the
* configuration file on the server.
*
* @param incomingSettings the JSON message containing the heartbeat frequency value
* @return 200 OK in case of success and 400 Bad Request in case of failure
*/
@POST
@Path("/setHBFrequency")
@Consumes(MediaType.APPLICATION_JSON)
public Response adjustHBThresholdValues(InputStream incomingSettings) {
JSONObject jsonObject = new JSONObject(readIncommingMessage(incomingSettings));
int HBFrequency = -1;
try {
HBFrequency = jsonObject.getInt("HBFrequency");
} catch (Exception e) {
log.error("Exception in adjustHBThresholdValues() function, SensorConfiguration class : " + e.getMessage());
}
if (HBFrequency >= 0) {
configManager.setConfigValue("HBFrequency", HBFrequency + "");
try {
Server s = Server.getInstance();
s.respondToClient(HBFrequency + "", InetAddress.getByName(Constants.ARDUINO_IP), Constants.ARDUINO_PORT);
} catch (SocketException | UnknownHostException e) {
log.error("Error sending configuration data to Arduino.");
}
return Response.status(200).build();
} else {
log.error("Wrong input data in adjustSensorsThresholdValues() method");
return Response.status(400).build();
}
}
[/code]
On the Arduino, we have a method that reads the incoming message via network:
[code language=”java”]
/**
* Read the incoming message from the network and send back an OK message.
*/
void readUDP() {
// Buffer to hold the incoming packet from the network.
char packetBuffer[UDP_TX_PACKET_MAX_SIZE];
int packetSize = Udp.parsePacket();
if(packetSize) {
Udp.read(packetBuffer,UDP_TX_PACKET_MAX_SIZE); // Read the packet into packetBufffer.
int valueFromTheServer = atoi(&packetBuffer[0]); // Set the incoming data to @HBInterval.
HBInterval = valueFromTheServer / 4;
sendOK();
}
}
[/code]
7. The Raspberry PI server
The communication between Arduino and the server is ensured by using the UDP protocol. Under the hood it uses a java.net.DatagramSocket to listen on a specific port for incoming DatagramPacket datagrams. This is a simple way to receive data because we can retrieve and then process it. The data transmitted by Arduino is in String format and contains information about the sensors. This String is parsed to extract the information for each sensor. With the extracted information, we create an object Message that encapsulates the current status of the sensors and we store it in the database:
[code language=”java”]
/**
* CONSTRUCTOR
* @param isHeartbeat boolean – Specifies if it is a control message from Arduino
* @param pirSensorVal boolean – The value indicated by the PIR Sensor can be either 1 (movement detected) or 0 (no movement)
* @param lightSensorVal
* @param timeReceived
*/
public Message(boolean isHeartbeat, boolean pirSensorVal, int lightSensorVal, String timeReceived) {
this.timeReceived = timeReceived;
this.isHeartbeat = isHeartbeat;
this.pirSensorVal = pirSensorVal;
this.lightSensorVal = lightSensorVal;
}
[/code]
We have a Job that is listening for incoming messages from Arduino:
[code language=”java”]
/**
* This class is responsible for receiving the
* sensors status information from the Arduino Board.
* The main logic is implemented in the <code>run()</code>
* method, ensuring that this activity will be executed in
* a separated thread.
* @author sscerbatiuc
*
*/
public class SensorJob implements Runnable{
private Server server;
private final Logger logger = Log4j.initLog4j(SensorJob.class);
private volatile boolean running = true;
public SensorJob(){
try {
server = Server.getInstance();
} catch (Exception e) {
logger.error(e.getMessage());
}
}
/**
* Defines the way the data should be received from the
* Arduino Board. The method logs the errors to the log file.
* @Override
*/
public void run() {
try {
while(running){
try {
Message receivedMessage = server.readMessage();
if(receivedMessage != null){
int queryAffectedRows = DBQuery.recordMessage(receivedMessage);
System.out.println(receivedMessage.toString());
if(queryAffectedRows <= 0){
logger.error("Message could not be recorded in the database");
}
} else {
logger.error("Null message received from the Arduino board");
}
} catch (Exception e) {
logger.error(e.getMessage());
running = false;
}
}
} catch (Exception e) {
logger.error(e.getMessage());
}
}
}
[/code]
8. The communication protocol
The String transmitted by Arduino contains the following information:
- The illuminance value (in lux) prefixed with a capital letter L (e.g. L 123);
- The motion sensor value, which can be 0 or 1, prefixed with a capital letter P (e.g. P 1 – in case movement has been detected or P 0 – in case the movement stops);
- The value of the control message, prefixed with a capital letter H. Basically, Arduino sends a message when the status of a sensor has changed. When the status of either of the sensors doesn’t change, it means there is no movement in the room or that the value of the light remains the same, Arduino doesn’t send anything. In this case, to make sure that Arduino is working, we have to send a control message accompanied by the sensor values. This message is called a heartbeat.
The values of each sensor are separated by this symbol: ” | ”. The prefix of the sensor is separated from its value by a space. A message from Arduino will have the following format: “H 0 | L 123 | P 1”. By analyzing it, we can say that this is not a control message, the value of light is 123 lux and movement was detected.
9. The REST interface
The REST interface is implemented using JAX-RS and Jersey. JAX-RS is a recent but already well supplied API. It provides specific annotations to bind URI and HTTP verbs for Java methods. JAX-RS also has some features to inject and recover data from the headers of a response or a query. The JAX-RS API provides everything you need in order to retrieve and write what you want in the body of an HTTP request/response. Finally, if Java throws an exception, JAX-RS is able to generate a response with the most relevant HTTP code.
One of the classes from REST API that is responsible for displaying the current status of the sensors looks like this:
[code language=”java”]
/**
*
* @author Nicolae
*
* Shows the current sensors status analyzing data from the database.
*/
@Path("/current")
public class SensorCurrentData {
static Logger log = Log4j.initLog4j(SensorCurrentData.class);
/**
* Get the last entry from the database, i.e. the last sensors status.
*
* @return a <code>JSONObject</code> containing the light sensor value, PIR
* sensor value and the heartbeat value.
*/
@SuppressWarnings("unchecked")
@GET
@Produces("application/json")
public Response getLastSensorState() {
Message message = new Message();
try {
message = DBQuery.getLastEntry();
JsonObject lastSensorState = JsonService.createJSONObject(message);
JSONObject analyzedData = SensorHistoryCriteria.checkForSomeoneInTheRoom();
Iterator<String> keys = analyzedData.keys();
while( keys.hasNext() ){
String key = (String)keys.next();
String value = analyzedData.getString(key);
lastSensorState.addProperty(key, value);;
}
String result = "" + lastSensorState;
return Response.status(200).entity(result).build();
} catch (Exception e) {
log.error("Exception in getLastSensorState() function, SensorCurrentData class : " + e.getMessage());
return Response.status(500).build();
}
}
/**
* Get the real-time situation in the room, if there is someone there and if the
* light is turned on or off.
*
* @return a <code>JSONObject</code> containing the <code>yes</code> or
* <code>no</code> values
*/
@Path("/presence")
@GET
@Produces("application/json")
public Response getRoomState() {
String result = "" + SensorHistoryCriteria.checkForSomeoneInTheRoom();
return Response.status(200).entity(result).build();
}
}
[/code]
The data the server returns is in JSON format.
10. The WEB interface
The components of the distributed system, the Arduino and the Raspberry PI communicate with each other through a defined protocol based on the prescribed configuration. To make the system more dynamic, to better interpret the sensor data, we implemented a WEB interface. And we used the Bootstrap framework to ensure a modern and a responsive interface.
Fig. 4 The Web interface menu
Using the site, you can view the current status of the sensors, so you can tell if there is someone in the room or not. This is established according to an algorithm that analyzes the last data stored in the database. However, the result isn’t solid because there is a 5 seconds error.
The final information that is displayed to the user shows if someone is in the room or not, if the light is on or off and if the control message arrived exactly when expected.
Fig. 5 Current sensors status
Fig. 6 shows us how to change the configuration of the devices, specifically, the transfer rate of the control message from Arduino to the server and the threshold value that tells us if the light in the room is on or not.
Fig. 6 Configuring the sensors
The possibility to view statistics based on the data received and stored in the database is a useful feature of the web interface. The information is displayed as graphs. The following graphs are available:
- total time of active movement in the room during a specific period of time;
- how long the light was on in the room (total time);
- the average and the maximum value of light in the room;
- total number of recorded movements.
11. The desktop application
In addition to the WEB interface there is a desktop application that also gives the possibility to view the current status of the sensors and configure them. The software is written in Java using the Swing graphics library. As long as the application is working, it can be accessed through the icon from the system tray (Fig. 7).
Fig. 7 App System tray icon
Fig. 8 The Desktop application showing the current sensors status
This application provides three features:
- Sensors status – displays the sensors current status;
- Threshold values – sets the threshold light value and frequency of the heartbeat message;
- Heartbeat – displays the time of the last received heartbeat message.
This application sends requests to the server every two seconds, and displays the changes. This can be easily observed in the Sensors Status window, so if the sensors status changes, the icons and the text change as well, thus creating a nice dynamic effect.
The LED in the top-right corner serves as a status indicator. If the connection between the Arduino board and the server is active, the LED is green, otherwise it turns red.
12. The Android app
The third client application that accesses the data stored on the server is written on Android. Generally, it provides the same functionality as the web interface and the desktop application.