Smart Building Security and Automation System

Team Members

  • Geoff Bond - EE
  • Brian Cochran - CS
  • Austin Keener - EE

Project Description

This project demonstrates how MBED OS, node-red, and MQTT can be used to facilitate a multi-node wireless communications network for a smart building security and automation system. This demonstration includes several components:

  • Wireless Door Sensor
  • Door Lock
  • Keypad
  • Raspberry Pi Server

Each of these components will be discussed in detail as well as the high level operations of the system.

Keypad

The custom-built keypad interfaces with a Cypress PSoC 6 Wi-Fi BT Prototyping Kit, which provides connection to the automation server with an MQTT client that communicates over a built in Wi-Fi radio. The code running on the PSoc 6 MCU will publish the disarm code entered on the keypad to the automation server. Based on the state of the alarm system, the keypad will display a red or green led, and will emit an audible alarm when the alarm has been triggered.

Wiring

The keypad wiring consists of 12 pushbuttons connected to 12 DIO ports on the Cypress PSoc 6 dev kit.

Code

The code running on the Cypress PSco6 MCU makes use of the MBED OS5 realtime operating system and incorporates the official Eclipse project MQTT library. Modification of the MQTT library was necessary to prevent the MQTT client from blocking the main thread execution and disconnecting from the MQTT broker. An additional MQTT Network wrapper is also needed.

Repository: HelloMQTT

main.cpp

/* mbed Microcontroller Library
 * Copyright (c) 2019 ARM Limited
 * SPDX-License-Identifier: Apache-2.0
 */

#include "mbed.h"
#include "platform/mbed_thread.h"
#include "MQTTClient.h"
#include "MQTTNetwork.h"
#include "MQTTmbed.h"
#include <string>

#include <string>

//Button Definitions
DigitalIn b1(P13_6); 
DigitalIn b2(P12_0);
DigitalIn b3(P13_7);
DigitalIn b4(P13_2);
DigitalIn b5(P0_4);
DigitalIn b6(P13_4);
DigitalIn b7(P13_0);
DigitalIn b8(P13_1);
DigitalIn b9(P13_7);
DigitalIn b10(P12_4);
DigitalIn b11(P13_5);
DigitalIn b12(P12_1);

WiFiInterface *wifi;

//MQTT Parameters
const char* mqttHostname = "<YOUR_MQTT_HOSTNAME>";
int mqttPort = 1883;
char clientId[20] = "client01";
char clientUser[20] = "<YOUR_MQTT_USERNAME>";
char clientPass[20] = "<YOUR_MQTT_PASSWORD>";
char codeTopic[20] = "/alarm/code";
char alarmTopic[20] = "/alarm/state";

string blank = "hello";
string keepalive = "keepalive";
string lockToggle = "toggle";

string alarmCode;
volatile int alarmState;
#define  alarmStateDisarmed 0
#define  alarmStateArming   1
#define  alarmStateArmed    2
#define  alarmStateAlarming 3
string disarmed = "disarmed";
string armed = "armed";
string arming = "arming";
string alarming = "alarming";


uint32_t timeout = 100;

const char *sec2str(nsapi_security_t sec)
{
    switch (sec) {
        case NSAPI_SECURITY_NONE:
            return "None";
        case NSAPI_SECURITY_WEP:
            return "WEP";
        case NSAPI_SECURITY_WPA:
            return "WPA";
        case NSAPI_SECURITY_WPA2:
            return "WPA2";
        case NSAPI_SECURITY_WPA_WPA2:
            return "WPA/WPA2";
        case NSAPI_SECURITY_UNKNOWN:
        default:
            return "Unknown";
    }
}

bool b1Block = false;
bool b2Block = false;
bool b3Block = false;
bool b4Block = false;
bool b5Block = false;
bool b6Block = false;
bool b7Block = false;
bool b8Block = false;
bool b9Block = false;
bool b10Block = false;
bool b11Block = false;
bool b12Block = false;

time_t now;
time_t last;

