These last two weeks have been quite hectic in terms of progressing with DomPi. My first intention was to develop most of the features and functionalities in C++ modules which would connect among them and with the openHAB via Mosquitto messages. When I was half way through it, I have gladly discovered that most of the C coding could be performed directly with the openHAB via the rules and actions that this platform supports.
What were the implications? A complete redesign of the DomPi development in this area... and investing long hours and days on understanding how to get code done in openHAB. However, my expectation is that the final result will be much more robust and scalable than the previous C++ modules-approach. Time will tell!!
This post covers three key components:
- the C++ module that connects the external RF24 messaging with the internal mosquitto channels,
- the initial openHAB sitemaps, items and rules
- the Presence Emulator feature to simulate that I am at home instead of enjoying my vacations
Previous Posts
Project Status
RF24 C++ Gateway
This module governs the RF board, enabling the communication with the remote nodes. It will receive and send any data and commands at DomPi. Internally, it interfaces with the Mosquitto hub in the Command Center (RPI3), subscribing and publishing to the relevant mqtt channels. Via these channels it shares the information received from the nodes with the openHAB and gets commands from it - like turn on/off the lights, provide updated temperature, humidity or movement detection.
As a quick summary, this is the architecture in a nutshell - further details at PiIoT - DomPi 08: Setting up the Command Center (3) openHAB, mosquitto, RF24:
#include <RF24/RF24.h>
#include <RF24Network/RF24Network.h>
#include </usr/include/mosquittopp.h>
#include </usr/include/mosquitto.h>
#include <iostream>
#include <ctime>
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <list>
#include "Defs_y_Clases.h"
/**
* g++ -L/usr/lib main.cc -I/usr/include -o main -lrrd
**/
// Constants that identify nodes
const uint16_t pi_node = 0;
const uint16_t sensor_node1 = 1; //Arduino at Kids bedroom: temp, hum, luminosity
const uint16_t action_node1 = 1; //Arduino at Kids bedroom: temp, hum, luminosity
const uint16_t sensor_node2 = 2;
const uint16_t action_node2 = 4;
const uint16_t sensor_node3 = 3; //Salon IR
const uint16_t action_node3 = 3; //Salon IR
const char* action_channel1_lamp = "casa/habebes/luz"; //kids bedroom, channel for the light
const char* action_channel2_lamp = "casa/dormitorio/luz"; //parents bedroom
const char* action_channel3_lamp = "casa/salon/luz"; //Living room
const char* action_channel4_lamp = "casa/pasillo/luz"; //Corridor
const char* action_channel3_force = "casa/salon/forzar"; //Living room - force message
const char* action_channel_casa = "casa/#";
int num_RF_node1 = 0; //Number of RF messages received from node 1. For testing purposes
#define TODAS_LUCES_TEMP 88
// CE Pin, CSN Pin, SPI Speed
// Old configRF24 radio(RPI_BPLUS_GPIO_J8_32,RPI_BPLUS_GPIO_J8_26, BCM2835_SPI_SPEED_8MHZ);
RF24 radio(RPI_BPLUS_GPIO_J8_15,RPI_BPLUS_GPIO_J8_24, BCM2835_SPI_SPEED_8MHZ);
RF24Network network(radio);
// Time between checking for packets (in ms)
const unsigned long interval = 1000;
// Structure of our messages
//Message to be sent to the remote nodes
struct message_action {
unsigned char cmd;
unsigned char info;
};
//Message to be received from the remote nodes
struct message_sensor {
int16_t temperature;
unsigned char humidity;
unsigned char light;
unsigned char motion;
unsigned char dooropen; //Not used, future purposes
};
//Main loop takes actions but do not execute right away
//actions to be sent are stored in a list and processed afterwards
struct elem_lista {
unsigned char cmd;
unsigned char info;
uint16_t target_node;
char* topic;
};
// List of message_action to process
std::list<elem_lista> lista_acciones;
//Auxiliary functions
uint16_t seleccionar_nodo(char* topic) {
//Identifies to wich node to send the message
uint16_t target_node=100;
if (strcmp(topic, action_channel4_lamp) == 0) {
//si es la lampara del pasillo, envio el mensaje al nodo 3, salon, para que actue
target_node = action_node3;
} else if (strcmp(topic, action_channel3_lamp) == 0) {
//si es la lampara del salon, envio el mensaje al nodo 3, salon, para que actue
target_node = action_node3;
} else if (strcmp(topic, action_channel3_force) == 0) {
//si es la lampara del salon, envio el mensaje al nodo 3, salon, para que actue
target_node = action_node3;
} else if (strcmp(topic, action_channel2_lamp) == 0) {
//si es la lampara del dormitorio, envio el mensaje al nodo 3, salon, para que actue
target_node = action_node3;
} else if (strcmp(topic, action_channel1_lamp) == 0) {
//si es la lampara de las ninnas, envio el mensaje al nodo 3, salon, para que actue
target_node = action_node3;
}
return target_node;
}
unsigned char determinar_cmd(char* payload) {
//Determines the command that has been received via RF24
int load = atoi(payload);
unsigned char val=100;
switch (load) {
case 0: //Ojo, es lo que envia Openhab a traves de mosquitto.
//Suele ser 0 o 1 pero puedo modificarlo en default items
val = LUZ_OFF;
break;
case 1:
val = LUZ_ON;
break;
case FORCE_SEND_MSG:
val = FORCE_SEND_MSG;
break;
default:
printf("Unknown message: %s\n", payload);
val = 100;
break;
}
return val;
}
unsigned char determinar_info(char* topic) {
//Determine info to transmit
unsigned char info = NO_ACTION;
if (strcmp(topic, action_channel1_lamp) == 0) {
//si es la lampara de las ninnas, LUZ_1
info = LUZ_1;
} else if (strcmp(topic, action_channel2_lamp) == 0) {
//si es la lampara del dormitorio, LUZ_2
info = LUZ_2;
} else if (strcmp(topic, action_channel3_lamp) == 0) {
//si es la lampara del salon, LUZ_3
info = LUZ_3;
} else if (strcmp(topic, action_channel3_force) == 0) {
//si solicito forzar el envio de datos de sensores
//la info en si es irrelevante
info = FORCE_SEND_MSG;
} else if (strcmp(topic, action_channel4_lamp) == 0) {
//si solicito lamp en canal 4/pasillo quiere decir que es TODAS LUCES
//Uso valor temporal, pq en fiuncion del cmd que ya haya determinado,
//tendre que darle On/OFF
info = TODAS_LUCES_TEMP;
}
return info;
}
void enviar_msg_RF24_aux(uint16_t target_node, unsigned char cmd, unsigned char info) {
// Create message to send via RF24
message_action actionmessage;
actionmessage = (message_action){ cmd, info };
// Send message on RF24 network
RF24NetworkHeader header(target_node);
header.type = '2';
printf("Sending instructions to node %i. Header.type: %c. Cmd: %i, Info: %i\n", target_node, header.type, cmd, info);
if (network.write(header, &actionmessage, sizeof(actionmessage))) {
printf("Message sent\n");
} else {
printf("Could not send message\n");
}
}
void enviar_msg_RF24_todasluces(uint16_t target_node, unsigned char cmd) {
enviar_msg_RF24_aux(target_node, cmd, LUZ_3);
delay(300);
enviar_msg_RF24_aux(target_node, cmd, LUZ_2);
delay(300);
enviar_msg_RF24_aux(target_node, cmd, LUZ_1);
}
void enviar_msg_RF24(char* topic, uint16_t target_node, unsigned char cmd, unsigned char info) {
//Compruebo si es necesario accion especial, como con luces pasillo que apaga todas
if (strcmp(topic, action_channel4_lamp) == 0) {
enviar_msg_RF24_todasluces(target_node, cmd);
} else {
enviar_msg_RF24_aux(target_node, cmd, info);
}
}
void revisar_cola() {
elem_lista e;
while (lista_acciones.size()>0) {
e = lista_acciones.front();
lista_acciones.pop_front();
enviar_msg_RF24(e.topic, e.target_node, e.cmd, e.info);
}
}
// Mosquitto class
class MyMosquitto : public mosquittopp::mosquittopp {
public:
MyMosquitto() : mosquittopp::mosquittopp ("PiBrain") { MyMosquitto::lib_init(); }
virtual void on_connect (int rc) { printf("Connected to Mosquitto\n"); }
virtual void on_disconnect () { printf("Disconnected\n"); }
virtual void on_message(const struct mosquitto_message* mosqmessage) {
unsigned char cmd = 0; //NO_ACTION;
unsigned char info = 0; //NO_ACTION;
// Message received on a channel we subscribe to
//printf("Message found on channel %s: %s\n", mosqmessage->topic, mosqmessage->payload);
printf("Message found on channel %s", mosqmessage->topic);
//Extraer datos del mensaje Mosquitto recibido
//Lo primero es ignorar los mensajes si son "retained": vamos,
//los ignoro si son mensajes almacenados y no nuevos mensajes
if (mosqmessage->retain) {
//If a mosquitto message is marked as retain, it is ignored
printf("Mensaje ignorado - marcado como retained.\n");
return;
}
//Determinar cmd recibido
cmd = determinar_cmd((char*)mosqmessage->payload);
if (cmd==100) return; //en el script original, si era cmd desconocido hace return
//Determinar la info a transmitir
info = determinar_info(mosqmessage->topic);
if (info==FORCE_SEND_MSG) cmd = FORCE_SEND_MSG; //Arreglo el problema de que el FORCE_SEND_MSG no lo envia el openhab
if (info==TODAS_LUCES_TEMP) {
//Si es todas luces, ahora tengo que ver si el cmd era ON u OFF
//y poner TODAS_LUCES_ON / _OFF
if (cmd == LUZ_ON) info = TODAS_LUCES_ON;
else if (cmd == LUZ_OFF) info = TODAS_LUCES_OFF;
}
// Determine target node based on channel
uint16_t target_node;
target_node = seleccionar_nodo(mosqmessage->topic);
elem_lista e;
e.cmd = cmd;
e.info = info;
e.target_node = target_node;
//strncpy(e.topic, mosqmessage->topic, sizeof(mosqmessage->topic));
e.topic = mosqmessage->topic;
lista_acciones.push_back(e);
}
};
MyMosquitto mosq;
void suscribirse_canales() {
int val;
val = mosq.subscribe(0, action_channel1_lamp);
if (val!=MOSQ_ERR_SUCCESS) printf("Hubo error al subscribirse a canal mosquitto 1. ");
val = mosq.subscribe(0, action_channel2_lamp);
if (val!=MOSQ_ERR_SUCCESS) printf("Hubo error al subscribirse a canal mosquitto 2. ");
val = mosq.subscribe(0, action_channel3_lamp);
if (val!=MOSQ_ERR_SUCCESS) printf("Hubo error al subscribirse a canal mosquitto 3. ");
val = mosq.subscribe(0, action_channel4_lamp);
if (val!=MOSQ_ERR_SUCCESS) printf("Hubo error al subscribirse a canal mosquitto 4. ");
val = mosq.subscribe(0, action_channel3_force);
if (val!=MOSQ_ERR_SUCCESS) printf("Hubo error al subscribirse a canal mosquitto 3 Forzar. ");
}
void borrarse_canales() {
mosq.unsubscribe(0, action_channel1_lamp);
mosq.unsubscribe(0, action_channel2_lamp);
mosq.unsubscribe(0, action_channel3_lamp);
mosq.unsubscribe(0, action_channel4_lamp);
mosq.unsubscribe(0, action_channel3_force);
printf("Borrado de los canales\n");
}
int main(int argc, char** argv)
{
// Initialize all radio related modules
radio.begin();
delay(50);
radio.setPALevel(RF24_PA_MAX);
delay(50);
radio.setDataRate(RF24_250KBPS);
delay(50);
radio.setChannel(108);
delay(50);
//radio.enableDynamicPayloads();
//radio.setPayloadSize(6);
delay(5);
network.begin(90, pi_node);
// Print some radio details (for debug purposes)
radio.printDetails();
network.update();
mosq.connect("127.0.0.1");
suscribirse_canales(); //mosq.subscribe(0, action_channel_casa);
while (true) {
// Get the latest network info
network.update();
//printf(".");
// Enter this loop if there is data available to be read,
// and continue it as long as there is more data to read
while ( network.available() ) {
RF24NetworkHeader header;
message_sensor sensormessage;
// Have a peek at the data to see the header type
network.peek(header);
// We can only handle type 1 sensor nodes for now
if (header.type == '1') {
// Read the message
network.read(header, &sensormessage, sizeof(sensormessage));
// Print it out in case someone's watching
printf("Data received from node %i\n", header.from_node);
if (header.from_node==1) {
num_RF_node1++;
printf(" .. Message to the previous node: %i\n", num_RF_node1);
}
char buffer [50];
float temperature_rx = sensormessage.temperature/100.0;
//printf("---Test Temperatura_rf: %i \n", sensormessage.temperature);
//printf("---Test Temperatura_rx: %f \n", temperature_rx);
switch (header.from_node) {
case sensor_node1:
sprintf (buffer, "mosquitto_pub -t casa/habebes/temperatura -m \"%f\"", temperature_rx);
system(buffer);
sprintf (buffer, "mosquitto_pub -t casa/habebes/humedad -m \"%i\"", sensormessage.humidity);
system(buffer);
sprintf (buffer, "mosquitto_pub -t casa/habebes/luminosidad -m \"%i\"", sensormessage.light);
system(buffer);
break;
case sensor_node2:
sprintf (buffer, "mosquitto_pub -t casa/dormitorio/temperatura -m \"%f\"", temperature_rx);
system(buffer);
sprintf (buffer, "mosquitto_pub -t casa/dormitorio/humedad -m \"%i\"", sensormessage.humidity);
system(buffer);
sprintf (buffer, "mosquitto_pub -t casa/dormitorio/luminosidad -m \"%i\"", sensormessage.light);
system(buffer);
break;
case sensor_node3:
//printf("Nodo Salon. Temperatura: %f Humedad %i Luminosidad %i \n", temperature_rx, sensormessage.humidity, sensormessage.light);
//sprintf (buffer, "mosquitto_pub -t casa/salon/temperatura -m \"%f\"", temperature_rx);
printf("Nodo Salon. Temperatura: %f Humedad %i Luminosidad %i Movimiento %i\n", temperature_rx, sensormessage.humidity, sensormessage.light, sensormessage.motion);
sprintf (buffer, "mosquitto_pub -t casa/salon/temperatura -m \"%f\"", temperature_rx);
system(buffer);
sprintf (buffer, "mosquitto_pub -t casa/salon/humedad -m \"%i\"", sensormessage.humidity);
system(buffer);
sprintf (buffer, "mosquitto_pub -t casa/salon/luminosidad -m \"%i\"", sensormessage.light);
system(buffer);
sprintf (buffer, "mosquitto_pub -t casa/salon/movimiento -m \"%i\"", sensormessage.motion);
system(buffer);
break;
default:
printf("Unknown node %i\n", header.from_node);
break;
}
} else if (header.type == '2') {
message_action actionmessage;
network.read(header, &actionmessage, sizeof(actionmessage));
unsigned char cmd_rx = actionmessage.cmd;
unsigned char info_rx = actionmessage.info;
printf("Message received from node %i header tipo 2. Cmd: %i Info:%i\n", header.from_node, cmd_rx, info_rx);
} else {
// This is not a type we recognize
network.read(header, &sensormessage, sizeof(sensormessage));
float temperature_rx = sensormessage.temperature/100;
printf("Unknown message received from node %i Header type: %i\n", header.from_node, header.type);
printf("Nodo Salon. Temperatura: %f Humedad %i Luminosidad %i \n", temperature_rx, sensormessage.humidity, sensormessage.light);
}
}
// Check for messages on our subscribed channels
mosq.loop();
revisar_cola();
delay(interval);
}
borrarse_canales(); //mosq.unsubscribe(0, action_channel_casa);
// last thing we do before we end things
return 0;
}
In order to compile it, I´m using this Makefile and build it via the "make" command in the RPI:
#############################################################################
#
# Makefile for librf24 examples on Raspberry Pi
#
# License: GPL (General Public License)
# Author: gnulnulf <arco@appeltaart.mine.nu>
# Date: 2013/02/07 (version 1.0)
#
# Description:
# ------------
# use make all and make install to install the examples
# You can change the install directory by editing the prefix line
#
prefix := /usr/local
# The recommended compiler flags for the Raspberry Pi
CCFLAGS=-Ofast -mfpu=vfp -mfloat-abi=hard -march=armv7-a -mtune=arm1176jzf-s
#CCFLAGS=
# define all programs
PROGRAMS = cc_RF2mqtt
SOURCES = ${PROGRAMS:=.cpp}
all: ${PROGRAMS}
${PROGRAMS}: ${SOURCES}
g++ ${CCFLAGS} -Wall -I../ -lrf24-bcm -lrf24network -lmosquittopp $@.cpp -o $@
clean:
rm -rf $(PROGRAMS)
install: all
test -d $(prefix) || mkdir $(prefix)
test -d $(prefix)/bin || mkdir $(prefix)/bin
for prog in $(PROGRAMS); do \
install -m 0755 $$prog $(prefix)/bin; \
done
.PHONY: install
Some explanation of the code above
My first call out is that the code is still too much in the development phase, meaning for example, it requires lots of tweaks to enable the communication with all of the remote nodes - it is in testing mode and works perfectly with a couple of them, it should be just a matter of time. All in all, there will be several improvements made in the remaining weeks and posts of the challenge.
High level, the module listens to any new messages coming via
- Mosquitto - see the on_message function from the Mosquitto class. When a mqtt message is received, its action is stored in a list of actions to be processed later on in the main loop. Perhaps this could be "not a must" as potentially you could execute the action right away. However, I want to break this automation and put additional controls in the main loop.
- RF24 - the code processes the RF message within the while loop (network.available()). As soon as the RF24 message is received, it is processed by checking which node sent it (living room, bedrooms, etc), what type of information it is carrying (header.type) and if it is recognized then a mqtt message is sent to the appropriate channel to share the information with openHAB.
The rest of the code should be self-explanatory.
Command Center - Control with openHAB
Rather than having tens of C-modules for developing the DomPi features, like the Presence Emulator or the Car Presence, I will be leveraging the rule engine. This has slowed down the DomPi progress as... I am discovering this as we go! But the final solution looks promising.
During this week, I have added more items to the design in the Post 8, as well as redesigned the sitemap. Now it just looks better, hope you agree with me!
Some snapshots here:
![]() | ![]() |
Items file:
Group All
Group gPiso (All)
Group gLuces (All)
Group gTempers (All)
Group gLuminos (All)
Group gHumidit (All)
Group gMotions (All)
Group gStatus "Estado General" (All)
Group gControls "Controls" (All)
Group gPE "Presence Emulator Schedule" (All)
Group gPE1 "Kids Room" (gPE)
Group gPE2 "Parents Room" (gPE)
Group gPE3 "Living Room" (gPE)
Group P_Salon "Salon" <video> (gPiso)
Group Dormitorio "Dormitorio" <video> (gPiso)
Group Pasillo "Pasillo" <video> (gPiso)
Group Habebes "Habitacion Bebes" <video> (gPiso)
Group gGarage "Garage" <video> (gPiso)
Group gGarden "Terraza" <video> (gPiso)
/* active groups */
Group:Switch:OR(ON, OFF) Lamparas "All Lights [(%d)]" (All)
Group:Number:AVG Temperaturas "Temperatura Media Interior[%.1f °C]" <temperature> (gStatus)
Group:Number:AVG Humedades "Humedad Media Interior[%.1f °C]" <bath> (gStatus)
Group:Number:AVG Luminosidades "Luminosidad Media Interior[%.1f °C]" <sun> (gStatus)
Group:Switch:OR(ON, OFF) Movimientos "Movimientos actuales [(%d)]" <shield> (All)
Group:Switch:OR(ON, OFF) Lights "All Lights [(%d)]" (All)
Group:Switch:OR(ON, OFF) Heating "No. of Active Heatings [(%d)]" <heating> (All)
Group:Number:AVG Temperature "Avg. Room Temperature [%.1f °C]" <temperature> (Status)
Group:Contact:OR(OPEN, CLOSED) Windows "Open windows [(%d)]" <contact> (All)
/* Parents Bedroom */
Number Nodo02Temperatura "Temperatura [%.1f C]" <temperature> (Dormitorio, gTempers) {mqtt="<[dompimqtt:casa/dormitorio/temperatura:state:default]"}
Number Nodo02Humedad "Humedad [%d %%]" <bath> (Dormitorio, gHumidit) {mqtt="<[dompimqtt:casa/dormitorio/humedad:state:default]" }
Number Nodo02Luminosidad "Luminosidad [%d %%]" <sun> (Dormitorio, gLuminos) {mqtt="<[dompimqtt:casa/dormitorio/luminosidad:state:default]" }
Switch Lampara_2 "Luz" (Dormitorio, gLuces) { mqtt=">[dompimqtt:casa/dormitorio/luz:command:ON:1],>[dompimqtt:casa/dormitorio/luz:command:OFF:0]"}
/* Kids Bedroom */
Number Nodo01Temperatura "Temperatura [%.1f C]" <temperature> (Habebes, gTempers) {mqtt="<[dompimqtt:casa/habebes/temperatura:state:default]"}
Number Nodo01Humedad "Humedad [%d %%]" <bath> (Habebes, gHumidit) {mqtt="<[dompimqtt:casa/habebes/humedad:state:default]" }
Number Nodo01Luminosidad "Luminosidad [%d %%]" <sun> (Habebes, gLuminos) {mqtt="<[casapi:casa/habebes/luminosidad:state:default]" }
Switch Lampara_1 "Luz" (Habebes, gLuces) { mqtt=">[dompimqtt:casa/habebes/luz:command:ON:1],>[dompimqtt:casa/habebes/luz:command:OFF:0]"}
/* Living Room */
Number Nodo03Temperatura "Temperatura [%.1f C]" <temperature> (P_Salon, gTempers) { mqtt="<[dompimqtt:casa/salon/temperatura:state:default] "}
Number Nodo03Humedad "Humedad [%d %%]" <bath> (P_Salon, gHumidit) { mqtt="<[dompimqtt:casa/salon/humedad:state:default] "}
Number Nodo03Luminosidad "Luminosidad [%d %%]" <sun> (P_Salon, gLuminos) { mqtt="<[dompimqtt:casa/salon/luminosidad:state:default] "}
Number Nodo03Movimiento "Movimiento [%d]" <shield> (P_Salon, gMotions) { mqtt="<[dompimqtt:casa/salon/movimiento:state:default] "}
Switch Lampara_3 "Luz" (P_Salon, gLuces) { mqtt=">[dompimqtt:casa/salon/luz:command:ON:1],>[dompimqtt:casa/salon/luz:command:OFF:0]"}
/* Corridor */
Number Nodo04Temperatura "Temperatura [%.1f C]" <temperature> (Pasillo, gTempers) {mqtt="<[dompimqtt:casa/pasillo/temperatura:state:default] "}
Number Nodo04Humedad "Humedad [%d %%]" <bath> (Pasillo, gHumidit) {mqtt="<[dompimqtt:casa/pasillo/humedad:state:default] "}
Number Nodo04Luminosidad "Luminosidad [%d %%]" <sun> (Pasillo, gLuminos) {mqtt="<[dompimqtt:casa/pasillo/luminosidad:state:default] "}
Switch Luces_Casa "Todas las Luces" (Pasillo, gLuces) { mqtt=">[dompimqtt:casa/pasillo/luz:command:ON:1],>[dompimqtt:casa/pasillo/luz:command:OFF:0]"}
/* Garage */
Number Nodo05Temperatura "Temperatura [%.1f C]" <temperature> (gGarage, gTempers) {mqtt="<[dompimqtt:casa/garage/temperatura:state:default] "}
Number Nodo05Humedad "Humedad [%d %%]" <bath> (gGarage, gHumidit) {mqtt="<[dompimqtt:casa/garage/humedad:state:default] "}
Number Nodo05Luminosidad "Luminosidad [%d %%]" <sun> (gGarage, gLuminos) {mqtt="<[dompimqtt:casa/garage/luminosidad:state:default] "}
/* Garden */
Number Nodo06Temperatura "Temperatura [%.1f C]" <temperature> (gGarden, gTempers) {mqtt="<[dompimqtt:casa/terraza/temperatura:state:default] "}
Number Nodo06Humedad "Humedad [%d %%]" <bath> (gGarden, gHumidit) {mqtt="<[dompimqtt:casa/terraza/humedad:state:default] "}
Number Nodo06Luminosidad "Luminosidad [%d %%]" <sun> (gGarden, gLuminos) {mqtt="<[dompimqtt:casa/terraza/luminosidad:state:default] "}
/* Status */
Switch Forzar_tx "Actualizar" (gStatus, gControls) { mqtt=">[dompimqtt:casa/salon/forzar:command:ON:7],>[dompimqtt:casa/salon/forzar:command:OFF:7]"}
/* Controls */
Switch Presence_Emulator "Presence Emulator" (gControls)
/* Presence Emulator Items */
DateTime dtLampara_1_on1 "L1 On [%1$tH:%1$tM]" (gPE1)
DateTime dtLampara_1_off1 "L1 Off [%1$tH:%1$tM]" (gPE1)
DateTime dtLampara_1_on2 "L1 On [%1$tH:%1$tM]" (gPE1)
DateTime dtLampara_1_off2 "L1 Off [%1$tH:%1$tM]" (gPE1)
DateTime dtLampara_2_on1 "L2 On [%1$tH:%1$tM]" (gPE2)
DateTime dtLampara_2_off1 "L2 Off [%1$tH:%1$tM]" (gPE2)
DateTime dtLampara_2_on2 "L2 On [%1$tH:%1$tM]" (gPE2)
DateTime dtLampara_2_off2 "L2 Off [%1$tH:%1$tM]" (gPE2)
DateTime dtLampara_3_on1 "L3 On [%1$tH:%1$tM]" (gPE3)
DateTime dtLampara_3_off1 "L3 Off [%1$tH:%1$tM]" (gPE3)
DateTime dtLampara_3_on2 "L3 On [%1$tH:%1$tM]" (gPE3)
DateTime dtLampara_3_off2 "L3 Off [%1$tH:%1$tM]" (gPE3)
/* NTP binding demo item */
DateTime Date "Date [%1$tH:%1$tM %1$tA, %1$td.%1$tm.%1$tY]" <calendar> { ntp="Europe/Madrid" }
/* Internet Weather */
Group Weather_Chart (Weather)
Number Weather_Temperature "Temperatura en Krakow [%.1f °C]" <temperature> (Weather_Chart) { http="<[http://weather.yahooapis.com/forecastrss?w=638242&u=c:60000:XSLT(yahoo_weather_temperature.xsl)]" }
Number Weather_Humidity "Outside Humidity [%.1f %%]" <temperature> (Weather) { http="<[http://weather.yahooapis.com/forecastrss?w=638242&u=c:60000:XSLT(yahoo_weather_humidity.xsl)]" }
Number Weather_Humidex "Humidex [SCALE(humidex.scale):%s]" (Weather)
Number Weather_Temp_Max "Todays Maximum [%.1f °C]" <temperature> (Weather_Chart)
Number Weather_Temp_Min "Todays Minimum [%.1f °C]" <temperature> (Weather_Chart)
Number Weather_Chart_Period "Chart Period"
DateTime Weather_LastUpdate "Last Update [%1$ta %1$tR]" <clock>
/* Locations */
Location DemoLocation "Krakow"
Sitemap file:
Sitemap file:
sitemap default label="DomPi - My Domek"
{
Frame label="Date" {
Text item=Date
}
Frame {
Switch item=Presence_Emulator mappings=[ON="On", OFF="Off"]
Group item=gPE visibility=[Presence_Emulator==ON]
}
Frame {
Group item=P_Salon label="Salon" //icon="firstfloor"
Group item=Dormitorio label="Dormitorio" //icon="firstfloor"
Group item=Habebes label="Habitacion Bebes" //icon="firstfloor"
Group item=Pasillo label="Pasillo" //icon="firstfloor"
Group item=gGarage label="Garage" //icon="firstfloor"
Group item=gGarden label="Terraza" //icon="firstfloor"
}
Frame label="Estado" {
Group item=gStatus
}
Frame label="El Tiempo" {
Text item=Weather_Temperature valuecolor=[Weather_LastUpdate=="Uninitialized"="lightgray",Weather_LastUpdate>90="lightgray",>25="orange",>15="green",>5="orange",<=5="blue"] {
Frame {
Text item=Weather_Temp_Max valuecolor=[>25="orange",>15="green",>5="orange",<=5="blue"]
Text item=Weather_Temp_Min valuecolor=[>25="orange",>15="green",>5="orange",<=5="blue"]
Text item=Weather_Humidity
Text item=Weather_Humidex
Text item=Weather_LastUpdate visibility=[Weather_LastUpdate>30] valuecolor=[Weather_LastUpdate>120="orange", Weather_LastUpdate>300="red"]
}
Frame {
Switch item=Weather_Chart_Period label="Chart Period" mappings=[0="Hour", 1="Day", 2="Week"]
Chart item=Weather_Chart period=h refresh=6000 visibility=[Weather_Chart_Period==0, Weather_Chart_Period=="Uninitialized"]
Chart item=Weather_Chart period=D refresh=30000 visibility=[Weather_Chart_Period==1]
Chart item=Weather_Chart period=W refresh=30000 visibility=[Weather_Chart_Period==2]
}
}
}
/*
Frame label="Conjuntos" {
Text label="Agrupacion" icon="firstfloor" {
Switch item=Lamparas mappings=[OFF="All Off"]
Group item=Movimientos
Text item=Temperaturas
Text item=Humedades
Text item=Luminosidades
}
Text label="Widget Overview" icon="chart" {
Frame label="Binary Widgets" {
Switch item=DemoSwitch label="Toggle Switch"
Switch item=DemoSwitch label="Button Switch" mappings=[ON="On", OFF="Off"]
}
Frame label="Map/Location" {
Mapview item=DemoLocation height=10
}
}
}
*/
}
Once again, please note that there are some items that still require some fine tune. For example, the way Yahoo shares the weather information has changed and I plan to use the Weather Binding from openHAB to get this to work - I left the code you see in the item file just as reference.
Presence Emulator
Strictly speaking, last feature of the project was developed in the PiIoT - DomPi 05: Ready for use Living Room and parents and kids´ bedrooms (lights control via the TV remote). That was "long" time ago and I am very happy to be able to deliver the next feature from the Project Status list
The purpose of the Presence Emulator feature is in this first development to turn on and off the rooms´lights simulating that there is someone at home while we are away on vacations or for the weekend. This is key for us as we live in the basement and want to avoid "surprises" when returning home. I have developed a openHAB set of rules that govern this emulation. You can see the rule file below. Additionally I have added a new item to the item file (as per above), the Presence_Emulator, that allows the user to activate or deactivate the feature. Finally, I did add the item in the sitemap, I just added it to the main page to have it handy. If the main page starts to get busy I might move it out to other pages.
Rules in openHAB are quite straightforward to understand. They follow this structure:
- import block
- variable declarations
- rule:
- rule name. Just call it whatever you want, like "Initialize Presence Emulator"
- when -> trigger conditions. There can be one or more conditions, depending on your needs
- then -> execution block
Don´t get me wrong, the structure is simple, but this can get really complex if your project requires it. To add the rule file, you just need to name it <name>.rules. You can add as many rule files as you need, however, those rules within the same file can share variables. In principle, I will try to keep the rules within the same file, meaning I will be updating this file in future posts.
Rule file
import org.openhab.core.library.types.*
import org.openhab.core.persistence.*
import org.openhab.model.script.actions.*
import org.joda.time.*
//Presence Emulator variables
var Number is_PresenceEmulator_on = 0 //Determines of the Presence Emulator is on or off
val java.util.Random rand = new java.util.Random //object to get random numbers
// variables to store the hour:min in minutes to actuate on the lamps
var Number l1on1=0
var Number l1off1=0
var Number l2on1=0
var Number l2off1=0
var Number l3on1=0
var Number l3off1=0
/**
* Presence Emulator.
* Rules to
* initialize activate and deactivate the Presence Emulator
* check if action is required
* launch a new timetable
*
*/
rule "Initialize Presence Emulator"
when
System started
then
sendCommand(Presence_Emulator, OFF)
is_PresenceEmulator_on = 0
end
rule "Presence Emulator to OFF"
when
Item Presence_Emulator changed from ON to OFF or
Item Presence_Emulator received command OFF
then
is_PresenceEmulator_on = 0
end
rule "Presence Emulator to ON"
when
Item Presence_Emulator changed from OFF to ON or
Item Presence_Emulator received command ON
then
if (is_PresenceEmulator_on == 0) {
say("Creating a new Scheduler")
//If the Presence Emulator was off, we activate it
//If it was on (==1) we do nothing
is_PresenceEmulator_on = 1
//Init Lights 1 and 2. Only first cycle on-off
var DateTime thoy_init = parse(now.getYear() + "-" + now.getMonthOfYear() + "-" + now.getDayOfMonth() + "T00:00")
var DateTime thoy = parse(now.getYear() + "-" + now.getMonthOfYear() + "-" + now.getDayOfMonth() + "T00:00")
//initialize first On to 19:00 plus random minutes between 0 and 30
//Lamp 1 on
l1on1 = 19*60 + rand.nextInt(31)
thoy = thoy_init.plusMinutes(l1on1)
postUpdate(dtLampara_1_on1, new DateTimeType(thoy.toCalendar(null)))
//Lamp 2 on
l2on1 = 19*60 + rand.nextInt(31)
thoy = thoy_init.plusMinutes(l2on1)
postUpdate(dtLampara_2_on1, new DateTimeType(thoy.toCalendar(null)))
//initialize first Off to 21:00 plus random minutes between 0 and 30
//Lamp 1 off
l1off1 = 21*60 + rand.nextInt(31)
thoy = thoy_init.plusMinutes(l1off1)
postUpdate(dtLampara_1_off1, new DateTimeType(thoy.toCalendar(null)))
//Lamp 2 off
l2off1 = 21*60 + rand.nextInt(31)
thoy = thoy_init.plusMinutes(l2off1)
postUpdate(dtLampara_2_off1, new DateTimeType(thoy.toCalendar(null)))
//Init Light 3 only first cycle
//Lamp 3 on. Random number from 20:30 to 21:14
l3on1 = 20*60 + 30 + rand.nextInt(45)
thoy = thoy_init.plusMinutes(l3on1)
postUpdate(dtLampara_3_on1, new DateTimeType(thoy.toCalendar(null)))
//Lamp 3 off. Random number from 22:45 to 23:30
l3off1 = 22*60 + 45 + rand.nextInt(45)
thoy = thoy_init.plusMinutes(l3off1)
postUpdate(dtLampara_3_off1, new DateTimeType(thoy.toCalendar(null)))
}
end
rule "Check if action is required in Presence Emulator"
when
Time cron "0 * * * * ?" //We check every minute if action is required
then
if (is_PresenceEmulator_on == 1) { //We act only if the Presence Emulator is ON
//Calculate current hour and minute and convert to minutes
var Number tnow = now.getMinuteOfDay()
//say(tnow.toString)
//say(l1on1.toString)
//Compare minutes to turn Lamp 1, On cycle 1 with now. If equal, we actuate on the lamp
//Same logic for rest of lamps and actions
if (tnow==l1on1) {
sendCommand(Lampara_1, "ON")
} else if (tnow==l2on1) {
sendCommand(Lampara_2, "ON")
} else if (tnow==l3on1) {
sendCommand(Lampara_3, "ON")
} else if (tnow==l1off1) {
sendCommand(Lampara_1, "OFF")
} else if (tnow==l2off1) {
sendCommand(Lampara_2, "OFF")
} else if (tnow==l3off1) {
sendCommand(Lampara_3, "OFF")
}
}
end
rule "Presence Emulator get new Scheduler after midnight"
when
Time cron "1 0 0 * * ?" //We check every midnight on the 1st second
then
if (is_PresenceEmulator_on == 1) { //We act only if the Presence Emulator is ON
is_PresenceEmulator_on = 0 //We put it to zero, however with the sendCommand below,
//it will be reset to 1
sendCommand(Presence_Emulator, ON) //When forced ON, the rule above will create a new Schedule
}
end
// Creates an item that stores the last update time of this item
rule "Records last weather update time"
when
Item Weather_Temperature received update
then
postUpdate(Weather_LastUpdate, new DateTimeType())
end
// This rule will be used to test Scale transformation service
rule "Compute humidex"
when
Item Weather_Temperature changed or
Item Weather_Humidity changed
then
var Number T = Weather_Temperature.state as DecimalType
var Number H = Weather_Humidity.state as DecimalType
var Number x = 7.5 * T/(237.7 + T)
var Number e = 6.112 * Math::pow(10, x.doubleValue) * H/100
var Number humidex = T + (new Double(5) / new Double(9)) * (e - 10)
postUpdate(Weather_Humidex, humidex)
end
The key part is the rule called "Presence Emulator to ON", which is called when the feature is activated. Its first action is to create the scheduler for turning on and off the lights for the day of today. In this first development I have just added 1 on cycle and 1 off cycle per light. The light in the living room has different hours than the other two for turning on and off. To make it more real, I have randomized all of the times, so that each day the lights turn on and off at different hours.
This approach is quite basic and in future developments I plan to add: automatic addition of another cycle for Winter (it will differentiate between Summer and Winter since where I live there is big time differences) and I will add some random cycles (from time to time, to turn on/off the lights more times per day). Not sure if I will be able to make it within the PiIoT challenge as I will need to focus on adding more features. In any case, in the items file, I have already added a placeholder for a second cycle for all of the lights.
Finally, a cool feature from openHAB is that you can hide/unhide some items in the User Interface by adding the "visibility" label. In the screenshot above of openHAB for the main page, you can see the buttons to activate/deactivate the Presence Emulator. If it is "On", openHAB will display an additional clickable line that brings you to the scheduler to check the hours when the lights will be turned on and off. See the screenshots below.
![]() |
This is all for this post! As noted above, please take these files as interim and forgive any bad-coding - I´m just discovering this as we go!







