From 039f7d023c67adb9e3fe2d78912e1dd7208b73bd Mon Sep 17 00:00:00 2001 From: Ryan Wagoner Date: Tue, 29 Nov 2016 22:58:51 -0500 Subject: [PATCH] 1.0.8 - Fixed web service threading when multiple subscriptions exist - Added additional zone types to contact and motion web service API - Split command line options for config and log files --- HAILogger/CoreServer.cs | 12 ++----- HAILogger/Event.cs | 2 +- HAILogger/Global.cs | 8 ++--- HAILogger/HAIService.cs | 10 ++++-- HAILogger/Program.cs | 34 +++++++++++++++--- HAILogger/Properties/AssemblyInfo.cs | 4 +-- HAILogger/Settings.cs | 52 ++++++++++------------------ HAILogger/WebNotification.cs | 30 ++++++++++------ README.md | 34 ++++++++++++++---- 9 files changed, 113 insertions(+), 73 deletions(-) diff --git a/HAILogger/CoreServer.cs b/HAILogger/CoreServer.cs index 2fdaf1a..ab7ff38 100644 --- a/HAILogger/CoreServer.cs +++ b/HAILogger/CoreServer.cs @@ -3,7 +3,6 @@ using System; using System.Collections.Generic; using System.Data; using System.Data.Odbc; -using System.IO; using System.Reflection; using System.Text; using System.Threading; @@ -44,14 +43,6 @@ namespace HAILogger private void Server() { - Global.event_log = "EventLog.txt"; - Global.event_source = "HAI Logger"; - - if (string.IsNullOrEmpty(Global.dir_config)) - Global.dir_config = Path.GetDirectoryName(Assembly.GetEntryAssembly().Location); - - Settings.LoadSettings(); - Event.WriteInfo("CoreServer", "Starting up server " + Assembly.GetExecutingAssembly().GetName().Version.ToString()); @@ -784,10 +775,13 @@ namespace HAILogger case enuZoneType.X2EntryDelay: case enuZoneType.X4EntryDelay: case enuZoneType.Perimeter: + case enuZoneType.Tamper: + case enuZoneType.Auxiliary: WebNotification.Send("contact", Helper.Serialize(Helper.ConvertZone( MSG.ObjectNumber(i), HAC.Zones[MSG.ObjectNumber(i)]))); break; case enuZoneType.AwayInt: + case enuZoneType.NightInt: WebNotification.Send("motion", Helper.Serialize(Helper.ConvertZone( MSG.ObjectNumber(i), HAC.Zones[MSG.ObjectNumber(i)]))); break; diff --git a/HAILogger/Event.cs b/HAILogger/Event.cs index ce09c39..2c4bb4a 100644 --- a/HAILogger/Event.cs +++ b/HAILogger/Event.cs @@ -148,7 +148,7 @@ namespace HAILogger try { - FileStream fs = new FileStream(Global.dir_config + "\\" + Global.event_log, FileMode.Append, FileAccess.Write); + FileStream fs = new FileStream(Global.log_file, FileMode.Append, FileAccess.Write); StreamWriter sw = new StreamWriter(fs); sw.WriteLine(DateTime.Now.ToString("MM/dd/yyyy HH:mm:ss ") + source + ": " + value); diff --git a/HAILogger/Global.cs b/HAILogger/Global.cs index 266d693..eb46923 100644 --- a/HAILogger/Global.cs +++ b/HAILogger/Global.cs @@ -4,12 +4,12 @@ namespace HAILogger { public abstract class Global { - // Events Preset - public static string event_log; + // Events public static string event_source; - // Directories - public static string dir_config; + // Files + public static string config_file; + public static string log_file; // HAI Controller public static string hai_address; diff --git a/HAILogger/HAIService.cs b/HAILogger/HAIService.cs index 6c7a7ec..2f8bce9 100644 --- a/HAILogger/HAIService.cs +++ b/HAILogger/HAIService.cs @@ -53,7 +53,9 @@ namespace HAILogger if ((zone.ZoneType == enuZoneType.EntryExit || zone.ZoneType == enuZoneType.X2EntryDelay || zone.ZoneType == enuZoneType.X4EntryDelay || - zone.ZoneType == enuZoneType.Perimeter) && zone.DefaultProperties == false) + zone.ZoneType == enuZoneType.Perimeter || + zone.ZoneType == enuZoneType.Tamper || + zone.ZoneType == enuZoneType.Auxiliary) && zone.DefaultProperties == false) names.Add(new NameContract() { id = i, name = zone.Name }); } return names; @@ -68,7 +70,8 @@ namespace HAILogger { clsZone zone = WebService.HAC.Zones[i]; - if (zone.ZoneType == enuZoneType.AwayInt && zone.DefaultProperties == false) + if ((zone.ZoneType == enuZoneType.AwayInt || + zone.ZoneType == enuZoneType.NightInt) && zone.DefaultProperties == false) names.Add(new NameContract() { id = i, name = zone.Name }); } return names; @@ -152,9 +155,12 @@ namespace HAILogger case enuZoneType.X2EntryDelay: case enuZoneType.X4EntryDelay: case enuZoneType.Perimeter: + case enuZoneType.Tamper: + case enuZoneType.Auxiliary: ctx.OutgoingResponse.Headers.Add("type", "contact"); break; case enuZoneType.AwayInt: + case enuZoneType.NightInt: ctx.OutgoingResponse.Headers.Add("type", "motion"); break; case enuZoneType.Water: diff --git a/HAILogger/Program.cs b/HAILogger/Program.cs index 08f1f42..acc771f 100644 --- a/HAILogger/Program.cs +++ b/HAILogger/Program.cs @@ -1,5 +1,7 @@ using System; using System.Diagnostics; +using System.IO; +using System.Reflection; using System.ServiceProcess; namespace HAILogger @@ -22,7 +24,10 @@ namespace HAILogger ShowHelp(); return; case "-c": - Global.dir_config = args[++i]; + Global.config_file = args[++i]; + break; + case "-l": + Global.config_file = args[++i]; break; case "-i": interactive = true; @@ -30,6 +35,26 @@ namespace HAILogger } } + if (string.IsNullOrEmpty(Global.log_file)) + Global.log_file = Path.GetDirectoryName(Assembly.GetEntryAssembly().Location) + + Path.DirectorySeparatorChar + "EventLog.txt"; + + if (string.IsNullOrEmpty(Global.config_file)) + Global.config_file = Path.GetDirectoryName(Assembly.GetEntryAssembly().Location) + + Path.DirectorySeparatorChar + "HAILogger.ini"; + + Global.event_source = "HAI Logger"; + + try + { + Settings.LoadSettings(); + } + catch + { + // Errors are logged in LoadSettings(); + Environment.Exit(1); + } + if (Environment.UserInteractive || interactive) { Console.TreatControlCAsInput = false; @@ -64,9 +89,10 @@ namespace HAILogger static void ShowHelp() { Console.WriteLine( - AppDomain.CurrentDomain.FriendlyName + " [-c config_file] [-i]\n" + - "\t-c Specifies the name of the config file. Default is HAILogger.ini.\n" + - "\t-i Run in interactive mode."); + AppDomain.CurrentDomain.FriendlyName + " [-c config_file] [-l log_file] [-i]\n" + + "\t-c Specifies the name of the config file. Default is HAILogger.ini\n" + + "\t-l Specifies the name of the log file. Default is EventLog.txt\n" + + "\t-i Run in interactive mode"); } } } diff --git a/HAILogger/Properties/AssemblyInfo.cs b/HAILogger/Properties/AssemblyInfo.cs index 9be8e2e..1cad50e 100644 --- a/HAILogger/Properties/AssemblyInfo.cs +++ b/HAILogger/Properties/AssemblyInfo.cs @@ -32,5 +32,5 @@ using System.Runtime.InteropServices; // You can specify all the values or you can default the Build and Revision Numbers // by using the '*' as shown below: // [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyVersion("1.0.7.0")] -[assembly: AssemblyFileVersion("1.0.7.0")] +[assembly: AssemblyVersion("1.0.8.0")] +[assembly: AssemblyFileVersion("1.0.8.0")] diff --git a/HAILogger/Settings.cs b/HAILogger/Settings.cs index 2033154..69e7980 100644 --- a/HAILogger/Settings.cs +++ b/HAILogger/Settings.cs @@ -10,7 +10,7 @@ namespace HAILogger { public static void LoadSettings() { - NameValueCollection settings = LoadCollection(Global.dir_config + Path.DirectorySeparatorChar + "HAILogger.ini"); + NameValueCollection settings = LoadCollection(Global.config_file); // HAI Controller Global.hai_address = settings["hai_address"]; @@ -62,10 +62,8 @@ namespace HAILogger catch { Event.WriteError("Settings", "Invalid integer specified for " + section); - Environment.Exit(1); + throw; } - - throw new Exception("ValidateInt shouldn't reach here"); } private static int ValidatePort(NameValueCollection settings, string section) @@ -82,10 +80,8 @@ namespace HAILogger catch { Event.WriteError("Settings", "Invalid port specified for " + section); - Environment.Exit(1); + throw; } - - throw new Exception("ValidatePort shouldn't reach here"); } private static bool ValidateBool(NameValueCollection settings, string section) @@ -97,10 +93,8 @@ namespace HAILogger catch { Event.WriteError("Settings", "Invalid bool specified for " + section); - Environment.Exit(1); + throw; } - - throw new Exception("ValidateBool shouldn't reach here"); } private static IPAddress ValidateIP(NameValueCollection settings, string section) @@ -118,10 +112,8 @@ namespace HAILogger catch { Event.WriteError("Settings", "Invalid IP specified for " + section); - Environment.Exit(1); + throw; } - - throw new Exception("ValidateIP shouldn't reach here"); } private static string ValidateDirectory(NameValueCollection settings, string section) @@ -136,10 +128,8 @@ namespace HAILogger catch { Event.WriteError("Settings", "Invalid directory specified for " + section); - Environment.Exit(1); + throw; } - - throw new Exception("ValidateDirectory shouldn't reach here"); } private static MailAddress ValidateMailFrom(NameValueCollection settings, string section) @@ -151,10 +141,8 @@ namespace HAILogger catch { Event.WriteError("Settings", "Invalid email specified for " + section); - Environment.Exit(1); + throw; } - - throw new Exception("ValidateMailFrom shouldn't reach here"); } private static MailAddress[] ValidateMailTo(NameValueCollection settings, string section) @@ -175,10 +163,8 @@ namespace HAILogger catch { Event.WriteError("Settings", "Invalid email specified for " + section); - Environment.Exit(1); + throw; } - - throw new Exception("ValidateMailTo shouldn't reach here"); } private static string[] ValidateMultipleStrings(NameValueCollection settings, string section) @@ -193,10 +179,8 @@ namespace HAILogger catch { Event.WriteError("Settings", "Invalid string specified for " + section); - Environment.Exit(1); + throw; } - - throw new Exception("ValidateMultipleStrings shouldn't reach here"); } private static bool ValidateYesNo (NameValueCollection settings, string section) @@ -210,10 +194,8 @@ namespace HAILogger else { Event.WriteError("Settings", "Invalid yes/no specified for " + section); - Environment.Exit(1); + throw new Exception(); } - - throw new Exception("ValidateYesNo shouldn't reach here"); } private static NameValueCollection LoadCollection(string sFile) @@ -235,13 +217,15 @@ namespace HAILogger if (line.StartsWith("#")) continue; - string[] split = line.Split('='); + int pos = line.IndexOf('=', 0); - for (int i = 0; i < split.Length; i++) - split[i] = split[i].Trim(); + if (pos == -1) + continue; - if (split.Length == 2) - settings.Add(split[0], split[1]); + string key = line.Substring(0, pos).Trim(); + string value = line.Substring(pos + 1).Trim(); + + settings.Add(key, value); } sr.Close(); @@ -250,7 +234,7 @@ namespace HAILogger catch (FileNotFoundException) { Event.WriteError("Settings", "Unable to parse settings file " + sFile); - Environment.Exit(1); + throw; } return settings; diff --git a/HAILogger/WebNotification.cs b/HAILogger/WebNotification.cs index c0d4b92..9213e58 100644 --- a/HAILogger/WebNotification.cs +++ b/HAILogger/WebNotification.cs @@ -7,25 +7,33 @@ namespace HAILogger static class WebNotification { private static List subscriptions = new List(); + private static object subscriptions_lock = new object(); public static void AddSubscription(string callback) { - if (!subscriptions.Contains(callback)) + lock (subscriptions_lock) { - Event.WriteVerbose("WebRequest", "Adding subscription to " + callback); - subscriptions.Add(callback); + if (!subscriptions.Contains(callback)) + { + Event.WriteVerbose("WebNotification", "Adding subscription to " + callback); + subscriptions.Add(callback); + } } } public static void Send(string type, string body) { - WebClient client = new WebClient(); - client.Headers.Add(HttpRequestHeader.ContentType, "application/json"); - client.Headers.Add("type", type); - client.UploadStringCompleted += client_UploadStringCompleted; + string[] send; + lock (subscriptions_lock) + send = subscriptions.ToArray(); - foreach (string subscription in subscriptions) + foreach (string subscription in send) { + WebClient client = new WebClient(); + client.Headers.Add(HttpRequestHeader.ContentType, "application/json"); + client.Headers.Add("type", type); + client.UploadStringCompleted += client_UploadStringCompleted; + try { client.UploadStringAsync(new Uri(subscription), "POST", body, subscription); @@ -43,8 +51,10 @@ namespace HAILogger if (e.Error != null) { Event.WriteError("WebNotification", "An error occurred sending notification to " + e.UserState.ToString() + "\r\n" + e.Error.Message); - subscriptions.Remove(e.UserState.ToString()); + + lock (subscriptions_lock) + subscriptions.Remove(e.UserState.ToString()); } } } -} +} \ No newline at end of file diff --git a/README.md b/README.md index 734f249..95bbb7d 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ Provides logging and web service API for HAI/Leviton OmniPro II controllers ##Download -You can download the [binary here](http://www.excalibur-partners.com/downloads/HAILogger_1_0_7.zip) +You can download the [binary here](http://www.excalibur-partners.com/downloads/HAILogger_1_0_8.zip) ##Requirements - .NET Framework 4.0 @@ -20,12 +20,25 @@ You can download the [binary here](http://www.excalibur-partners.com/downloads/H - Emails are sent to mail_alarm_to when an area status changes - Prowl notifications are sent when an areas status changes -##Installation -1. Copy files to your desiered location like C:\HAILogger -2. Create mySQL database and import HAILogger.sql -3. Update HAILogger.ini with settings -4. Run HAILogger.exe and verify everything is working -5. For Windows Service run install.bat / uninstall.bat +##Installation Windows +1. Copy files to your desired location like C:\HAILogger +2. Edit HAILogger.ini and define at a minimum the controller IP and encryptions keys +3. Run HAILogger.exe to verify connectivity +4. For Windows Service run install.bat / uninstall.bat +5. Start service from Administrative Tools -> Services + +##Installation Linux +1. Copy files to your desired location like /opt/HAILogger +2. Configure at a minimum the controller IP and encryptions keys + - vim HAILogger.ini +3. Run as interactive to verify connectivity + - ./HAILogger.exe -i +4. Add systemd file and configure ExecStart path + - cp hailogger.service /etc/systemd/system/ + - vim /etc/systemd/system/hailogger.service +5. Enable at boot and start service + - systemctl enable hailogger.service + - systemctl start hailogger.service ##MySQL Setup You will want to install the MySQL Community Server, Workbench, and ODBC Connector. The Workbench software provides a graphical interface to administer the MySQL server. The HAI Logger uses ODBC to communicate with the database. The MySQL ODBC Connector library is needed for Windows ODBC to communicate with MySQL. Make sure you install version 5.1 of the MySQL ODBC Connector provided in the link below. @@ -37,7 +50,9 @@ http://dev.mysql.com/downloads/connector/odbc/5.1.html After installing MySQL server it should have asked you to setup an instance. One of the steps of the instance wizard was to create a root password. Assuming you installed the HAI Logger on the same computer you will want to use the below settings in HAILogger.ini. mysql_server = localhost + mysql_user = root + mysql_password = password you set in the wizard At this point we need to open MySQL Workbench to create the database (called a schema in the Workbench GUI) for HAILogger to use. @@ -58,6 +73,11 @@ To test the API you can use your browser to view a page or PowerShell (see below - Invoke-WebRequest -Uri "http://localhost:8000/SetUnit" -Method POST -ContentType "application/json" -Body (convertto-json -InputObject @{"id"=1;"value"=100}) -UseBasicParsing ##Change Log +Version 1.0.8 - 2016-11-28 +- Fixed web service threading when multiple subscriptions exist +- Added additional zone types to contact and motion web service API +- Split command line options for config and log files + Version 1.0.7 - 2016-11-25 - Use previous area state when area is arming for web service API - Add interactive command line option and use path separator for Mono compatibility