Hardware components | ||||||
| × | 1 | ||||
| × | 1 | ||||
| × | 1 | ||||
| × | 1 | ||||
| × | 1 | ||||
| × | 1 | ||||
| × | 1 | ||||
Hand tools and fabrication machines | ||||||
| ||||||
|
This project was made to answer a simple question- what do you do when you go on vacation and you leave your house alone - what do you do about the lights? We decided to make it because when we go on vacation, we like to know our house is safe from criminals. We decided to make a system that turns on and off our lights based on light levels outside and on a random schedule, so that it is not a regular on and off every day at a specific time. The circuits we made (3) are connected to the Raspberry Pi model B using an application that runs over the Windows 10 IoT Core and connects to our local area network (LAN), and are controlled using three ESP8266, which also connect wirelessly to the same network..
The project was more challenging than we first expected, as the ESP8266's and the Raspberry Pi's are both relatively new, so there is very little documentation on how to do any of these things that we had to do for this project. We did originally plan on making these boxes with light sensors built in, but time constraints didn't allow for this. We were, however, able to make different "sequences" in the code, as well as manual override, where you can turn on and off the lights as you please.
As far as the construction of the whole device goes, we etched our own PCBs for the relay circuits and used a breadboard to connect them to ESP8266. The Raspberry Pi's interface is very simple, with the only buttons being selection of choices about the lights to turn on (along with the choice to go automatic). This application runs using the Windows 10 IoT Core in the Pi. The program itself is attached later.
This is our entry for the Hackster/Microsoft IoT contest.
Raspberry Pi code
C#//*********************************************************
//
// Copyright (c) Microsoft. All rights reserved.
// This code is licensed under the MIT License (MIT).
// THIS CODE IS PROVIDED *AS IS* WITHOUT WARRANTY OF
// ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING ANY
// IMPLIED WARRANTIES OF FITNESS FOR A PARTICULAR
// PURPOSE, MERCHANTABILITY, OR NON-INFRINGEMENT.
//
//*********************************************************
using SDKTemplate;
using System;
using System.Collections.Generic;
using Windows.ApplicationModel.Core;
using Windows.Networking;
using Windows.Networking.Connectivity;
using Windows.Networking.Sockets;
using Windows.Storage.Streams;
using Windows.UI.Core;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Navigation;
namespace HomeAutomationProject
{
/// <summary>
/// A page for fifth scenario.
/// </summary>
public sealed partial class Scenario5 : Page
{
// A pointer back to the main page. This is needed if you want to call methods in MainPage such
// as NotifyUser()
private MainPage rootPage = MainPage.Current;
private DatagramSocket listenerSocket = null;
public Scenario5()
{
this.InitializeComponent();
}
private void CloseListenerSocket()
{
if (listenerSocket != null)
{
// DatagramSocket.Close() is exposed through the Dispose() method in C#.
// The call below explicitly closes the socket, freeing the UDP port that it is currently bound to.
listenerSocket.Dispose();
listenerSocket = null;
}
}
/// <summary>
/// Invoked when this page is about to be displayed in a Frame.
/// </summary>
/// <param name="e">Event data that describes how this page was reached. The Parameter
/// property is typically used to configure the page.</param>
protected override void OnNavigatedTo(NavigationEventArgs e)
{
// Only Broadcast Scenario will be used for Vacation Automation.
// I leave the Multicast option in the code for future enhancements.
// MulticastRadioButton.IsChecked = true;
SetupBroadcastScenarioUI();
}
/// <summary>
/// Invoked immediately before the Page is unloaded and is no longer the current source of a parent Frame.
/// </summary>
/// <param name="e">Event data representative of the navigation that will unload the current Page.</param>
protected override void OnNavigatingFrom(NavigatingCancelEventArgs e)
{
CloseListenerSocket();
}
// Sets up the UI to display the multicast scenario options.
private void SetupMulticastScenarioUI()
{
RemoteAddressLabel.Text = "Multicast Group:";
StartListenerButton.Content = "Start listener and join multicast group";
RemoteAddress.Text = "224.3.0.5";
RemoteAddress.IsEnabled = false;
StartListenerButton.IsEnabled = true;
SendMessageButton.IsEnabled = false;
CloseListenerButton.IsEnabled = false;
SendOutput.Text = "";
}
// Sets up the UI to display the broadcast scenario options.
private void SetupBroadcastScenarioUI()
{
RemoteAddressLabel.Text = "Broadcast Address:";
StartListenerButton.Content = "Start Main Application";
SendMessageButton.Content = "Send Message";
CloseListenerButton.Content = "Close Main Application";
RemoteAddress.Text = "255.255.255.255";
TextToSend.Text = "Please choose sequence...";
ServiceName.IsEnabled = false;
RemoteAddress.IsEnabled = false;
TextToSend.IsEnabled = false;
StartListenerButton.IsEnabled = true;
Sequence1RadioButton.IsEnabled = false;
Sequence2RadioButton.IsEnabled = false;
Sequence3RadioButton.IsEnabled = false;
AllOffRadioButton.IsEnabled = false;
AllOnRadioButton.IsEnabled = false;
FullyAutomaticRadioButton.IsEnabled = false;
SendMessageButton.IsEnabled = false;
CloseListenerButton.IsEnabled = false;
SendOutput.Text = "";
ServiceName.IsReadOnly = true;
RemoteAddress.IsReadOnly = true;
TextToSend.IsReadOnly = true;
}
// private void MulticastRadioButton_Checked(object sender, RoutedEventArgs e)
// {
// CloseListenerSocket();
// SetupMulticastScenarioUI();
// }
// private void MulticastRadioButton_Unchecked(object sender, RoutedEventArgs e)
// {
// CloseListenerSocket();
// SetupBroadcastScenarioUI();
// }
private void Sequence1RadioButton_Checked(object sender, RoutedEventArgs e)
{
TextToSend.Text = "Run Sequence 1";
{
rootPage.NotifyUser(
"Sequence 1 has been selected by the user.",
NotifyType.StatusMessage);
}
}
private void Sequence2RadioButton_Checked(object sender, RoutedEventArgs e)
{
TextToSend.Text = "Run Sequence 2";
{
rootPage.NotifyUser(
"Sequence 2 has been selected by the user.",
NotifyType.StatusMessage);
}
}
private void Sequence3RadioButton_Checked(object sender, RoutedEventArgs e)
{
TextToSend.Text = "Run Sequence 3";
{
rootPage.NotifyUser(
"Sequence 3 has been selected by the user.",
NotifyType.StatusMessage);
}
}
private void FullyAutomaticRadioButton_Checked(object sender, RoutedEventArgs e)
{
TextToSend.Text = "Automatic Mode";
{
rootPage.NotifyUser(
"Fully automatic mode has been selected by the user.",
NotifyType.StatusMessage);
}
}
private void AllOffRadioButton_Checked(object sender, RoutedEventArgs e)
{
TextToSend.Text = "All OFF";
{
rootPage.NotifyUser(
"All automation equipment has been turned off by the user.",
NotifyType.StatusMessage);
}
}
private void AllOnRadioButton_Checked(object sender, RoutedEventArgs e)
{
TextToSend.Text = "All ON";
{
rootPage.NotifyUser(
"All automation equipment has been turned on by the user.",
NotifyType.StatusMessage);
}
}
/// <summary>
/// This is the click handler for the 'StartListener' button.
/// </summary>
/// <param name="sender">Object for which the event was generated.</param>
/// <param name="e">Event's parameters.</param>
private async void StartListener_Click(object sender, RoutedEventArgs e)
{
if (String.IsNullOrEmpty(ServiceName.Text))
{
rootPage.NotifyUser("Please provide a service name.", NotifyType.ErrorMessage);
return;
}
if (listenerSocket != null)
{
rootPage.NotifyUser("A listener socket is already set up.", NotifyType.ErrorMessage);
return;
}
bool isMulticastSocket = false;
listenerSocket = new DatagramSocket();
listenerSocket.MessageReceived += MessageReceived;
if (isMulticastSocket)
{
// DatagramSockets conduct exclusive (SO_EXCLUSIVEADDRUSE) binds by default, effectively blocking
// any other UDP socket on the system from binding to the same local port. This is done to prevent
// other applications from eavesdropping or hijacking a DatagramSocket's unicast traffic.
//
// Setting the MulticastOnly control option to 'true' enables a DatagramSocket instance to share its
// local port with any Win32 sockets that are bound using SO_REUSEADDR/SO_REUSE_MULTICASTPORT and
// with any other DatagramSocket instances that have MulticastOnly set to true. However, note that any
// attempt to use a multicast-only DatagramSocket instance to send or receive unicast data will result
// in an exception being thrown.
//
// This control option is particularly useful when implementing a well-known multicast-based protocol,
// such as mDNS and UPnP, since it enables a DatagramSocket instance to coexist with other applications
// running on the system that also implement that protocol.
listenerSocket.Control.MulticastOnly = true;
}
// Start listen operation.
try
{
await listenerSocket.BindServiceNameAsync(ServiceName.Text);
if (isMulticastSocket)
{
// Join the multicast group to start receiving datagrams being sent to that group.
listenerSocket.JoinMulticastGroup(new HostName(RemoteAddress.Text));
rootPage.NotifyUser(
"Listening on port " + listenerSocket.Information.LocalPort + " and joined to multicast group",
NotifyType.StatusMessage);
}
else
{
rootPage.NotifyUser(
"Main application has started, and it is listening on port " + listenerSocket.Information.LocalPort + ".",
NotifyType.StatusMessage);
}
// Disable StartListenerButton after active listening socket has been created.
StartListenerButton.IsEnabled = false;
// Enable scenario steps that require us to have an active listening socket.
Sequence1RadioButton.IsEnabled = true;
Sequence2RadioButton.IsEnabled = true;
Sequence3RadioButton.IsEnabled = true;
AllOffRadioButton.IsEnabled = true;
AllOnRadioButton.IsEnabled = true;
FullyAutomaticRadioButton.IsEnabled = true;
SendMessageButton.IsEnabled = true;
CloseListenerButton.IsEnabled = true;
}
catch (Exception exception)
{
listenerSocket.Dispose();
listenerSocket = null;
// If this is an unknown status it means that the error is fatal and retry will likely fail.
if (SocketError.GetStatus(exception.HResult) == SocketErrorStatus.Unknown)
{
throw;
}
rootPage.NotifyUser(
"Start listening failed with error: " + exception.Message,
NotifyType.ErrorMessage);
}
}
/// <summary>
/// This is the click handler for the 'SendMessage' button.
/// </summary>
/// <param name="sender">Object for which the event was generated.</param>
/// <param name="e">Event's parameters.</param>
private async void SendMessage_Click(object sender, RoutedEventArgs e)
{
SendOutput.Text = "";
try
{
IOutputStream outputStream;
HostName remoteHostname = new HostName(RemoteAddress.Text);
// GetOutputStreamAsync can be called multiple times on a single DatagramSocket instance to obtain
// IOutputStreams pointing to various different remote endpoints. The remote hostname given to
// GetOutputStreamAsync can be a unicast, multicast or broadcast address.
outputStream = await listenerSocket.GetOutputStreamAsync(remoteHostname, ServiceName.Text);
// Send out some multicast or broadcast data. Datagrams generated by the IOutputStream will use
// <source host, source port> information obtained from the parent socket (i.e., 'listenSocket' in
// this case).
string stringToSend = TextToSend.Text;
DataWriter writer = new DataWriter(outputStream);
writer.WriteString(stringToSend);
await writer.StoreAsync();
SendOutput.Text = "\"" + stringToSend + "\" sent successfully.";
}
catch (Exception exception)
{
// If this is an unknown status it means that the error is fatal and retry will likely fail.
if (SocketError.GetStatus(exception.HResult) == SocketErrorStatus.Unknown)
{
throw;
}
rootPage.NotifyUser("Send failed with error: " + exception.Message, NotifyType.ErrorMessage);
}
}
/// <summary>
/// This is the click handler for the 'CloseListener' button.
/// </summary>
/// <param name="sender">Object for which the event was generated.</param>
/// <param name="e">Event's parameters.</param>
private void CloseListener_Click(object sender, RoutedEventArgs e)
{
CloseListenerSocket();
// Enable StartListenerButton after active listening socket has been closed.
StartListenerButton.IsEnabled = true;
// Disable scenario steps that require us to have an active listening socket.
Sequence1RadioButton.IsEnabled = false;
Sequence2RadioButton.IsEnabled = false;
Sequence3RadioButton.IsEnabled = false;
AllOffRadioButton.IsEnabled = false;
AllOnRadioButton.IsEnabled = false;
FullyAutomaticRadioButton.IsEnabled = false;
SendMessageButton.IsEnabled = false;
CloseListenerButton.IsEnabled = false;
SendOutput.Text = "";
rootPage.NotifyUser("Main Application closed", NotifyType.StatusMessage);
}
/// <summary>
/// Message received handler
/// </summary>
/// <param name="socket">The socket object</param>
/// <param name="eventArguments">The datagram event information</param>
void MessageReceived(DatagramSocket socket, DatagramSocketMessageReceivedEventArgs eventArguments)
{
try
{
// Interpret the incoming datagram's entire contents as a string.
uint stringLength = eventArguments.GetDataReader().UnconsumedBufferLength;
string receivedMessage = eventArguments.GetDataReader().ReadString(stringLength);
NotifyUserFromAsyncThread(
"Received data from remote peer (Remote Address: " +
eventArguments.RemoteAddress.CanonicalName +
", Remote Port: " +
eventArguments.RemotePort + "): \"" +
receivedMessage + "\"",
NotifyType.StatusMessage);
}
catch (Exception exception)
{
SocketErrorStatus socketError = SocketError.GetStatus(exception.HResult);
if (SocketError.GetStatus(exception.HResult) == SocketErrorStatus.Unknown)
{
throw;
}
rootPage.NotifyUser(
"Error happened when receiving a datagram:" + exception.Message,
NotifyType.ErrorMessage);
}
}
/// <summary>
/// Notifies the user from a non-UI thread
/// </summary>
/// <param name="strMessage">The message</param>
/// <param name="type">The type of notification</param>
private void NotifyUserFromAsyncThread(string strMessage, NotifyType type)
{
var ignore = Dispatcher.RunAsync(
CoreDispatcherPriority.Normal, () => rootPage.NotifyUser(strMessage, type));
}
private void TextBlock_SelectionChanged(object sender, RoutedEventArgs e)
{
}
}
}
ESP8266 Code
C/C++/*
WiFi UDP Send and Receive String
This sketch wait an UDP packet on localPort using a WiFi ESP8266 module.
When a packet is received an Acknowledge packet is sent to the client on port remotePort
*/
#include <ESP8266WiFi.h>
#include <ESP8266WebServer.h>
#include <SPI.h>
#include <WiFiUdp.h>
int sensorPin = 5; // select the input pin for the light sensor
int ledPin = 4; // select the pin for the LED
// int sensorValue = 0; // variable to store the value coming from the sensor
float sensorValue;
// It indicates if LED must be on or off.
boolean NewRequestedOutputState = LOW;
String UDPString=""; // String to send via udp
String StringPacketBuffer="";
// We need a character array to compare.
String Sequence01="Run Sequence 1";
String Sequence02="Run Sequence 2";
String Sequence03="Run Sequence 3";
String AllOFF="All OFF";
String AllON="All ON";
String AutomaticMode="Automatic Mode";
char CharSequence01[50];
char CharSequence02[50];
char CharSequence03[50];
// Network keys... Put in your internet name and password here
const char* ssid = "xxxxxxxxxx";
const char* password = "xxxxxxxxxx";
int counter = 0;
char myCharArray[16] = "Acknowledged # ";
int status = WL_IDLE_STATUS;
int keyIndex = 0; // your network key Index number (needed only for WEP)
unsigned int localPort = 11000; // local port to listen on
char packetBuffer[255]; //buffer to hold incoming packet
char ReplyBuffer[16] = "acknowledged"; // a string to send back
char charBuf[50]; // It sends the response to the Raspberry Pi.
WiFiUDP Udp;
// =========================
// Start "setup" section
// =========================
void setup() {
pinMode(ledPin, OUTPUT);
// Initialize serial and wait for port to open:
Serial.begin(115200);
// Connect to WiFi network using static IP set in variable "ip".
WiFi.begin(ssid, password);
Serial.print("\n\r \n\rWorking to connect to Home Network. Please, be patient.");
// attempt to connect to Wifi WPA/WPA2 network:
while ( WiFi.status() != WL_CONNECTED)
{
Serial.print("Attempting to connect to SSID: ");
Serial.println(ssid);
delay(500);
}
Serial.println("Connected to wifi");
printWifiStatus();
Serial.println("\nStarting connection to server...");
// if you get a connection, report back via serial:
Udp.begin(localPort);
Serial.println("Connection to server supposedly achieved");
}
// =======================
// End "setup" section
// =======================
// ========================
// Start "loop" section
// ========================
void loop() {
// If there's data available, read a packet
int packetSize = Udp.parsePacket();
if (packetSize)
{
Serial.print("Received packet of size ");
Serial.println(packetSize);
Serial.print("From ");
IPAddress remoteIp = Udp.remoteIP();
Serial.print(remoteIp);
Serial.print(", port ");
Serial.println(Udp.remotePort());
// read the packet into packetBufffer
int len = Udp.read(packetBuffer, 255);
if (len > 0) packetBuffer[len] = 0;
Serial.println("Contents:");
Serial.println(packetBuffer);
// Concert "array" to "String" for comparison purposes.
StringPacketBuffer = String(packetBuffer);
Serial.println("PacketBuffer en modo string:");
Serial.println(StringPacketBuffer);
Serial.println(StringPacketBuffer.length());
Serial.println("Variable Sequence01:");
Serial.println(Sequence01.length());
// Convert "string" to "array" for comparison purposes.
Sequence01.toCharArray(CharSequence01, 50);
Sequence02.toCharArray(CharSequence02, 50);
Sequence03.toCharArray(CharSequence03, 50);
// For debugging purposes:
//Serial.println(CharSequence01);
//Serial.println(CharSequence02);
//Serial.println(CharSequence03);
// Axel. Check the value received from the Raspberry Pi to see what sequence needs to be run.
if (StringPacketBuffer == Sequence03 || StringPacketBuffer == AllON)
{
NewRequestedOutputState = HIGH;
Serial.println(NewRequestedOutputState);
SetDigitalOutputState(ledPin, NewRequestedOutputState);
// Get the light value
getlight(); // read sensor
UDPString= "Relay 3 is ON..."+String((int)sensorValue);
UDPString.toCharArray(charBuf, 50);
// send a reply, to the IP address and port that sent us the packet we received
// ReplyBuffer = myCharArray;
Serial.println(UDPString);
Serial.println(ReplyBuffer);
Serial.println(charBuf);
Udp.beginPacket(Udp.remoteIP(), Udp.remotePort());
// Udp.write(ReplyBuffer);
Udp.write(charBuf);
Udp.endPacket();
}
else if (StringPacketBuffer == Sequence01)
{
NewRequestedOutputState = LOW;
Serial.print(NewRequestedOutputState);
SetDigitalOutputState(ledPin, NewRequestedOutputState);
}
else if (StringPacketBuffer == Sequence02)
{
NewRequestedOutputState = LOW;
Serial.print(NewRequestedOutputState);
SetDigitalOutputState(ledPin, NewRequestedOutputState);
}
else if (StringPacketBuffer == AllOFF)
{
NewRequestedOutputState = LOW;
Serial.print(NewRequestedOutputState);
SetDigitalOutputState(ledPin, NewRequestedOutputState);
}
}
}
void printWifiStatus() {
// print the SSID of the network you're attached to:
Serial.print("SSID: ");
Serial.println(WiFi.SSID());
// print your WiFi shield's IP address:
IPAddress ip = WiFi.localIP();
Serial.print("IP Address: ");
Serial.println(ip);
// print the received signal strength:
long rssi = WiFi.RSSI();
Serial.print("signal strength (RSSI):");
Serial.print(rssi);
Serial.println(" dBm");
}
void getlight() {
// read the value from the sensor:
sensorValue = analogRead(5);
// turn the ledPin on
// digitalWrite(4, HIGH);
// stop the program for <sensorValue> milliseconds:
delay(400);
// turn the ledPin off:
// digitalWrite(4, LOW);
return;
}
void SetDigitalOutputState(int PinNumber, boolean OutputState)
{
digitalWrite(PinNumber, OutputState);
return;
}
Comments