Hello Everyone,
It did not take long to get the High-power Power Relay working. I can now control a light or appliance via the HSS website. I only ran into one problem. Not all of the GPIO pins that I wanted to use were available. I spent a little time trying to figure out how to activate the pins I wanted to use. However, I decided save time and just use the pins I know will work. Section I contains a table of the GPIO pins I will use.
The details for adding the Light Smart Plug to the Home Security System are given in the sections below:
Section I – GPIO Pin Assignments
Section II – Smart Plug Design
Section III – Smart Plug Code
Section IV – Linux Tips
Next, I will start working on implementing the Carbon Monoxide and Natural Gas Sensors.
Talk to you soon.
Quote of the Moment -
The road to success is dotted with many tempting parking spaces.
-- Will Rogers
Previous Bluetooth Challenge Home Security System Posts |
Section I - GPIO Pin Assignments
Here are the current GPIO pin assignments.
GPIO Pin | Assignment |
---|---|
P8_9 | Living Room Light |
P8_10 | TV |
P8_11 | Garage Door |
P8_12 | Gas Range |
P8_14 | Carbon Monoxide Sensor |
P8_15 | Natural Gas Sensor |
P8_16 | Motion Sensor |
Section II - Smart Plug Design
I made a major design change. I found it easier to split the processing for the sensors and smart plugs into their own sub-process (thread). First, I changed the design for the motion sensor to use its own thread as shown in Figure 1. Now, the hss-controller spawns a thread to handle the logic for the PIR Sensor interface (Figure 1). The pirSensorThread will interface with the human-sensor-server and call the processRequest as needed. Everything else stays the same as it was in blog post VII Section II PIR Sensor Design.
Figure 1.
The design for the Smart Plug is similar to the PIR Sensor design. It has three main modules in the PIR Sensor design; the HSS Controller, the HSS Web Server, and the Light1 Server (Figure 2). One of the differences is that the Smart Plug logic interfaces with the High-power Power Relay hardware (Figure 3) instead of the text-to-speech hardware. The HSS Controller spawns the light1Thread.py thread. The light1 thread checks the status of the P8_9 GPIO pin. If the status has changed the light1 thread will send a request to either turn the smart plug on (GPIO pin is high) or turn it off (GPIO pin is low). The Light1 Server (light-server-1.ino) responds to requests to turn the plug on or off. It does not send any responds back to the light1 thread. If the home owner presses the green Living Room Light button, the browser sets the button to red and sends a message to the HSS Web Server to turn the light on. If the home owner presses the red Living Room Light button, the browser sets the button to green and sends a message to the HSS Web Server to turn the light off. The HSS Web Server will set the P8_9 pin high if it is passed a "1" (Living Room Light button set to red) or low if it passed a "0" (Living Room Light button set to green).
Figure 2.
Figure 3.
Section III - Smart Plug Code
I will cover changes made to the HSS system code required to implement the logic for the Smart Plug. See blog post VII Section III PIR Sensor for a more detail discussion on the existing code.
light-server-1.ino
light-server-1.ino is an Arduino sketch (Figure 4). This sketch turns the smart plug on or off depending on the status received by the light1Thread module. A status of "1" will turn the light on. A status of "0" will turn the light off.
It uses the softwareSerial module instead of the hardware serial port for communicating with the Bluetooth module. SoftwareSerial uses pin 3 as the receive port and pin 4 for the transmit port. (Lines 1 & 2).
Lines 3-5 defines the variables used.
Line 8 initializes the Bluetooth serial port.
Line 9 initializes the Arduino's serial port. If necessary it can be used as a debug port.
The Arduino pin that is attached to the High-power Power Relay is set to output, on Line 11.
Line 12 initializes the data buffer.
Line 13 set pin 8 on the Arduino high.
Lines 14-17 waits for a message from the HSS Controller before continuing to the Loop function.
Line 18 read the "Welcome message" sent by the BeagleBone Black.
The sketch waits 10 seconds before continuing to the main loop (Line 20).
All the main loop does is calls the CheckForMessage() function (lines 24-28).
The ClearBuffer function (Lines 30-36) sets the data buffer to nulls.
Lines 38-47 contain the code for the CheckForMessage function. If data is available (Line 40), the function reads the data, calls the ProcessRequest function to process the data, then clears the input buffer (Lines 42-45).
Lines 49-66 contains the code for the ProcessRequest function. It first verifies that the input (Line 54) is from the BeagleBone Black (Source_Adapter_Id = 000) and the input is for this Bluetooth device ( Destination_ Adapter_Id = 002).
If the status is "0" the ProcessRequest function will set the Arduino pin 8 low, which will turn the light off (Lines 56-59). If the status is "1" the ProcessRequest function will set the Arduino pin 8 high, which will turn the light on (Line 62).
Note: The High-power Power Relay contains two types of plugs, normally OFF and normally ON. The logic above is set for a light/appliance plugged into the normally OFF socket. If it is plugged into the normally ON outlet the logic is reversed. Pin 8 is set to high to turn the plug off and pin 8 is set low to turn the plug on.
#include <SoftwareSerial.h> SoftwareSerial BTserial(3, 4); char dataBuffer[81]; String stringBuffer; int pirPin = 8; // choose the input pin (for PIR sensor) void setup(){ BTserial.begin(9600); Serial.begin(9600); pinMode(pirPin, OUTPUT); // declare sensor as input ClearBuffer(); digitalWrite(pirPin,HIGH); while (!BTserial.available()) { // Wait for a connected message from the HHS Controller } // if(!BTserial.available()) BTserial.readBytes(dataBuffer,80); delay(10000); } // End setup() void loop(){ CheckForMessage(); } // End loop() void ClearBuffer() { for (int i = 0; i < 80; i++) { dataBuffer[i] = 0; } // End for (int i = 0; i < 80; i++) } // End ClearBuffer() void CheckForMessage() { if( BTserial.available()) { BTserial.readBytes(dataBuffer,80); stringBuffer = dataBuffer; ProcessRequest(); ClearBuffer(); } // End if( BTserial.available()) } // End CheckForMessage() void ProcessRequest() { // Source_Adapter_Id Destination_ Adapter_Id Max_Transmission_Count Status Data // 000 002 02 0/1 if(stringBuffer.substring(0,3) == "000" && stringBuffer.substring(3,6) == "002") { if(stringBuffer.substring(8,9) == "0") { digitalWrite(pirPin,LOW); } else { digitalWrite(pirPin,HIGH); } // End if(stringBuffer.substring(8,9) == "0") } // End if(stringBuffer.substring(0,3) == "000" && stringBuffer.substring(3,6) == "002") } // End ProcessRequest()
Figure 4.
hss-controller.py
There has been several changes made to the hss-controller (Figure 5).
First, the import section has changed (Lines 1-8). The processRequest has been deleted. Lines 5, 7, and 8 have been added.
The setup statement (formally line 10) is now in the pirSensorThread.py module.
The address of the Bluetooth device used for the Smart Plug was added to the bluetoothAddress array (Lines 13-14).
In Line 18, the port number has been added to the ConnectSocket call.
The while loop contained the logic to process the requests from the PIR Sensor module. That logic was moved to the pirSensorThread.py module. The while loop just loops forever , sleeping for a minute before waking up to loop again (lines 28-29). I left it as a place holder in case I wanted to add logic to the loop later.
Lines 19-20 starts the thread for the PIR Sensor processing. When the thread starts is passes the PIR socket to the thread.
Lines 22-25 initiates the process for the Smart Plug. Lines 22-23 creates the light1Socket socket and connects it to the Smart Plug Bluetooth module. Lines 24-25 starts the thread for the Smart Plug processing. When the thread starts is passes the Smart Plug socket to the thread.
import Adafruit_BBIO.GPIO as GPIO import serial import bluetooth import time from threading import Thread import bluetoothSockets import pirSensorThread import light1Thread pirSensorStatus = "0"; bluetoothAddress = ["00:14:03:06:58:68", # PIR Sensor "00:14:03:06:68:93"] # Light 1 pirSocket = bluetoothSockets.CreateSocket() bluetoothSockets.ConnectSocket(pirSocket, bluetoothAddress[0], 1) t = Thread(target=pirSensorThread.pir, args=(pirSocket,)) t.start() light1Socket = bluetoothSockets.CreateSocket() bluetoothSockets.ConnectSocket(light1Socket, bluetoothAddress[1], 1) t = Thread(target=light1Thread.light1, args=(light1Socket,)) t.start() while (1 == 1): time.sleep(60)
Figure 5.
bluetoothSockets.py
bluetoothSockets.py is a python program that handles the socket connections for communicating with the Bluetooth devices (Figure 6). Only two changes were made to this module.
The global variable sock was deleted.
The ConnectSocket function now accepts the port number from the calling program (Lines 8-9).
import bluetooth import time def CreateSocket(): sock=bluetooth.BluetoothSocket( bluetooth.RFCOMM ) return sock def ConnectSocket(sock, serverAddr, port): sock.connect((serverAddr, port)) sendcount=sock.send("Hello Bluetooth Device!!!") def ReceiveData(socket): receiveBuffer = "" bluetoothData = "" time.sleep(5) while ("$" not in receiveBuffer): receiveBuffer = socket.recv(80) bluetoothData = bluetoothData + receiveBuffer return bluetoothData
Figure 6.
processRequest.py
The processRequest.py is a python program (Figure 7) that performs tasks depending on what status is passed to it. The status is either an one for on or a zero for off.
The GPIO pin was changed from P8_20 to P8_16 in Lines 9 and 12.
import alert import Adafruit_BBIO.GPIO as GPIO def PirSensor(status): if (status == "1"): alert.speak("S Warning! Warning! a intruder has been detected!") alert.speak("S Warning! Warning! a intruder has been detected!") alert.speak("S Warning! Warning! a intruder has been detected!") GPIO.output("P8_16", GPIO.HIGH) else: alert.speak("S The intruder alert has been cleared!") GPIO.output("P8_16", GPIO.LOW)
Figure 7.
pirSensorThread.py
pirSensorThread.py contains the code that previously resided in the hss-controller module (Figure 8). It processes the data sent to/from the human-sensor-server.ino module.
Lines 1-4 contains the required import statements.
Line 7 sets the GPIO pin P8_16 to output. Note that the pin was changed from P8_20 to P8_16.
The processing logic is contained in the while loop which loops until the program is terminated (Lines 8-25).
Line 9 calls the bluetoothSockets program, which checks for data on the Bluetooth socket. The ReceiveData function blocks, therefore the function will not return until it receives data on the Bluetooth socket. The purpose of the heartbeat from the PIR Sensor module is to send data to the ReceiveData function so it can return back to the pirSensorThread.py program.
Lines 10-13 parses the data received from a Bluetooth module.
Lines 24-24 processes the data received from a Bluetooth module. If the status is a "0" or "1" it checks to see if the destination_Adapter_Id is "000" (BeagleBone Black). If the destination_Adapter_Id is not "000" the data is ignored. It then checks to see if the source_Adapter_Id is "001" (PIR Sensor). If it is it calls the PirSensor function in the processRequest module and sets the pirSensorStatus variable to "1". Currently the PIR Sensor will only send a status of "1". Therefore, the PirSensor function will generate an "alert" message. Next, it checks the PIR Sensor pin (P8_16). If the pin is high it does nothing, otherwise it checks if the pirSensorStatus variable is equal to "1". If it is it calls the PirSensor function in the processRequest module and sets the pirSensorStatus variable to "0". Since the PirSensor function is passed a "0" it will generate an "all clear" message.
import Adafruit_BBIO.GPIO as GPIO import alert import bluetoothSockets import processRequest def pir(socket): GPIO.setup("P8_16", GPIO.OUT) while (1 == 1): bluetoothData = bluetoothSockets.ReceiveData(socket) source_Adapter_Id = bluetoothData[0:3] destination_Adapter_Id = bluetoothData[3:6] max_Transmission_Count = bluetoothData[6:8] status = bluetoothData[8:9] if (status != "H"): if (destination_Adapter_Id == "000"): if (source_Adapter_Id == "001"): processRequest.PirSensor(status) pirSensorStatus = "1" if GPIO.input("P8_16"): // print "Do Nothing" else: if (pirSensorStatus == "1"): processRequest.PirSensor("0") pirSensorStatus = "0" sock.close()
Figure 8.
light1Thread.py
light1Thread.py contains the code that turns the living room light on and off (Figure 9).
Lines 1-4 contains the required import statements.
Line 7 sets the GPIO pin P8_9 to input.
The program goes into a never ending loop at Line 8.
The program checks to see if the P8_9 pin is high (Line 9). If so, it sends the follow data to light-server-1.ino (Line 10):
"000002021$"
// Source_Adapter_Id Destination_ Adapter_Id Max_Transmission_Count Status
// 000 002 02 1
Otherwise the pin is low, so it sends the follow data to light-server-1.ino (Line 12):
"000002020$"
// Source_Adapter_Id Destination_ Adapter_Id Max_Transmission_Count Status
// 000 002 02 0
The program will sleep for 5 seconds before continuing the loop (Line 13).
import Adafruit_BBIO.GPIO as GPIO import time import bluetoothSockets import processRequest def light1(socket): GPIO.setup("P8_9", GPIO.IN) while(1==1): if GPIO.input("P8_9"): sendcount=socket.send("000002021$") else: sendcount=socket.send("000002020$") time.sleep(5)
Figure 9.
hss.py
hss.js is a JavaScript program (Figure 10). This is the web server code that communicates with the web browser and the BeagleBone Black.
hss.js did not change.
var app = require('http').createServer(handler); var io = require('/usr/local/lib/node_modules/bonescript/node_modules/socket.io').listen(app); var fs = require('fs'); var bb = require('bonescript'); var htmlPage = '/home/debian/HSS/hss.html'; // use this for Debian var pinStates = {}; var soc; app.listen(9090); function handler (req, res) { fs.readFile(htmlPage, function (err, data) { if (err) { res.writeHead(500); return res.end('Error loading file: ' + htmlPage); } res.writeHead(200); res.end(data); }); } function onConnect(socket) { socket.on('digitalWrite', handleDigitalWrite); socket.on('monitor', handleMonitorRequest); soc = socket; } function handleMonitorRequest(pin) { bb.pinMode(pin, bb.INPUT); pinStates[pin] = 0; } function handleDigitalWrite(message) { var data = JSON.parse(message); bb.pinMode(data.pin, bb.OUTPUT); bb.digitalWrite(data.pin, data.value); } function checkInputs() { for (var pin in pinStates) { var oldValue = pinStates[pin]; var newValue = bb.digitalRead(pin); if (oldValue != newValue) { soc.emit("pinUpdate", '{"pin":"' + pin + '", "value":' + newValue + '}'); pinStates[pin] = newValue; } } } io.sockets.on('connection', onConnect); setInterval(checkInputs, 500);
Figure 10.
hss.html
hss.html contains the HTML and JavaScript code to display and modify the web browser. Only two changes were made to hss.html.
The GPIO pin was changed from P8_20 to P8_16 on Lines 91 and 177.
USR3 was changed to GPIO pin P8_9 on Lines 96 and 100.
<html> <head> <style> /* The switch - the box around the slider */ .switch { position: relative; display: inline-block; width: 60px; height: 34px; } .switch input {display:none;} .slider { position: absolute; cursor: pointer; top: 0; left: 0; right: 0; bottom: 0; background-color: green; -webkit-transition: .4s; transition: .4s; } .slider:before { position: absolute; content: ""; height: 26px; width: 26px; left: 4px; bottom: 4px; background-color: white; -webkit-transition: .4s; transition: .4s; } input:checked + .slider { background-color: red; } input:focus + .slider { box-shadow: 0 0 1px #2196F3; } input:checked + .slider:before { -webkit-transform: translateX(26px); -ms-transform: translateX(26px); transform: translateX(26px); } /* Rounded sliders */ .slider.round { border-radius: 34px; } .slider.round:before { border-radius: 50%; } .grid-container { display: grid; grid-template-columns: auto auto; background-color: #2196F3; padding: 10px; } .grid-item { background-color: rgba(255, 255, 255, 0.8); border: 1px solid rgba(0, 0, 0, 0.8); padding: 20px; font-size: 30px; text-align: left; color: black } .hss-heading-font { font-family: "Arial", cursive, bold, sans-serif; font-size: 48px; color: blue; } </style> <script src = "/socket.io/socket.io.js"> </script> <script type=text/javascript src="http://code.jquery.com/jquery-3.3.1.min.js"></script> <script> var socket = io.connect(); socket.on("pinUpdate", handlePinUpdate); socket.emit('monitor', 'P8_16'); function FunLivingRoomLight(){ var x = document.getElementById("LivingRoomLight").checked; if(x == false) { socket.emit('digitalWrite', '{"pin":"P8_9", "value":0}'); } else { socket.emit('digitalWrite', '{"pin":"P8_9", "value":1}'); } } function FunTV(){ var x = document.getElementById("TV").checked; if(x == false) { socket.emit('digitalWrite', '{"pin":"USR3", "value":0}'); } else else { socket.emit('digitalWrite', '{"pin":"USR3", "value":1}'); } } function FunGarageDoor(){ var x = document.getElementById("GarageDoor").checked; if(x == false) { socket.emit('digitalWrite', '{"pin":"USR3", "value":0}'); } else { socket.emit('digitalWrite', '{"pin":"USR3", "value":1}'); } } function FunGasRange(){ var x = document.getElementById("GasRange").checked; if(x == false) { socket.emit('digitalWrite', '{"pin":"USR3", "value":0}'); } else { socket.emit('digitalWrite', '{"pin":"USR3", "value":1}'); } } function FunCarbonMonoxideSensor(){ var x = document.getElementById("CarbonMonoxideSensor").checked; if(x == false) { socket.emit('digitalWrite', '{"pin":"USR3", "value":0}'); } else { socket.emit('digitalWrite', '{"pin":"USR3", "value":1}'); } } function FunNaturalGasSensor(){ var x = document.getElementById("NaturalGasSensor").checked; if(x == false) { socket.emit('digitalWrite', '{"pin":"USR3", "value":0}'); } else { socket.emit('digitalWrite', '{"pin":"USR3", "value":1}'); } } function FunMotionSensor(){ var x = document.getElementById("MotionSensor").checked; if(x == false) { socket.emit('digitalWrite', '{"pin":"P8_16", "value":0}'); } else { socket.emit('digitalWrite', '{"pin":"P8_16", "value":1}'); } } var pin = 'P8_16'; function handlePinUpdate(message) { var data = JSON.parse(message); if (data.value == 0) { document.getElementById("MotionSensor").checked = false; } else { document.getElementById("MotionSensor").checked = true; } } function FunFrontDoorCamera(){ var x = document.getElementById("FrontDoorCamera").checked; if(x == false) { socket.emit('digitalWrite', '{"pin":"USR3", "value":0}'); } else { socket.emit('digitalWrite', '{"pin":"USR3", "value":1}'); } } function FunBackYardCamera(){ var x = document.getElementById("BackYardCamera").checked; if(x == false) { socket.emit('digitalWrite', '{"pin":"USR3", "value":0}'); } else { socket.emit('digitalWrite', '{"pin":"USR3", "value":1}'); } } </script> </head> <body> <div>Home Security System</div> <br><br> <div> <div>Living Room Light</div> <div> <label> <input id="LivingRoomLight" type="checkbox" onClick="FunLivingRoomLight();"> <span "grid-item"></span> </label> </div> <div>TV</div> <div> <label> <input id="TV" type="checkbox" onClick="FunTV();"> <span "grid-item"></span> </label> </div> <div>Garage Door</div> <div> <label> <input id="GarageDoor" type="checkbox" onClick="FunGarageDoor();"> <span "grid-item"></span> </label> </div> <div>Gas Range</div> <div> <label> <input id="GasRange" type="checkbox" onClick="FunGasRange();"> <span "grid-item"></span> </label> </div> <div>Carbon Monoxide Sensor</div> <div> <label> <input id="CarbonMonoxideSensor" type="checkbox" onClick="FunCarbonMonoxideSensor();"> <span "grid-item"></span> </label> </div> <div>Natural Gas Sensor</div> <div> <label> <input id="NaturalGasSensor" type="checkbox" onClick="FunNaturalGasSensor();"> <span "grid-item"></span> </label> </div> <div>Motion Sensor</div> <div> <label> <input id="MotionSensor" type="checkbox" onClick="FunMotionSensor();"> <span "grid-item"></span> </label> </div> <div>Front Door Camera</div> <div> <label> <input id="FrontDoorCamera" type="checkbox" onClick="FunFrontDoorCamera();"> <span "grid-item"></span> </label> </div> <div>Back Yard Camera</div> <div> <label> <input id="BackYardCamera" type="checkbox" onClick="FunBackYardCamera();"> <span "grid-item"></span> </label> </div>
Section IV -Linux Tips
I thought I would add an couple of tips the will help your development go a little faster.
Aliases
You can create aliases to reduce the amount of typing you do when entering commands. To create an alias: at the command prompt enter the word alias followed by the name of the alias, followed by an equal sign, followed by the command the alias will execute, in single quotes. To create alias for a long list option of the ls command, enter at the command prompt (figure 11):
alias ll='ls -l'
Figure 11.
Execute commands at Logon
If you enter ls -la in your home directory you will see a bunch of files that start with a period (Figure 12). Any file that starts with a period will not display with the ls command unless you use the "a" option.
Figure 12.
There is a file called .profile (Figure 12). This file is executed whenever you logon. The .profile file contains logic to see if the .bashrc exists. If so, the .profile file will execute it. The .bashrc file is for non-login accounts, so we will not cover the .bashrc file here. The lines we are interested in are the two lines I added at the bottom (Figure 13).
The first line modifies the path variable. The path variable defines where to look for executable files. It searches the paths in the order that they are listed. At logon the path variable looks like Figure 14. What I did was change the path to include the home directory and the bin directory in the home directory. If you want to include more directories just append a colon and the path of the directory you want to include. Now if I echo the path variable it looks like figure 15. Since the .profile is executed at logon, the next time I logon I will not need to execute the .profile to expand the path.
Figure 13.
Figure 14.
Figure 15.
I decided to put my aliases in a different file, which I called .myprofile. What the last line does (Figure 13) is execute the .myprofile file. Note the period at the beginning of the command. It is necessary to tell the operating system that you want to execute a file in the current directory. This was added as a security feature. The original Unix systems did not have this feature. So hackers would rewrite a system command, such as the ls command, to perform a malicious task before executing the system command. The hacker would put the modified command in a directory. Since the operating system searches the current directory first it would execute the malicious command instead of the system command.
The contents of .myprofile is given in Figure 16. I added aliases for the commands I use the most:
- The ls commands
- cd to my working directory
- Executing the vi editor with the C option
- Execute HSS application programs
The contents of .myprofile is not restricted to aliases. You can add any commands you want to the file.
Figure 16.
Top Comments