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
This commit is contained in:
Ryan Wagoner 2016-11-29 22:58:51 -05:00
parent 0d83ded2a4
commit 039f7d023c
9 changed files with 113 additions and 73 deletions

View file

@ -3,7 +3,6 @@ using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Data; using System.Data;
using System.Data.Odbc; using System.Data.Odbc;
using System.IO;
using System.Reflection; using System.Reflection;
using System.Text; using System.Text;
using System.Threading; using System.Threading;
@ -44,14 +43,6 @@ namespace HAILogger
private void Server() 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 " + Event.WriteInfo("CoreServer", "Starting up server " +
Assembly.GetExecutingAssembly().GetName().Version.ToString()); Assembly.GetExecutingAssembly().GetName().Version.ToString());
@ -784,10 +775,13 @@ namespace HAILogger
case enuZoneType.X2EntryDelay: case enuZoneType.X2EntryDelay:
case enuZoneType.X4EntryDelay: case enuZoneType.X4EntryDelay:
case enuZoneType.Perimeter: case enuZoneType.Perimeter:
case enuZoneType.Tamper:
case enuZoneType.Auxiliary:
WebNotification.Send("contact", Helper.Serialize<ZoneContract>(Helper.ConvertZone( WebNotification.Send("contact", Helper.Serialize<ZoneContract>(Helper.ConvertZone(
MSG.ObjectNumber(i), HAC.Zones[MSG.ObjectNumber(i)]))); MSG.ObjectNumber(i), HAC.Zones[MSG.ObjectNumber(i)])));
break; break;
case enuZoneType.AwayInt: case enuZoneType.AwayInt:
case enuZoneType.NightInt:
WebNotification.Send("motion", Helper.Serialize<ZoneContract>(Helper.ConvertZone( WebNotification.Send("motion", Helper.Serialize<ZoneContract>(Helper.ConvertZone(
MSG.ObjectNumber(i), HAC.Zones[MSG.ObjectNumber(i)]))); MSG.ObjectNumber(i), HAC.Zones[MSG.ObjectNumber(i)])));
break; break;

View file

@ -148,7 +148,7 @@ namespace HAILogger
try 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); StreamWriter sw = new StreamWriter(fs);
sw.WriteLine(DateTime.Now.ToString("MM/dd/yyyy HH:mm:ss ") + source + ": " + value); sw.WriteLine(DateTime.Now.ToString("MM/dd/yyyy HH:mm:ss ") + source + ": " + value);

View file

@ -4,12 +4,12 @@ namespace HAILogger
{ {
public abstract class Global public abstract class Global
{ {
// Events Preset // Events
public static string event_log;
public static string event_source; public static string event_source;
// Directories // Files
public static string dir_config; public static string config_file;
public static string log_file;
// HAI Controller // HAI Controller
public static string hai_address; public static string hai_address;

View file

@ -53,7 +53,9 @@ namespace HAILogger
if ((zone.ZoneType == enuZoneType.EntryExit || if ((zone.ZoneType == enuZoneType.EntryExit ||
zone.ZoneType == enuZoneType.X2EntryDelay || zone.ZoneType == enuZoneType.X2EntryDelay ||
zone.ZoneType == enuZoneType.X4EntryDelay || 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 }); names.Add(new NameContract() { id = i, name = zone.Name });
} }
return names; return names;
@ -68,7 +70,8 @@ namespace HAILogger
{ {
clsZone zone = WebService.HAC.Zones[i]; 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 }); names.Add(new NameContract() { id = i, name = zone.Name });
} }
return names; return names;
@ -152,9 +155,12 @@ namespace HAILogger
case enuZoneType.X2EntryDelay: case enuZoneType.X2EntryDelay:
case enuZoneType.X4EntryDelay: case enuZoneType.X4EntryDelay:
case enuZoneType.Perimeter: case enuZoneType.Perimeter:
case enuZoneType.Tamper:
case enuZoneType.Auxiliary:
ctx.OutgoingResponse.Headers.Add("type", "contact"); ctx.OutgoingResponse.Headers.Add("type", "contact");
break; break;
case enuZoneType.AwayInt: case enuZoneType.AwayInt:
case enuZoneType.NightInt:
ctx.OutgoingResponse.Headers.Add("type", "motion"); ctx.OutgoingResponse.Headers.Add("type", "motion");
break; break;
case enuZoneType.Water: case enuZoneType.Water:

View file

@ -1,5 +1,7 @@
using System; using System;
using System.Diagnostics; using System.Diagnostics;
using System.IO;
using System.Reflection;
using System.ServiceProcess; using System.ServiceProcess;
namespace HAILogger namespace HAILogger
@ -22,7 +24,10 @@ namespace HAILogger
ShowHelp(); ShowHelp();
return; return;
case "-c": case "-c":
Global.dir_config = args[++i]; Global.config_file = args[++i];
break;
case "-l":
Global.config_file = args[++i];
break; break;
case "-i": case "-i":
interactive = true; 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) if (Environment.UserInteractive || interactive)
{ {
Console.TreatControlCAsInput = false; Console.TreatControlCAsInput = false;
@ -64,9 +89,10 @@ namespace HAILogger
static void ShowHelp() static void ShowHelp()
{ {
Console.WriteLine( Console.WriteLine(
AppDomain.CurrentDomain.FriendlyName + " [-c config_file] [-i]\n" + 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-c Specifies the name of the config file. Default is HAILogger.ini\n" +
"\t-i Run in interactive mode."); "\t-l Specifies the name of the log file. Default is EventLog.txt\n" +
"\t-i Run in interactive mode");
} }
} }
} }

