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

PiIoT - DomPi: ApplicationPiIoT - DomPi: IntroPiIoT - DomPi 02: Project Dashboard and first steps in the Living room
PiIoT - DomPi 03: Living room, light control via TV remotePiIoT - DomPi 04: Movement detection and RF2.4Ghz commsPiIoT - DomPi 05: Ready for use Living Room and parents and kids´ bedrooms
PiIoT - DomPi 06: Setting up the Command Center - RPI3PiIoT - DomPi 07: Setting up the Command Center (2)PiIoT - DomPi 08: Setting up the Command Center (3) openHAB, mosquitto, RF24
PiIoT - DomPi 09: Presence EmulatorPiIoT - DomPi 10: Garage node (slave)PiIoT - DomPi 11: Ready to use. Garage node (master & slave)
PiIoT - DomPi 12: Ready to use. Control Panel and Garden Node

 

Project Status

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 IDSends dataAccepts command
Kids room1Temperature, humidity, luminosity and movementForce_send_message (note 1)
Parents room2Temperature, humidity, luminosity and movementForce_send_message
Living room3

Temperature, humidity, luminosity and movement

Force_send_message

Turn on/off a light

Turn on/off all lights
Garden4

Temperature, humidity and luminosity

Future: rain drop counter, movement, soil moisture

Force_send_message
Garage5

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.

RF messages

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).

RF-Mqtt-opHAB

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.

 

Forecast

 

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.

Pollution map

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

Alarm

 

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)

Car undefined

Car in

Car out

Lights MainLights all
All RoomsGarageLiving room

 

This is all for this post! Hope to implement a couple more of features from the Phase 2 still next week!

 

Nodes´ Dashboard

Nodes Dashboard