// MQTT Callback Function
void alarmStateCb(MQTT::MessageData& md){
    MQTT::Message &message = md.message;
    printf("Message arrived: qos %d, retained %d, dup %d, packetid %d\r\n", message.qos, message.retained, message.dup, message.id);
    printf("Payload %.*s\r\n", message.payloadlen, (char*)message.payload);
    
    if (!strncmp((char *)message.payload, disarmed.c_str(), disarmed.length())){
        alarmState = alarmStateDisarmed;
    } else if (!strncmp((char *)message.payload, arming.c_str(), arming.length())){
        alarmState = alarmStateArming;
    }  else if (!strncmp((char *)message.payload, armed.c_str(), armed.length())){
        alarmState = alarmStateArmed;
    } else if (!strncmp((char *)message.payload, alarming.c_str(), alarming.length())){
        alarmState = alarmStateAlarming;
    }
}

int main(){

    //Initialize wifi interface
    wifi = WiFiInterface::get_default_instance();
    if (!wifi) {
        return -1;
    }
    
    int ret = wifi->connect(MBED_CONF_APP_WIFI_SSID, MBED_CONF_APP_WIFI_PASSWORD, NSAPI_SECURITY_WPA_WPA2);
    if (ret != 0) {
        printf("\nConnection error: %d\n", ret);
        return -1;
    }
    printf("Wifi Connection Success\n\n");
    printf("MAC: %s\n", wifi->get_mac_address());
    printf("IP: %s\n", wifi->get_ip_address());
    printf("Netmask: %s\n", wifi->get_netmask());
    printf("Gateway: %s\n", wifi->get_gateway());
    printf("RSSI: %d\n\n", wifi->get_rssi());

    // Initialize MQTT Connection
    MQTTNetwork mqttNetwork(wifi);
    MQTT::Client<MQTTNetwork, Countdown> client(mqttNetwork);
    MQTT::Message message;
    printf("Connecting to %s:%d\r\n", mqttHostname, mqttPort);
    int rc = mqttNetwork.connect(mqttHostname, mqttPort);
    printf("rc from TCP connect is %d\r\n", rc);
    MQTTPacket_connectData data = MQTTPacket_connectData_initializer;
    data.MQTTVersion = 3;
    data.clientID.cstring = clientId;
    data.username.cstring =  clientUser;
    data.password.cstring = clientPass;
    if((rc = client.connect(data)) == 0){
        printf("Connected to MQTT Broker\n");
    }
    else{
        printf("Failed to connect to MQTT Broker\n");
    }
    ThisThread::sleep_for(5);

    // Publish Connected Message
    char hmiTopic[20] = "/hmi/state";
    char buf[100];
    sprintf(buf, "HMI initialized\r\n");
    message.qos = MQTT::QOS0;
    message.retained = false;
    message.dup = false;
    message.payload = (void*)buf;
    message.payloadlen = strlen(buf)+1;
    rc = client.publish(hmiTopic,message);
    last = time(NULL);
    printf("rc from MQTT Publish: %d \n", rc);
    string cmd;

    // Subscribe to Alarm State Topic
    //rc = client.subscribe("/alarm/state", MQTT::QOS0, alarmStateCb);
    ThisThread::sleep_for(3);
    printf("rc from MQTT Subscribe: %d \n", rc);

    // Configure pushbuttons
    b1.mode(PullUp);
    b2.mode(PullUp);
    b3.mode(PullUp);
    b4.mode(PullUp);
    b5.mode(PullUp);
    b6.mode(PullUp);
    b7.mode(PullUp);
    b8.mode(PullUp);
    b9.mode(PullUp);
    b10.mode(PullUp);
    b11.mode(PullUp);
    b12.mode(PullUp);

    time_t nextMqttLoop = time(NULL);

    while(1){

        if (!b1.read() & !b1Block){
            alarmCode.append("1");
            printf("Button 1 Pressed \n");
            b1Block = true;
        }
        else if (b1.read()){
            b1Block = false;
        }
        if (!b2.read() & !b2Block){
            alarmCode.append("2");
            printf("Button 2 Pressed \n");
            b2Block = true;
        }
        else if (b2.read()){
            b2Block = false;
        }
        if (!b3.read() & !b3Block){
            alarmCode.append("3");
            printf("Button 3 Pressed \n");
            b3Block = true;
        }
        else if (b3.read()){
            b3Block = false;
        }
        if (!b4.read() & !b4Block){
            alarmCode.append("4");
            printf("Button 4 Pressed \n");
            b4Block = true;
        }
        else if (b4.read()){
            b4Block = false;
        }
        if (!b5.read() & !b5Block){
            alarmCode.append("5");
            printf("Button 5 Pressed \n");
            b5Block = true;
        }
        else if (b5.read()){
            b5Block = false;
        }
        if (!b6.read() & !b6Block){
            alarmCode.append("6");
            printf("Button 6 Pressed \n");
            b6Block = true;
        }
        else if (b6.read()){
            b6Block = false;
        }
        if (!b7.read() & !b7Block){
            alarmCode.append("7");
            b7Block = true;
            printf("Button 7 Pressed \n");
        }
        else if (b7.read()){
            b7Block = false;
        }
        if (!b8.read() & !b8Block){
            alarmCode.append("8");
            b8Block = true;
            printf("Button 8 Pressed \n");
        }
        else if (b8.read()){
            b8Block = false;
        }
        if (!b9.read() & !b9Block){
            alarmCode.append("9");
            b9Block = true;
            printf("Button 9 Pressed \n");
        }
        else if (b9.read()){
            b9Block = false;
        }
        if (!b10.read() & !b10Block){
            alarmCode.append("0");
            b10Block = true;
            printf("Button 0 Pressed \n");
        }
        else if (b10.read()){
            b10Block = false;
        }

        if (!b11.read() & !b11Block){
            message.payload = (void *)alarmCode.c_str();
            message.payloadlen = strlen(alarmCode.c_str())+1;
            rc = client.publish(codeTopic, message);
            printf("Published alarm code %s. Return code %d\n", alarmCode.c_str(), rc);
            alarmCode.clear();
            b11Block = true;
        }
        else if (b11.read()){
            b11Block = false;
        }
        if (!b12.read() & !b12Block){
            message.payload = (void *)lockToggle.c_str();
            message.payloadlen = strlen(lockToggle.c_str())+1;
            rc = client.publish(codeTopic, message);
            printf("Published alarm code %s. Return code %d\n", lockToggle.c_str(), rc);
            alarmCode.clear();
            b12Block = true;
        }
        else if (b12.read()){
            b12Block = false;
        }
        
        // Alarm State Logic
        if (alarmState == alarmStateDisarmed){
            gLed.write(1);
        }
        else{
            gLed.write(0);
        }
        if(alarmState == alarmStateArming){
            rLed = !rLed;
        }
        else if (alarmState == alarmStateAlarming){
            rLed.write(1);
        }
        else{
            rLed.write(0);
        }

        // reconnect to broker if disconnected
        if (!client.isConnected()){
            rc = client.connect(data);
            printf("rc from reconnect %d", rc);
        }

        /*now = time(NULL);
        if (now - last >  2000){
            last = now;
            message.payload = (void *)blank.c_str();
            message.payloadlen = strlen(blank.c_str())+1;
            client.publish("outTopic", message);
            //client.yield()  - Needed to read incoming packets. Currently undefined behaviour. Blocks main thread operation and causes the client to disconnect from the broker. 
        }*/
        
        led = !led;  //Heartbeat LED*/
        ThisThread::sleep_for(250);
    }
}

