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.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<ZoneContract>(Helper.ConvertZone(
MSG.ObjectNumber(i), HAC.Zones[MSG.ObjectNumber(i)])));
break;
case enuZoneType.AwayInt:
case enuZoneType.NightInt:
WebNotification.Send("motion", Helper.Serialize<ZoneContract>(Helper.ConvertZone(
MSG.ObjectNumber(i), HAC.Zones[MSG.ObjectNumber(i)])));
break;

View file

@ -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);

View file

@ -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;

View file

@ -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:

View file

@ -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");
}
}
}

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
// 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")]

View file

@ -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;

View file

@ -7,25 +7,33 @@ namespace HAILogger
static class WebNotification
{
private static List<string> subscriptions = new List<string>();
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,7 +51,9 @@ 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());
}
}
}

View file

@ -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