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!