View file

@ -32,5 +32,5 @@ using System.Runtime.InteropServices;
// You can specify all the values or you can default the Build and Revision Numbers // You can specify all the values or you can default the Build and Revision Numbers
// by using the '*' as shown below: // by using the '*' as shown below:
// [assembly: AssemblyVersion("1.0.*")] // [assembly: AssemblyVersion("1.0.*")]
[assembly: AssemblyVersion("1.0.7.0")] [assembly: AssemblyVersion("1.0.8.0")]
[assembly: AssemblyFileVersion("1.0.7.0")] [assembly: AssemblyFileVersion("1.0.8.0")]

View file

@ -10,7 +10,7 @@ namespace HAILogger
{ {
public static void LoadSettings() public static void LoadSettings()
{ {
NameValueCollection settings = LoadCollection(Global.dir_config + Path.DirectorySeparatorChar + "HAILogger.ini"); NameValueCollection settings = LoadCollection(Global.config_file);
// HAI Controller // HAI Controller
Global.hai_address = settings["hai_address"]; Global.hai_address = settings["hai_address"];
@ -62,10 +62,8 @@ namespace HAILogger
catch catch
{ {
Event.WriteError("Settings", "Invalid integer specified for " + section); 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) private static int ValidatePort(NameValueCollection settings, string section)
@ -82,10 +80,8 @@ namespace HAILogger
catch catch
{ {
Event.WriteError("Settings", "Invalid port specified for " + section); 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) private static bool ValidateBool(NameValueCollection settings, string section)
@ -97,10 +93,8 @@ namespace HAILogger
catch catch
{ {
Event.WriteError("Settings", "Invalid bool specified for " + section); 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) private static IPAddress ValidateIP(NameValueCollection settings, string section)
@ -118,10 +112,8 @@ namespace HAILogger
catch catch
{ {
Event.WriteError("Settings", "Invalid IP specified for " + section); 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) private static string ValidateDirectory(NameValueCollection settings, string section)
@ -136,10 +128,8 @@ namespace HAILogger
catch catch
{ {
Event.WriteError("Settings", "Invalid directory specified for " + section); 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) private static MailAddress ValidateMailFrom(NameValueCollection settings, string section)
@ -151,10 +141,8 @@ namespace HAILogger
catch catch
{ {
Event.WriteError("Settings", "Invalid email specified for " + section); 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) private static MailAddress[] ValidateMailTo(NameValueCollection settings, string section)
@ -175,10 +163,8 @@ namespace HAILogger
catch catch
{ {
Event.WriteError("Settings", "Invalid email specified for " + section); 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) private static string[] ValidateMultipleStrings(NameValueCollection settings, string section)
@ -193,10 +179,8 @@ namespace HAILogger
catch catch
{ {
Event.WriteError("Settings", "Invalid string specified for " + section); 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) private static bool ValidateYesNo (NameValueCollection settings, string section)
@ -210,10 +194,8 @@ namespace HAILogger
else else
{ {
Event.WriteError("Settings", "Invalid yes/no specified for " + section); 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) private static NameValueCollection LoadCollection(string sFile)
@ -235,13 +217,15 @@ namespace HAILogger
if (line.StartsWith("#")) if (line.StartsWith("#"))
continue; continue;
string[] split = line.Split('='); int pos = line.IndexOf('=', 0);
for (int i = 0; i < split.Length; i++) if (pos == -1)
split[i] = split[i].Trim(); continue;
if (split.Length == 2) string key = line.Substring(0, pos).Trim();
settings.Add(split[0], split[1]); string value = line.Substring(pos + 1).Trim();
settings.Add(key, value);
} }
sr.Close(); sr.Close();
@ -250,7 +234,7 @@ namespace HAILogger
catch (FileNotFoundException) catch (FileNotFoundException)
{ {
Event.WriteError("Settings", "Unable to parse settings file " + sFile); Event.WriteError("Settings", "Unable to parse settings file " + sFile);
Environment.Exit(1); throw;
} }
return settings; return settings;

View file

@ -7,25 +7,33 @@ namespace HAILogger
static class WebNotification static class WebNotification
{ {
private static List<string> subscriptions = new List<string>(); private static List<string> subscriptions = new List<string>();
private static object subscriptions_lock = new object();
public static void AddSubscription(string callback) public static void AddSubscription(string callback)
{ {
if (!subscriptions.Contains(callback)) lock (subscriptions_lock)
{ {
Event.WriteVerbose("WebRequest", "Adding subscription to " + callback); if (!subscriptions.Contains(callback))
subscriptions.Add(callback); {
Event.WriteVerbose("WebNotification", "Adding subscription to " + callback);
subscriptions.Add(callback);
}
} }
} }
public static void Send(string type, string body) public static void Send(string type, string body)
{ {
WebClient client = new WebClient(); string[] send;
client.Headers.Add(HttpRequestHeader.ContentType, "application/json"); lock (subscriptions_lock)
client.Headers.Add("type", type); send = subscriptions.ToArray();
client.UploadStringCompleted += client_UploadStringCompleted;
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 try
{ {
client.UploadStringAsync(new Uri(subscription), "POST", body, subscription); client.UploadStringAsync(new Uri(subscription), "POST", body, subscription);
@ -43,7 +51,9 @@ namespace HAILogger
if (e.Error != null) if (e.Error != null)
{ {
Event.WriteError("WebNotification", "An error occurred sending notification to " + e.UserState.ToString() + "\r\n" + e.Error.Message); 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());
} }
} }
} }

View file

@ -2,7 +2,7 @@
Provides logging and web service API for HAI/Leviton OmniPro II controllers Provides logging and web service API for HAI/Leviton OmniPro II controllers
##Download ##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 ##Requirements
- .NET Framework 4.0 - .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 - Emails are sent to mail_alarm_to when an area status changes
- Prowl notifications are sent when an areas status changes - Prowl notifications are sent when an areas status changes
##Installation ##Installation Windows
1. Copy files to your desiered location like C:\HAILogger 1. Copy files to your desired location like C:\HAILogger
2. Create mySQL database and import HAILogger.sql 2. Edit HAILogger.ini and define at a minimum the controller IP and encryptions keys
3. Update HAILogger.ini with settings 3. Run HAILogger.exe to verify connectivity
4. Run HAILogger.exe and verify everything is working 4. For Windows Service run install.bat / uninstall.bat
5. 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 ##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. 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. 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_server = localhost
mysql_user = root mysql_user = root
mysql_password = password you set in the wizard 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. 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 - Invoke-WebRequest -Uri "http://localhost:8000/SetUnit" -Method POST -ContentType "application/json" -Body (convertto-json -InputObject @{"id"=1;"value"=100}) -UseBasicParsing
##Change Log ##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 Version 1.0.7 - 2016-11-25
- Use previous area state when area is arming for web service API - Use previous area state when area is arming for web service API
- Add interactive command line option and use path separator for Mono compatibility - Add interactive command line option and use path separator for Mono compatibility