Door Node

The door node consists of a Node MCU ESP8266 connected to a buzzer, state lights, the door lock solenoid, and a door sensor. When the ESP8266 receives a message for unlocking the door it instructs the latch to unlock the door. A solenoid from adafruit was used to latch the door. Since the solenoid requires more than 3V for operation a FET was implemented to drive the solenoid. The microcontroller sends a digital high or low to the control pin of the FET printed circuit board. When a high is sent the solenoid energizes and the latch unlocks. A return spring in the solenoid latches the door when a low is sent to the FET circuit and the solenoid is de-energized. The normal condition for the solenoid is latched.

A magnetic reed switch is implemented for the door sensor. We use the PinDedetect library to debounce and read as a digital input from the switch. The switch is normally open and is pulled high by a microcontroller pull-up resistor. When the door is closed the switch is activated and in the closed position. Closing the switch pulls the input low. A logical zero is detected indicating the door is closed. Thus negative logic is implemented for the signal. The MCU then posts to a topic on the MQTT server indicating door open or closed.

MOSFET Wiring

mbedMOSFET PCB5V <200MA* External Device
gndJP2-2 gndgnd external power supply
JP2-1 RAW12V external power supply
P8JP2-3 Control
JP1-1Solenoid gnd
JP1-2Solenoid pos

Code

Below is the Arduino code running on the Node MCU. The ESP8266Wifi and PubSubclient libraries are used to provide a wifi connection and MQTT communications respectively.

