Sunday, May 13, 2012

Home Alarm System using Netduino Plus (HomeAlarmPlus project)

Latest update: Sunday, April 1, 2013

This is a simple alarm monitoring system using Netduino Plus and a typical alarm panel. This implementation could be used in conjunction with the PC5010 Digital Security Controls (DSC) PowerSeries Security System control panel and sensors.  Tested with .NET Micro Framework 4.2 (QFE1 and QFE2).

Objective
Use full capabilities of Netduino Plus to monitor home alarm system and report any sensor/motion detector activities via email (using Simple Mail Transfer Protocol (SMTP)), local web server and Pachube (now Cosm).

Programming Languages and Web Development

  • C# for Netduino Plus
  • HTML for Web Server with Cascading Style Sheets (CSS)
  • JavaScript for Web Server


  • Warning
    This code contains information related to a typical home alarm systems.  Please, be aware that this procedure may void any warranty.  Any alarm system of any type may be compromised deliberately or may fail to operate as expected for a variety of reasons.

    The author, Gilberto García, is not liable for any System Failures such as: inadequate installation,  criminal knowledge, access by intruders, power failure, failure of replaceable batteries, compromise of Radio Frequency (Wireless) devices, system users, smoke detectors, motion detectors, warning devices (sirens, bells, horns), telephone lines, insufficient time, component failure, inadequate testing, security and insurance (property or life insurance).

    One last thing:
    DISCONNECT AC POWER AND TELEPHONE LINES PRIOR TO DOING ANYTHING.


    Required Hardware

    • Micro SD Card 
    • SD Card Adapter
    • 3mm green Light Emitting Diode (LED) per alarm zone and motion detector. 
    • 330 ohm for each LED.
    • 5600 ohm resistor per alarm zone and motion detector.
    • Schottky diode per alarm zone.  Schottky diode should have low forward voltage drop like the SBR10U40CT.
    • Sparkfun ProtoScrewShield (sku: DEV-09729).  Other shields that will work are: Proto-Screwshield (Wingshield) kit from Adafruit or WingShield Industries.


    Optional Hardware

    • USB Ruggedized / Waterproof Panel Connector (RR-211300-30)

    Circuitry
    The following Fritzing diagram shows how the Netduino plus, LEDs and the alarm zones (or motion detector) are wired.  LCD circuitry reference from Szymon Kobalczy.

    HomeAlarmPlus connection diagram (basic circuit) Rev-


    HomeAlarmPlus connection diagram (basic circuit) RevA


    HomeAlarmPlus connection diagram (enhanced circuit) RevG

    Zone/Sensor connection

    Keep in mind that one or more zone consist in the following:
    a) 1 Normally Open contact and 1 Normally closed contact with End Of Line (EOL) resistor.
    b) Double EOL circuit, 1 Normally closed contact with 5.6kohm EOL resistor and Schottky diode.  This will bring the protection needed for the Netduino or Arduino.
    c) Each ground zone should be connected to the  ProtoScrewShield GND.


    Netduino/ProtoScrewShield Pin
    Description
    A0 Zone #1
    A1 Zone #2
    A2 Zone #3
    A3 Zone #4
    A4 Sensor #1 [Motion Detector]
    D2 LED Zone #1
    D3 LED Zone #2
    D4 LED Zone #3
    D5 LED Zone #4
    D6 or D7 LED Sensor #1 [Motion Detector]


    HomeAlarmPlus schematic (basic circuit)

    References
    • Simple Mail Transfer Protocol (SMTP) based on BanskySPOTMail by: Pavel Bánský.

    • Web Server based on MFToolkit library by: Michael Schwarz.

    • Pachube Embeddable Graph Generator (Beta) by: Pachube, adapted by Gilberto García.

    • NTP Server and Extensions class based on a post/implementation by: Valkyrie-MT.

    • StopWatch class based on a post/implementation by: Chris Walker.

    • LCD display using uLiquidCrystal library.

    Web server options


    Web Server snapshot


    Options
    Description
    /Root page
    /openOpen last file on SD card.
    /sdcardList files on SD card.
    /suSuper user. Shows additional options.
    /pachubeShows Pachube activity per zone/Datastream.
    /aboutApplication credits and version.
    /delete-confirmDelete last file on SD card [confirm window].
    /delete-lastDelete last file on SD card [no confirm window].
    /diagnosticsFor now, displays available memory on Netduino and forces to clear the garbage collector.


    Code
    Config.ini file should be copied to the root directory on the SD Card. The purpose of this file is to customize parameters on SD Card and avoid minimal source code modification.  CSS files (header_style.css and table_style.css) should be located in WebResources folder.

    microSD directory structure


    Config.ini content:
     ;AlarmByZones - Programmed for Netduino by Gilberto García - 5/9/2012  
     ;Feel free to customize this file with your own definitions.  
     ;[NETDUINO_PLUS_HTTP_PORT] HTTP server port.  
     ;[ALARM_ZONES] should match with ACTIVE_ZONES constant defined in AlarmByZones.cs and Definitions.cs .  
     ;[SENSORS] should match with MOTION_SENSORS constant defined in AlarmByZones.cs and Definitions.cs .  
     ;[USE_EMAIL] Activate/Deactivate email option. Y or N arguments expected.  
     ;[EMAIL_FREQ] Email frequency in minutes when the alarm/sensor is active.   
     ;       This will define EMAIL_FREQUENCY in ConfigDefault.cs .  
     ;[STORE_LOG] Saves alarm/sensor log. Y or N arguments expected.  
     ;[STORE_EXCEPTION] Saves Netduino exceptions. Y or N arguments expected.  
     ;[USE_PACHUBE] Activate/Deactivate Pachube option. Y or N arguments expected.  
     ;  
     ;  
     ;  
     [NETDUINO_PLUS_HTTP_PORT]  
     8080  
     [ALARM_ZONES]  
     Zone1=FIRST FLOOR - Living room windows, Dining room windows, Porch (sliding doors), Garage door access.  
     Zone2=SECOND FLOOR - Master Bedroom and Bathroom Windows.  
     Zone3=FIRST FLOOR - Master Bedroom windows.  
     Zone4=SECOND FLOOR - Bedroom 2 and bathroom windows.  
     [SENSORS]  
     Sensor1=Main door access  
     [USE_EMAIL]  
     Y  
     [EMAIL_FREQ]  
     10  
     [STORE_LOG]  
     Y  
     [STORE_EXCEPTION]  
     N  
     [USE_PACHUBE]  
     Y  
    

    Architecture of AlarmByZones (main)


    Declaration
        /// <summary>  
         /// Alarm zones (Analog Input)  
         /// </summary>  
         static SecretLabs.NETMF.Hardware.AnalogInput[] Zones = new SecretLabs.NETMF.Hardware.AnalogInput[Alarm.User_Definitions.Constants.ACTIVE_ZONES];  
         /// <summary>  
         /// Alarm zones LEDs (Digital Output)  
         /// </summary>  
         static Microsoft.SPOT.Hardware.OutputPort[] AlarmLeds = new Microsoft.SPOT.Hardware.OutputPort[Alarm.User_Definitions.Constants.ACTIVE_ZONES];  
         /// <summary>  
         /// Motion detector sensors (Analog Input)  
         /// </summary>  
         static SecretLabs.NETMF.Hardware.AnalogInput[] Sensors = new SecretLabs.NETMF.Hardware.AnalogInput[Alarm.User_Definitions.Constants.MOTION_SENSORS];  
         /// <summary>  
         /// Motion detector LEDs (Digital Output)  
         /// </summary>  
         static Microsoft.SPOT.Hardware.OutputPort[] MotionLeds = new Microsoft.SPOT.Hardware.OutputPort[Alarm.User_Definitions.Constants.MOTION_SENSORS];  
         /// <summary>  
         /// Gets the total elapsed time measured by the current instance of each alarm zone.  
         /// </summary>  
         static System.Diagnostics.Stopwatch[] stopwatchZones = new Stopwatch[Alarm.User_Definitions.Constants.ACTIVE_ZONES];  
         /// <summary>  
         /// Gets the total elapsed time measured by the current instance of each motion detector sensor.  
         /// </summary>  
         static System.Diagnostics.Stopwatch[] stopwatchSensors = new Stopwatch[Alarm.User_Definitions.Constants.MOTION_SENSORS];  
         /// <summary>  
         /// Flag for detected zones when trigger.  
         /// </summary>  
         static bool[] detectedZones = new bool[Alarm.User_Definitions.Constants.ACTIVE_ZONES];  
         /// <summary>  
         /// Flag for detected sensors when is trigger.  
         /// </summary>  
         static bool[] detectedSensors = new bool[Alarm.User_Definitions.Constants.MOTION_SENSORS];  
         /// <summary>  
         /// Email  
         /// </summary>     
         /// <example> SMTPClient.Email("mail.gmx.com", 587, "user@gmx.com", "destination@email.com", "user password");   
         /// </example>  
         static SMTPClient.Email email = new SMTPClient.Email(Alarm.UserData.Email.host, Alarm.UserData.Email.port,  
           Alarm.UserData.Email.From, Alarm.UserData.Email.To, Alarm.UserData.Email.smtpPassword);  
         /// <summary>  
         /// SD Card Event Logger  
         /// </summary>  
         public static EventLogger SdCardEventLogger = new EventLogger();  
    
    
    
    Delegates
         /// <summary>  
         /// Monitor zones delegate  
         /// </summary>  
         public delegate void MonitorZonesDelegate();  
         /// <summary>  
         /// Monitor motion sensor delegate  
         /// </summary>  
         public delegate void MonitorMotionSensorDelegate();  
    

    Main
          public static void Main()  
         {  
           Zones[0] = new SecretLabs.NETMF.Hardware.AnalogInput(SecretLabs.NETMF.Hardware.NetduinoPlus.Pins.GPIO_PIN_A0);  
           Zones[1] = new SecretLabs.NETMF.Hardware.AnalogInput(SecretLabs.NETMF.Hardware.NetduinoPlus.Pins.GPIO_PIN_A1);  
           Zones[2] = new SecretLabs.NETMF.Hardware.AnalogInput(SecretLabs.NETMF.Hardware.NetduinoPlus.Pins.GPIO_PIN_A2);  
           Zones[3] = new SecretLabs.NETMF.Hardware.AnalogInput(SecretLabs.NETMF.Hardware.NetduinoPlus.Pins.GPIO_PIN_A3);  
           AlarmLeds[0] = new Microsoft.SPOT.Hardware.OutputPort(Pins.GPIO_PIN_D2, false);        
           AlarmLeds[1] = new Microsoft.SPOT.Hardware.OutputPort(Pins.GPIO_PIN_D3, false);  
           AlarmLeds[2] = new Microsoft.SPOT.Hardware.OutputPort(Pins.GPIO_PIN_D4, false);  
           AlarmLeds[3] = new Microsoft.SPOT.Hardware.OutputPort(Pins.GPIO_PIN_D5, false);  
           Sensors[0] = new SecretLabs.NETMF.Hardware.AnalogInput(SecretLabs.NETMF.Hardware.NetduinoPlus.Pins.GPIO_PIN_A4);  
           MotionLeds[0] = new Microsoft.SPOT.Hardware.OutputPort(Pins.GPIO_PIN_D7, false);  
           MonitorZonesDelegate monitorZones = new MonitorZonesDelegate(MonitorZones);  
           MonitorMotionSensorDelegate monitorMotion = new MonitorMotionSensorDelegate(MonitorSensors);  
           //based on a post by Valkyrie-MT  
           //http://forums.netduino.com/index.php?/topic/475-still-learning-internet-way-to-grab-date-and-time-on-startup/  
           Console.DEBUG_ACTIVITY("Setting NTP-time");  
           SetTime();  
           SdCardEventLogger.parseConfigFileContents(Alarm.User_Definitions.Constants.ALARM_CONFIG_FILE_PATH);  
           InitArrays();  
           Console.DEBUG_ACTIVITY(Microsoft.SPOT.Net.NetworkInformation.NetworkInterface.GetAllNetworkInterfaces()[0].IPAddress);  
           SdCardEventLogger.SDCardAccess();  
           new Thread(Alarm.ProtoScrewShield_LED.Blink).Start();        
           new Thread(Alarm.WebServer.startHttp).Start();  
           if (Alarm.ConfigDefault.Data.USE_PACHUBE)  
           {  
             Pachube.PachubeLibrary.InitPachubleLibrary(SdCardEventLogger.IsSDCardAvailable(), Alarm.ConfigDefault.Data.STORE_EXCEPTION);  
             new Thread(Pachube.PachubeLibrary.PachubeConnect).Start();  
           }  
           while (true)  
           {  
             Debug.Print("Memory available: " + Debug.GC(true));  
             monitorZones();  
             monitorMotion();  
             Thread.Sleep(Alarm.Common.Alarm_Constants.ALARM_DELAY_TIME);  
           }  
         }  
    

    Main Methods
          /// <summary>  
         /// Loops thru each alarm zone  
         /// </summary>  
         static void MonitorZones()  
         {  
           for (int i = 0; i < Zones.Length; i++)  
           {  
             int vInput = Zones[i].Read();  
             float volts = ((float)vInput / 1024.0f) * 3.3f;  
             string strZoneDescription = "N/A"; //If zone description is not found on SD Card N/A is default description.  
             Console.DEBUG_ACTIVITY("Zone " + (i + 1).ToString() + ": Volts: " + volts);  
             //format:                //Zone number, voltage  
             Pachube.PachubeLibrary.statusToPachube = i == 0 ? (i + 1).ToString() + "," + volts : (i + 1).ToString() + "," + volts + "\r\n" + Pachube.PachubeLibrary.statusToPachube;  
             AlarmLeds[i].Write(volts >= 3);  
             //elapsed seconds  
             double eSeconds = stopwatchZones[i].ElapsedSeconds;  
             //elapsed minutes  
             double eMinutes = stopwatchZones[i].ElapsedMinutes;  
             Console.DEBUG_ACTIVITY("stopwatch[" + i.ToString() + "] = " + eSeconds.ToString() + " seconds");  
             Console.DEBUG_ACTIVITY("stopwatch[" + i.ToString() + "] = " + eMinutes.ToString() + " minutes\n");  
             if (volts >= 3 )  
             {  
               /*  
                Case #1:  
                 !detectedZones[i] = not triggered before. This is the first time in this cycle and first email to send.  
                Case #2:  
                 detectedZones[i] && eMinutes >= EMAIL_FREQUENCY = triggered before and time is up for sending another email.             
                */  
               if (!detectedZones[i] || (detectedZones[i] && eMinutes >= Alarm.ConfigDefault.Data.EMAIL_FREQUENCY))  
               {  
                 if (Alarm.Common.Alarm_Info.zoneDescription.Count>0)  
                 {  
                   if (Alarm.Common.Alarm_Info.zoneDescription.Contains("Zone" + (i + 1).ToString()))  
                   {  
                     strZoneDescription = (string)Alarm.Common.Alarm_Info.zoneDescription["Zone" + (i + 1).ToString()];  
                   }  
                 }  
                 string info = "Zone " + (i + 1).ToString() + " " + strZoneDescription;  
                 stopwatchZones[i] = Stopwatch.StartNew();  
                 detectedZones[i] = true;  
                 email.SendEmail("Alarm Trigger!", info);  
                 Alarm.Common.Alarm_Info.sbActivity.AppendLine("<tr>");
                 Alarm.Common.Alarm_Info.sbActivity.AppendLine("<td><center>" + DateTime.Now.ToString() + "</center></td> ");
                 Alarm.Common.Alarm_Info.sbActivity.AppendLine("<td><center> Zone " + (i + 1).ToString() + "</center></td>");
                 Alarm.Common.Alarm_Info.sbActivity.AppendLine("<td><center>" + strZoneDescription + "</center></td>");
                 Alarm.Common.Alarm_Info.sbActivity.AppendLine("</tr>");             if (Alarm.ConfigDefault.Data.USE_PACHUBE)  
                 {  
                   //supress timer and update Pachube with new status  
                   Pachube.PachubeLibrary.forceUpdate = true;  
                 }  
                 if (SdCardEventLogger.IsSDCardAvailable() && Alarm.ConfigDefault.Data.STORE_LOG)  
                 {  
                   SdCardEventLogger.saveFile(DateTime.Now.ToString("d_MMM_yyyy--HH_mm_ss") + ".log", info, "Log");  
                 }  
               }  
             }  
             else  
             {  
               detectedZones[i] = false;  
             }  
           }  
         }  
         /// <summary>  
         /// Loops thru each sensor  
         /// </summary>  
         static void MonitorSensors()  
         {    
           for (int i = 0; i < Sensors.Length; i++)  
           {  
             int vInput = Sensors[i].Read();  
             float volts = ((float)vInput / 1024.0f) * 3.3f;  
             string strSensorDescription = "N/A"; //If sensor description is not found on SD Card N/A is default description.  
             Console.DEBUG_ACTIVITY("Sensor " + (i + 1).ToString() + ": Volts: " + volts);  
             MotionLeds[i].Write(volts >= 3);  
             double eSeconds = stopwatchSensors[i].ElapsedSeconds;  
             double eMinutes = stopwatchSensors[i].ElapsedMinutes;  
             Console.DEBUG_ACTIVITY("stopwatch[" + i.ToString() + "] = " + eSeconds.ToString() + " seconds");  
             Console.DEBUG_ACTIVITY("stopwatch[" + i.ToString() + "] = " + eMinutes.ToString() + " minutes\n");  
             if (volts >= 3)  
             {  
               /*  
                Case #1:  
                 !detectedZones[i] = not triggered before. This is the first time in this cycle and first email to send.  
                Case #2:  
                 detectedZones[i] && eMinutes >= EMAIL_FREQUENCY = triggered before and time is up for sending another email.             
                */  
               if (!detectedSensors[i] || (detectedSensors[i] && eMinutes >= Alarm.ConfigDefault.Data.EMAIL_FREQUENCY))  
               {  
                 if (Alarm.Common.Alarm_Info.sensorDescription.Count > 0)  
                 {  
                   if (Alarm.Common.Alarm_Info.sensorDescription.Contains("Sensor" + (i + 1).ToString()))  
                   {  
                     strSensorDescription = (string)Alarm.Common.Alarm_Info.sensorDescription["Sensor" + (i + 1).ToString()];  
                   }  
                 }  
                 string info = "Sensor " + (i + 1).ToString() + " " + strSensorDescription;  
                 stopwatchSensors[i] = Stopwatch.StartNew();  
                 detectedSensors[i] = true;  
                 email.SendEmail("Alarm Trigger!", info);  
                 Alarm.Common.Alarm_Info.sbActivity.AppendLine("<tr>");
                 Alarm.Common.Alarm_Info.sbActivity.AppendLine("<td><center>" + DateTime.Now.ToString() + "</center></td> ");
                 Alarm.Common.Alarm_Info.sbActivity.AppendLine("<td><center> Sensor " + (i + 1).ToString() + "</center></td>");
                 Alarm.Common.Alarm_Info.sbActivity.AppendLine("<td><center>" + strSensorDescription + "</center></td>");
                 Alarm.Common.Alarm_Info.sbActivity.AppendLine("</tr>");  
                 if (Alarm.ConfigDefault.Data.USE_PACHUBE)  
                 {  
                   //supress timer and update Pachube with new status  
                   Pachube.PachubeLibrary.forceUpdate = true;  
                 }  
                 if (SdCardEventLogger.IsSDCardAvailable() && Alarm.ConfigDefault.Data.STORE_LOG)  
                 {  
                   SdCardEventLogger.saveFile(DateTime.Now.ToString("d_MMM_yyyy--HH_mm_ss") + ".log", info, "Log");  
                 }  
               }  
             }  
             else  
             {  
               detectedSensors[i] = false;  
             }  
           }  
         }  
         /// <summary>  
         /// Synchronize Netduino time with NTP Server  
         /// </summary>  
         public static void SetTime()  
         {  
           //based on a post from user "Valkyrie-MT" at http://forums.netduino.com/index.php?/topic/475-still-learning-internet-way-to-grab-date-and-time-on-startup/  
           Extension.SetFromNetwork(DateTime.Now, new TimeSpan(-5, 0, 0));  
         }  
         /// <summary>  
         /// Initializes stopwatch and alarms/sensors arrays  
         /// </summary>  
         private static void InitArrays()  
         {  
           for (int i = 0; i < Alarm.User_Definitions.Constants.ACTIVE_ZONES; i++)  
           {  
             stopwatchZones[i] = Stopwatch.StartNew();  
             detectedZones[i] = false;  
           }  
           for (int i = 0; i < Alarm.User_Definitions.Constants.MOTION_SENSORS; i++)  
           {  
             stopwatchSensors[i] = Stopwatch.StartNew();  
             detectedSensors[i] = false;  
           }  
         }  
    

    Cosm, formerly Pachube, Alarm Console

    Final Product
    LCD and WiFi Internet Adapter
    Alarm Panel with Netduino Plus, LCD and WiFi Internet Adapter

    Comments from Netduino Community
    http://forums.netduino.com/index.php?/topic/4202-home-alarm-system-using-netduino-plus-homealarmplus-project/

    Base Code Repository
    https://github.com/ferraripr/HomeAlarmPlus

    Video

    YouTube video