Trying to move full speed during the last two weeks of the challenge! In the last few days I have achieved a great milestone in the DomPi project: all the features from the Phase 1 are now fully operational . The missing pieces that I worked out these days have been:
- finalize the C module that acts as a gateway between the remote nodes (via the RF 2.4Ghz comms module) and the Command Center (with the Mosquitto channels). This was already quite advanced, but now it should be the final version
- Weather and Pollution forecast. Display weather and pollution information
- Alarm - Basic. Implement a basic alarm system that detects movement at home and triggers an action
Let´s have a quick look at the current Project Status and go to the details!
Previous Posts
Project Status
C Module - Gateway between Mosquitto and RF24
This week I have finalized the C code shown in the PiIoT - DomPi 08: Setting up the Command Center (3) openHAB, mosquitto, RF24. Now all of the nodes are present in the code and can send and receive messages to the Command Center:
Node Name | Node ID | Sends data | Accepts command |
---|---|---|---|
Kids room | 1 | Temperature, humidity, luminosity and movement | Force_send_message (note 1) |
Parents room | 2 | Temperature, humidity, luminosity and movement | Force_send_message |
Living room | 3 | Temperature, humidity, luminosity and movement | Force_send_message Turn on/off a light Turn on/off all lights |
Garden | 4 | Temperature, humidity and luminosity Future: rain drop counter, movement, soil moisture | Force_send_message |
Garage | 5 | Temperature, humidity, luminosity and movement Ultrasonic distance from the rear wall to the car Future: two more ultrasonic distances (lateral walls), flood detection | Force_send_message Get if car is in the garage |
Note (1): Force_send_message - if the Command Center needs to force the node to send its sensors' status. Also it can be used as a "ping" to determine of the node is alive
In the said post, I shared some details of the protocol and how the C gateway works. Let me just focus on this post on some details not covered at that time. The RF communication is based on three types of messages as seen in the picture below. There will be a forth type of message once the Garden node is expanded to be able to send the rain drop and moisture data.
On the Mosquitto side of things, each room or smartspace publishes and receives data through its own Mosquitto channel. For example, the living room gets and sends data via /casa/salon/... Within the room, each sensor and actuator again sends or receives data via its own channel: the temperature in the living room uses /casa/salon/temperatura. This approach much easier to expand the project and also to test and debug it since you keep all of the components independent from each other. Then openHAB will manipulate the data as required.
The C code can be found below. Please note the comments from the previous post on how the code is written as well as the required Makefile to successfully compile it in the Raspberry Pi.
#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, movement const uint16_t action_node1 = 1; //Arduino at Kids bedroom: temp, hum, luminosity, movement const uint16_t sensor_node2 = 2; //Arduino at Parents room: temp, hum, luminosity, movement const uint16_t action_node2 = 2; //Arduino at Parents room: temp, hum, luminosity, movement const uint16_t sensor_node3 = 3; //Salon IR const uint16_t action_node3 = 3; //Salon IR const uint16_t sensor_node4 = 4; //Arduino in the Garden room: temp, hum, luminosity, movement const uint16_t action_node4 = 4; //Arduino in the Garden room: temp, hum, luminosity, movement const uint16_t sensor_node5 = 5; //Arduino in the Garage room: temp, hum, luminosity, movement, car_detection const uint16_t action_node5 = 5; //Arduino in the Garage room: temp, hum, luminosity, movement, car_detection const char* action_channel1_lamp = "casa/habebes/luz"; //kids bedroom, channel for the lamp const char* action_channel2_lamp = "casa/dormitorio/luz"; //parents bedroom, channel for lamp const char* action_channel3_lamp = "casa/salon/luz"; //Living room, channel for lamp const char* action_channel4_alllamp = "casa/pasillo/luz"; //Actuate on all lamps const char* action_channel1_force = "casa/habebes/forzar"; //Kids room - force the remote node to send status message to Command Center const char* action_channel2_force = "casa/dormitorio/forzar"; //Parents room - force the remote node to send status message to Command Center const char* action_channel3_force = "casa/salon/forzar"; //Living room - force the remote node to send status message to Command Center const char* action_channel4_force = "casa/jardin/forzar"; //Garden room - force the remote node to send status message to Command Center const char* action_channel5_force = "casa/garage/forzar"; //Garage room - force the remote node to send status message to Command Center const char* action_channel5_iscar = "casa/garage/iscar"; //Garage room - check if the car is there const char* action_channel_casa = "casa/#"; //Subscribe to any channel at 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 RF24 radio(RPI_BPLUS_GPIO_J8_15,RPI_BPLUS_GPIO_J8_24, BCM2835_SPI_SPEED_8MHZ); // Old configRF24 radio(RPI_BPLUS_GPIO_J8_32,RPI_BPLUS_GPIO_J8_26, 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 (except garage) struct message_sensor { int16_t temperature; unsigned char humidity; unsigned char light; unsigned char motion; unsigned char dooropen; //Not used, future purposes }; //Message to be received from the garage node with the ultrasonic distances struct message_sensor_usonic { int16_t temperature; unsigned char humidity; unsigned char light; unsigned char motion; uint16_t distance_sensor1; uint16_t distance_sensor2; uint16_t distance_sensor3; }; //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 which node to send the message uint16_t target_node=100; if (strcmp(topic, action_channel4_alllamp) == 0) { //If this channel, then send message to node 3 - living room - to actuate on all lamps target_node = action_node3; } else if (strcmp(topic, action_channel3_lamp) == 0) { //If this channel, then send message to node 3 target_node = action_node3; } else if (strcmp(topic, action_channel3_force) == 0) { //If this channel, then send message to node 3 target_node = action_node3; } else if (strcmp(topic, action_channel2_lamp) == 0) { //If this channel, then send message to node 3 since the lamps are controlled by node 3 target_node = action_node3; } else if (strcmp(topic, action_channel2_force) == 0) { //If this channel, then send message to node 2 target_node = action_node2; } else if (strcmp(topic, action_channel1_lamp) == 0) { //If this channel, then send message to node 3 since the lamps are controlled by node 3 target_node = action_node3; } else if (strcmp(topic, action_channel1_force) == 0) { //If this channel, then send message to node 1 target_node = action_node1; } else if (strcmp(topic, action_channel4_force) == 0) { //If this channel, then send message to node 4 target_node = action_node4; } else if (strcmp(topic, action_channel5_force) == 0) { //If this channel, then send message to node 5 target_node = action_node5; } else if (strcmp(topic, action_channel5_iscar) == 0) { //If this channel, then send message to node 5 target_node = action_node5; } 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: //This is what OpenHab sends via mosquito as per default //It can be 0 or 1, but can be modified in Openhab 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) { //If this channel, it is kids room, so actuate on LUZ_1 info = LUZ_1; } else if (strcmp(topic, action_channel2_lamp) == 0) { //If this channel, it is parents room, so actuate on LUZ_2 info = LUZ_2; } else if (strcmp(topic, action_channel3_lamp) == 0) { //If this channel, it is living room, so actuate on LUZ_3 info = LUZ_3; } else if (strcmp(topic, action_channel4_alllamp) == 0) { //Actuate on all the lamps //I use a temporal value, as depending on the cmd, it will be on/off info = TODAS_LUCES_TEMP; } else if (strcmp(topic, action_channel1_force) == 0) { //requested to force node to send status //the info to send is irrelevant and will be ignored, but lets //replicate Force message: info = FORCE_SEND_MSG; } else if (strcmp(topic, action_channel2_force) == 0) { info = FORCE_SEND_MSG; } else if (strcmp(topic, action_channel3_force) == 0) { info = FORCE_SEND_MSG; } else if (strcmp(topic, action_channel4_force) == 0) { info = FORCE_SEND_MSG; } else if (strcmp(topic, action_channel5_force) == 0) { info = FORCE_SEND_MSG; } else if (strcmp(topic, action_channel5_iscar) == 0) { info = GET_ISCAR_GARAGE; } 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) { //Send the command to actuate on all of the lamps 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) { //Function which checks if the command requires a special action //For example the “all the lamps” requires a special function to actuate on all lights if (strcmp(topic, action_channel4_alllamp) == 0) { enviar_msg_RF24_todasluces(target_node, cmd); } else { enviar_msg_RF24_aux(target_node, cmd, info); } } void revisar_cola() { //Checks if there is any action pending in the queue 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); //Extract data on the Mosquitto message received //Firs step to ignore any message marked as “retained” //we will just process newly received messages if (mosqmessage->retain) { //If a mosquitto message is marked as retain, it is ignored printf("Mensaje ignorado - marcado como retained.\n"); return; } //Determine cmd received cmd = determinar_cmd((char*)mosqmessage->payload); if (cmd==100) return; //if it is 100 we jump out of the loop //Determine the info to transmit info = determinar_info(mosqmessage->topic); if (info==FORCE_SEND_MSG) cmd = FORCE_SEND_MSG; if (info==TODAS_LUCES_TEMP) { //If t is all the lights, then I need to check if it was ON or 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); // Now we add a new element to the queue with the extracted information elem_lista e; e.cmd = cmd; e.info = info; e.target_node = target_node; e.topic = mosqmessage->topic; lista_acciones.push_back(e); } }; MyMosquitto mosq; void suscribirse_canales() { //Subscribe to all of the relevant channels. It could be done using the subscribe to “casa/#” //However, by doing it one by one I can track if there was any error subscribing to it int val; val = mosq.subscribe(0, action_channel1_lamp); if (val!=MOSQ_ERR_SUCCESS) printf("Error while subscribing to the mosquitto channel Lamp 1. "); val = mosq.subscribe(0, action_channel2_lamp); if (val!=MOSQ_ERR_SUCCESS) printf("Error while subscribing to the mosquitto channel Lamp 2. "); val = mosq.subscribe(0, action_channel3_lamp); if (val!=MOSQ_ERR_SUCCESS) printf("Error while subscribing to the mosquitto channel Lamp 3. "); val = mosq.subscribe(0, action_channel4_alllamp); if (val!=MOSQ_ERR_SUCCESS) printf("Error while subscribing to the mosquitto channel All Lamp. "); val = mosq.subscribe(0, action_channel1_force); if (val!=MOSQ_ERR_SUCCESS) printf("Error while subscribing to the mosquitto channel Force 1. "); val = mosq.subscribe(0, action_channel2_force); if (val!=MOSQ_ERR_SUCCESS) printf("Error while subscribing to the mosquitto channel Force 2. "); val = mosq.subscribe(0, action_channel3_force); if (val!=MOSQ_ERR_SUCCESS) printf("Error while subscribing to the mosquitto channel Force 3. "); val = mosq.subscribe(0, action_channel4_force); if (val!=MOSQ_ERR_SUCCESS) printf("Error while subscribing to the mosquitto channel Force 4. "); val = mosq.subscribe(0, action_channel5_force); if (val!=MOSQ_ERR_SUCCESS) printf("Error while subscribing to the mosquitto channel Force 5. "); val = mosq.subscribe(0, action_channel5_iscar); if (val!=MOSQ_ERR_SUCCESS) printf("Error while subscribing to the mosquitto channel IsCar 5. "); } 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_alllamp); mosq.unsubscribe(0, action_channel1_force); mosq.unsubscribe(0, action_channel2_force); mosq.unsubscribe(0, action_channel3_force); mosq.unsubscribe(0, action_channel4_force); mosq.unsubscribe(0, action_channel5_force); mosq.unsubscribe(0, action_channel5_iscar); 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(); 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; // Have a peek at the data to see the header type network.peek(header); // We can only handle type 1, 2 and 3 sensor nodes for now // Type 1: temperature, hum, luminosity, motion // Type 2: cmd and info // Type 3: as type 1 plus the 3x ultrasonic sensors - mainly for the garage if (header.type == '1') { // Read the message message_sensor sensormessage; 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==9) { //For testing purposes, relevant for checking RF24 range num_RF_node1++; printf(" .. Message to the previous node: %i\n", num_RF_node1); } char buffer [50]; float temperature_rx = sensormessage.temperature/100.0; //Transformation of the temperature into float switch (header.from_node) { case sensor_node1: //Kids room 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); sprintf (buffer, "mosquitto_pub -t casa/habebes/movimiento -m \"%i\"", sensormessage.motion); system(buffer); break; case sensor_node2: //Parents room 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); sprintf (buffer, "mosquitto_pub -t casa/dormitorio/movimiento -m \"%i\"", sensormessage.motion); system(buffer); break; case sensor_node3: //Living room 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; case sensor_node4: //Garden sprintf (buffer, "mosquitto_pub -t casa/terraza/temperatura -m \"%f\"", temperature_rx); system(buffer); sprintf (buffer, "mosquitto_pub -t casa/terraza/humedad -m \"%i\"", sensormessage.humidity); system(buffer); sprintf (buffer, "mosquitto_pub -t casa/terraza/luminosidad -m \"%i\"", sensormessage.light); system(buffer); sprintf (buffer, "mosquitto_pub -t casa/terraza/movimiento -m \"%i\"", sensormessage.motion); system(buffer); break; default: printf("Unknown node %i\n for header.type 1", 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 type 2. Cmd: %i Info:%i\n", header.from_node, cmd_rx, info_rx); } else if (header.type == '3') { message_sensor_usonic sensormessage; network.read(header, &sensormessage, sizeof(sensormessage)); char buffer [70]; float temperature_rx = sensormessage.temperature/100.0; //Transformation of the temperature into float switch (header.from_node) { case sensor_node5: //Garage sprintf (buffer, "mosquitto_pub -t casa/garage/temperatura -m \"%f\"", temperature_rx); system(buffer); sprintf (buffer, "mosquitto_pub -t casa/garage/humedad -m \"%i\"", sensormessage.humidity); system(buffer); sprintf (buffer, "mosquitto_pub -t casa/garage/luminosidad -m \"%i\"", sensormessage.light); system(buffer); sprintf (buffer, "mosquitto_pub -t casa/garage/movimiento -m \"%i\"", sensormessage.motion); system(buffer); // for testing Transformation of the distance from uint into float // float dist_rx = sensormessage.distance_sensor1/100.0; sprintf (buffer, "mosquitto_pub -t casa/garage/ultrasonido1 -m \"%i\"", sensormessage.distance_sensor1); system(buffer); sprintf (buffer, "mosquitto_pub -t casa/garage/ultrasonido2 -m \"%i\"", sensormessage.distance_sensor2); system(buffer); sprintf (buffer, "mosquitto_pub -t casa/garage/ultrasonido3 -m \"%i\"", sensormessage.distance_sensor3); system(buffer); break; default: printf("Unknown node %i\n for header.type 3", header.from_node); break; } printf("Message received from node %i header type 3", header.from_node); } else { // This is not a type we recognize message_sensor sensormessage; network.read(header, &sensormessage, sizeof(sensormessage)); printf("Unknown message received from node %i Header type: %i\n", header.from_node, header.type); } } // 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; }
As a high level summary of how the C Gateway module interacts with the rest of DomPi, a quick summary of the blocks (as shown in post 8).
Weather forecast
For this feature, as well as the Alarm later on, I changed the approach from using more C modules. As discussed in post 9, openHAB has quite a robust rule machine that I am leveraging. This means that I am struggling to keep the right pace of the Challenge while learning the best practices of programming in openHAB... The result is that very probably the code you will find here for the items, sitemap and rule files of openHAB won´t be as neat, optimized and adequate as I´d like to... Despite all this, I do believe that giving the openHAB a try is the right thing for DomPi.
OpenHAB has a binding, Weather-Binding, that helps out on gathering weather information from a set of seven providers in the Internet and show them in your project. In my case, I have selected the Yahoo one as it is simple to use, does not require any registration and includes most of the weather data I am looking.
These providers usually limit the number of times that your IP can ask for an update. This provider is also ok on this - in any case, the weather forecast does not change more than a few times a day.
To use Yahoo, the first step is to locate the where on Earth ID - woeid. Yahoo no longer uses GPS coordinates but the woeid. I found out my city at woeid.rosselliot.co.nz/ It was 502075 for the City of Krakow, Poland. Then I copied the weather-binding into the /openhab/addons folder so that it can be utilized. The last step is to update the openhab.cfg to configure the weather-binding. By "sudo nano openhab.cfg" I modified the following lines, I did have to scroll down a lot, looking first for the Bindings section and then for the Weather Bindings.
# location configuration, you can specify multiple locations # Note: latitude and longitude are NOT required for Yahoo # woeid is ONLY required for Yahoo weather:location.home.name=Krakow #weather:location.<locationId1.latitude= #weather:location.<locationId1>.longitude= weather:location.home.woeid=502075 weather:location.home.provider=Yahoo weather:location.home.language=en weather:location.home.updateInterval=20
The last line establishes a 20 min refresh.
After these steps I was able to partially get the weather data I was looking for, but not all of it. For example, I didn't have the UV index or the forecast for tomorrow. After some trials and different attemps, I ended up running the binding in the Debug mode so that I could see what I was getting from the Yahoo server. To do so, I followed the instructions here, I added this line to the file logback.xml:
<logger name="org.openhab.binding.weather" level="DEBUG" />
With this, I have been able to fine tune it and I am getting tomorrow´s forecast (I was calling the "current" temperature and... well there is effectively no "current" temperature for the future ). However, I have not yet been able to receive teh UV index. From the debug mode it seems, Yahoo does not provide it, so I might change the provider in the future.
Pollution forecast
In my current city, we have problems with the high pollution, specially during Winter time. Since I like biking to work, I am very interested in knowing how bad is the pollution and decide whether to use a mask or don´t bike that day. There are some webpages that provide different ranges of data. I have selected this one.
How does it work? To display the pollution forecast, there are two steps. Firstly, DomPi downloads the pic from the web server and stores it in a specific folder with always the same file name. Secondly, openHAB looks for this file name and displays the image. Let´s have a look to the details:
- Download the picture. This is achieved by the rule "Download image of pollution" in the rules file of my project. Basically, every day at 6:00 am it request from the above server the daily image that shows visually the forecast for the pollution levels. The name of the image changes everyday, but the format follows always the same approach: http://www.smog.imgw.pl/home/images/krakow/PM25/fapps_map_krakow_1km_2016_m08_d20_0000_pm25_24hr.pngyou can easily detect that what changes is the year, month and day. The above openHAB rule imitates this pattern and daily it changes based on the current day. To download the picture, I use the executeCommandLine function in openHAB, which allows you to run commands as if you where in a terminal window. The command I run is "curl" and it stores the downloaded image always in the same folder and with the same name: webapps/smgimg/contaminacion.png. The rule code is:
rule "Download image of pollution" when Time cron "0 0 6 * * ?" or //Download pic every day at 6am System started //or when the system starts then //First step we create the correct file name. It consists of the year, month and day val String s1 = "curl@@-o@@webapps/smgimg/contaminacion.png@@http://www.smog.imgw.pl/home/images/krakow/PM25/fapps_map_krakow_1km_" val String s2 = "_m" val String s3 = "_d" val String s4 = "_0000_pm25_24hr.png" val yr = now.getYear //Get current year, no zero padding val mt = String::format("%1$02d", now.getMonthOfYear) //Get current month, zero padding val dy = String::format("%1$02d", now.getDayOfMonth) //Get day, zero padding say("Skipping download of the pollution image!") var result = executeCommandLine(s1+yr+s2+mt+s3+dy+s4) end
- Display the image. It is important that the image has always the same name as I have not been able to find out in google how to link a dynamic picture within an item. But it is quite easy to show any image with a fixed name, I found quite useful this post in the openHAB forum. I display the image with the below line in the sitemap file. The refresh parameter reloads the image every 15 mins. It won´t change that often, actually just once a day, but I have not found out yet how to sync the rule with the sitemap and just get it refreshed when needed.
Image url="http://localhost:8080/smgimg/contaminacion.png" height=10 refresh=900000
And below you can see what is the output in openHAB.
This image is quite neat and will help me decide if it is a good day for biking or better not to risk the health of my lungs...
Alarm - Basic
The final feature for the Phase 1 is the Alarm. Due to time limitations, I am focusing on the basic requirements of an alarm: activate and deactivate it, trigger the alarm if movement is detected by sending an email, allow some seconds (60 sec) after the alarm is activated to let me go out from home, allow another 60 secs before triggering the alarm in case I am entering home to have time to deactivate it.
Once again, I am using the openHAB rules to implement all of this instead of C modules. The alarm feature is governed by 7 rules and 4 items in DomPi. It´d be quite lengthy to explain all of them in detail in the post so I will focus on the main points of it and you can always review the code and the comments in the openHAB files attached.
The main rule is called "Check motion sensors". OpenHAB allows lambda expressions so checking if any motion sensor is active is quite simple:
gMotions.members.forEach(motionsensor| if(motionsensor.state==1) mov_detected = true //if any of the sensors is 1, we modify the variable )
The output of the rule is an update on the item called Nodo09MotionDetected, it will turn it ON or OFF. This output is the trigger for the next rule, called "Check if to trigger Alarm - Motion detected PIR". If the Alarm is active, this rule will update the item Nodo09AlarmStatus and turn it ON.
Finally, the rule "Trigger Alarm" comes into play if the item Nodo09AlarmStatus was moved from OFF to ON. This rule checks if we are still waiting the 60 secs after the user pressed the button to activate the alarm (as a reminder, I am giving myself 60 secs to leave home). If we are out of those 60 secs, it starts a timer and counts yet another 60 secs before triggering the alarm: I want to give myself this time if I am entering home to be able to deactivate the alarm.
For easiness, here is the code for these 7 rules:
/* * Rules related to the Alarm * Check motion sensors * Check if alarm needs to be triggered * ... * */ rule "Allow time after alarm is activated" //Allow 60 secs to leave home before raising any alarm when Item Nodo09AlarmSwitch changed from OFF to ON then timer_alarm_activation = createTimer(now.plusSeconds(60)) [| //Once time passed, we just remove the timer. The rule //for triggering the alarm will just check the timer status if (timer_alarm_activation!=null) { timer_alarm_activation.cancel timer_alarm_activation = null } postUpdate(Nodo09AlarmStatus, OFF) //After the 60 initial secs reset the Alarm status postUpdate(Nodo09MotionDetected, OFF) //After the 60 initial secs, reset motion detection ] end rule "Turn off timer if alarm deactivated" //If alarm is turned off while the rule "Allw time after alarm is activated" is //on hold with the timer, this cancels the timer when Item Nodo09AlarmSwitch changed from ON to OFF then if (timer_alarm_activation!=null) { timer_alarm_activation.cancel timer_alarm_activation = null } end rule "Check motion sensors" when Time cron "*/2 * * * * ?" //We check motion sensors every two seconds then mov_detected=false gMotions.members.forEach(motionsensor| if(motionsensor.state==1) mov_detected = true //if any of the sensors is 1, we modify variable ) //To avoid continuous update of Node09MotionDetected, we only update it if the status has changed if (Nodo09MotionDetected.state!=(if(mov_detected) ON else OFF)) Nodo09MotionDetected.postUpdate(if(mov_detected) ON else OFF) end //If button pushed to force the alarm, wait 15 secs and stop it rule "Trigger Alarm - Forced" when Item Nodo09AlarmForce changed from OFF to ON then Nodo09AlarmStatus.postUpdate(ON) timer_alarm_forced = createTimer(now.plusSeconds(15)) [| Nodo09AlarmStatus.postUpdate(OFF) Nodo09AlarmForce.postUpdate(OFF) if (timer_alarm_forced!=null) { timer_alarm_forced.cancel timer_alarm_forced = null } ] end rule "Check if to trigger Alarm - Motion detected PIR" when Item Nodo09MotionDetected changed from OFF to ON then //If the alarm is active, then modify the Alarm Status to ON and ring the Alarm //If the alarm is deactivated, do nothing if(Nodo09AlarmSwitch.state==ON) { Nodo09AlarmStatus.postUpdate(ON) alarm_report_send_email = "Motion detected now in room X" //prepare report to send per email alarm_report_internal = "Alarm!!! Motion detected and Alarm not deactivated within 60s" } end rule "Trigger Alarm" //First wait 60 secs and check again that there is a status of alarm, this will avoid false alarms when Item Nodo09AlarmStatus changed from OFF to ON then //if timer_alarm_activation is null, this means that the alarm was activated //and the 60 secs allowance to leave home have ellapsed, therefore, we should trigger the alarm if (timer_alarm_activation==null) { say("Allowing 60secs before triggering alarm") timer_alarm_trigger = createTimer(now.plusSeconds(60)) [| //Check again status of the Alarm before triggering it if (Nodo09AlarmStatus.state==ON) { say(alarm_report_internal) //sendMail("mg.serigop@gmail.com", "DomPi - Alarm. Motion detected", alarm_report_send_email) say("SendMail deactivated") } else say("Alarm deactivated. False Alarm") if (timer_alarm_trigger!=null) { timer_alarm_trigger.cancel timer_alarm_trigger = null alarm_report_internal ="" alarm_report_send_email="" } ] } else { say("Alarm not triggered - we are still within 60 secs from activation") } end rule "Cancel Trigger Alarm" //Cancels timer for the alarm trigger when Item Nodo09AlarmStatus changed from ON to OFF then if (timer_alarm_trigger!=null) { timer_alarm_trigger.cancel timer_alarm_trigger = null alarm_report_internal ="" alarm_report_send_email="" say("Alarm deactivated. False Alarm") } end
Other improvements this week
While playing with the alarm and the weather forecast, I have learned some more things about openHAB that I have applied to the existing set-up. Some improvements I worked on are:
- Car presence. The car presence feature only displays an icon showing the current status out of three possible states: car, no car and undefined. I created the icons and added them to the webapps/images folder. The important thing is that they are 30x30 and .png. In my case I added these files: car.png (undefined status), car-on.png and car-off.png for the states of car/no-car respectively
- Light control. I improved the appearance of the control for the lights
- Show all the rooms. Now I show all of the rooms and by clicking on them you have access to the relevant sensors and actuators (temperature, ultrasonic distance, etc, etc)
This is all for this post! Hope to implement a couple more of features from the Phase 2 still next week!