Keypad.ino

#include <ESP8266WiFi.h>
#include <ESP8266mDNS.h>
#include <PubSubClient.h>

// Update these with values suitable for your network.

const char* ssid = "SM-G920PFC8";
const char* password = "7705333493";
const char* mqtt_server = "192.168.43.228";
const char* userName  = "akeener";
const char* passWord = "hello";


WiFiClient espClient;
PubSubClient client(espClient);
long lastMsg = 0;
long lastTone = 0;
long lastblink = 0;
long nowTime = 0;
long lastUpdate = 0;
char msg[50];
int value = 0;

#define alarmStateDisarmed 0
#define alarmStateArming 1
#define alarmStateArmed 2
#define alarmStateAlarming 3

#define gLED D0
#define rLED D1
#define door D2
#define doorLock D3
#define buzzer D4

volatile int alarmState = 0;
int prevAlarmState = 0;
volatile bool lock;

void setup_wifi() {

  delay(10);
  // We start by connecting to a WiFi network
  Serial.println();
  Serial.print("Connecting to ");
  Serial.println(ssid);

  WiFi.begin(ssid, password);

  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.print(".");
  }

  randomSeed(micros());

  Serial.println("");
  Serial.println("WiFi connected");
  Serial.println("IP address: ");
  Serial.println(WiFi.localIP());
}

void callback(char* topic, byte* payload, unsigned int length) {
  Serial.print("Message arrived [");
  Serial.print(topic);
  Serial.print("] ");
  for (int i = 0; i < length; i++) {
    Serial.print((char)payload[i]);
  }
  Serial.println();

  if ((char)payload[0] == '1') {
    alarmState = alarmStateArming;
  } else if((char)payload[0] == '0'){
    alarmState = alarmStateDisarmed;
  } else if((char)payload[0] == '2'){
    alarmState = alarmStateArmed;
  } else if((char)payload[0] == '3'){
    alarmState = alarmStateAlarming;
  } else if ((char)payload[0] == '4'){
    lock = false;
  } else if ((char)payload[0] == '5'){
    lock = true;
  }
}

void reconnect() {
  // Loop until we're reconnected
  while (!client.connected()) {
    Serial.print("Attempting MQTT connection...");
    // Create a random client ID
    String clientId = "ESP8266Client-";
    clientId += String(random(0xffff), HEX);
    // Attempt to connect
    if (client.connect(clientId.c_str(), userName, passWord)) {
      Serial.println("connected");
      // Once connected, publish an announcement...
      client.publish("/arduino", "hello world");
      // ... and resubscribe
      client.subscribe("/alarm/state");
    } else {
      Serial.print("failed, rc=");
      Serial.print(client.state());
      Serial.println(" try again in 5 seconds");
      // Wait 5 seconds before retrying
      delay(5000);
    }
  }
}

void setup() {
  pinMode(BUILTIN_LED, OUTPUT);     // Initialize the BUILTIN_LED pin as an output
  pinMode(gLED, OUTPUT);
  pinMode(rLED, OUTPUT);
  pinMode(doorLock, OUTPUT);
  pinMode(buzzer, OUTPUT);
  pinMode(door, INPUT_PULLUP);
  Serial.begin(115200);
  setup_wifi();
  client.setServer(mqtt_server, 1883);
  client.setCallback(callback);
}

void loop() {

  if (!client.connected()) {
    reconnect();
  }

  if (alarmState == alarmStateDisarmed){
    digitalWrite(gLED, HIGH);
    digitalWrite(rLED, LOW);
    tone(buzzer, 0); 
    //printf("Alarm State: disarmed");
  } else if(alarmState == alarmStateArmed){
    digitalWrite(gLED, LOW);
    digitalWrite(rLED, HIGH);
    tone(buzzer, 0); 
    //printf("Alarm State: armed");
  } else if(alarmState == alarmStateArming){
    nowTime = millis();
    tone(buzzer, 0);
    if ((nowTime - lastblink) > 1000){
       digitalWrite(gLED, HIGH);
        if ((nowTime - lastblink) > 2000){
          lastblink = millis();
        }
    }
    else{
      digitalWrite(gLED,LOW);
    }
    digitalWrite(rLED, LOW);
    //printf("Alarm State: arming\n");
    
  } else if(alarmState == alarmStateAlarming){
    digitalWrite(gLED, LOW);
    nowTime = millis();
    if ((nowTime - lastTone) > 500){
      tone(buzzer, 2000); // Send 1KHz sound signal...
      digitalWrite(rLED, HIGH);
        if ((nowTime - lastTone) > 1000){
          lastTone = millis();
        }
    }
    else{
      tone(buzzer, 0); // Send 1KHz sound signal...
      digitalWrite(rLED, LOW);
    }
    //printf("Alarm State: alarming\n");
  }

  if (lock){
    digitalWrite(doorLock, HIGH);
  }
  else{
    digitalWrite(doorLock, LOW);
  }

  nowTime = millis();
  if ((nowTime - lastUpdate) > 100){
    if (!digitalRead(door)){
      client.publish("/door", "open");
    }
    else {
      client.publish("/door", "closed");
    }
    lastUpdate = millis();
  }
 
  
  client.loop();
  
  long now = millis();
  if (now - lastMsg > 2000) {
    lastMsg = now;
    ++value;
    snprintf (msg, 50, "hello world #%ld", value);
    Serial.print("Publish message: ");
    Serial.println(msg);
    client.publish("outTopic", msg);
  }
}

Raspberry Pi Server

The Central Control server runs on any computer that runs Node.js, including the embedded raspberry pi computer. The server is a state machine that reads in MQTT messages from the IOT devices and publishes door locking commands as well as entry warnings. Setup Instructions

  1. Install node.js.
    • It should install npm as well, just run npm version in the commandline to be sure
  2. Install node red
  3. Install mosquitto MQTT broker
  4. Start Mosquitto (note: run your cmd tool as administrator for this to work)
  5. Start Node Red
    • Cmd line: node-red
    • Go to localhost:1880 in your browser
  6. Load in the ECE server flow program
  7. Make sure the nodes see your MQTT broker (they give a green connected symbol)
    • You can add debug outputs to any of the mqtt inputs or outputs to see results
  8. Deploy button (also press it after any changes you make to the code)

Automation Rules

https://os.mbed.com/media/uploads/akeener3/ece4180_server_statemachine_v1.3_-1-.jpg

Project Demonstration

Problems Faced

A significant amount of time was spent attempting to interface the Nextion HMI with MBED . Nextion is a Human Machine Interface (HMI) solution combining an onboard processor and memory touch display with Nextion Editor software for HMI GUI project development.

Using the Nextion Editor software, you can quickly develop the HMI GUI by drag-and-drop components (graphics, text, button, slider etc.) and ASCII text based instructions for coding how components interact at display side.

Nextion HMI display connects to peripheral MCU via TTL Serial (5V, TX, RX ,GND) to provide event notifications that peripheral MCU can act on, the peripheral MCU can easily update progress and status back to Nextion display utilizing simple ASCII text based instructions.

The video below shows the demonstration application developed for this project. The touch screen buttons are tied back to serial interrupt callbacks on the MCU, which processes the keypresses. Once the check button is pressed on the keypad, the MCU sends the alarm code over Wifi to the automation server using the MQTT protocol. If the server accepts the alarm code, it will disarm the alarm system, and send a state update back to the HMI. The MCU will then change the active screen to the security page, which displays the active state of the system and all of the sensor states. From this screen, the user can then arm the security system and lock/unlock the door lock.

Unfortunately there was not any library support for the Nextion display for MBED, and after spending much time trying to develop a library that would work with OS5, this element of the project was abandoned.

Other issues face included getting the MQTT subscribe functionality to work. The MQTT library for OS5 has an issue where the socket read function will block the main thread execution indefinitely. This is an open issue on the Mbed forums. Adding a timeout to the function resolved this issue, but also caused the MQTT client to completely disconnect from the broker. Ultimately, a NodeMCU running Arduino libraries was used to provide MQTT subscribe functionality in order to demo the entirety of the project.

Future Work

Future work on the project would include replacing the keypad with a proper touch screen interface. Additionally, the automation server could be integrated with a platform like Alexa to provide voice control, and a push notification service like Pushbullet could be added to provide instant notifications to the user's phone upon certain events like the alarm being triggered. The architecture of the networking system is flexible and easily expandable, so many additional wireless nodes could be added to provide a true smart home solution. This could include lighting integration, motion sensors, fire alarm integration, water shutoff valves, garage door openers, blind controls, home theatre integration, and much more!


Please log in to post comments.