mirror of
https://github.com/excaliburpartners/OmniLinkBridge
synced 2024-12-22 02:32:25 +00:00
1.1.0 - Renamed to OmniLinkBridge
- Restructured code to be event based with modules - Added MQTT module for Home Assistant - Added pushover notifications - Added web service API subscriptions file to persist subscriptions
This commit is contained in:
parent
039f7d023c
commit
4e2bb85623
4
.gitignore
vendored
Normal file
4
.gitignore
vendored
Normal file
|
@ -0,0 +1,4 @@
|
|||
bin/
|
||||
obj/
|
||||
packages/
|
||||
.vs
|
|
@ -1,22 +0,0 @@
|
|||
|
||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||
# Visual Studio 14
|
||||
VisualStudioVersion = 14.0.25123.0
|
||||
MinimumVisualStudioVersion = 10.0.40219.1
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "HAILogger", "HAILogger\HAILogger.csproj", "{0A636707-98BA-45AB-9843-AED430933CEE}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|x86 = Debug|x86
|
||||
Release|x86 = Release|x86
|
||||
EndGlobalSection
|
||||
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||
{0A636707-98BA-45AB-9843-AED430933CEE}.Debug|x86.ActiveCfg = Debug|x86
|
||||
{0A636707-98BA-45AB-9843-AED430933CEE}.Debug|x86.Build.0 = Debug|x86
|
||||
{0A636707-98BA-45AB-9843-AED430933CEE}.Release|x86.ActiveCfg = Release|x86
|
||||
{0A636707-98BA-45AB-9843-AED430933CEE}.Release|x86.Build.0 = Release|x86
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
EndGlobalSection
|
||||
EndGlobal
|
|
@ -1,16 +0,0 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<configuration>
|
||||
<system.diagnostics>
|
||||
<switches>
|
||||
<!-- This switch controls data messages. In order to receive data
|
||||
trace messages, change value="0" to value="1" -->
|
||||
<add name="DataMessagesSwitch" value="0"/>
|
||||
<!-- This switch controls general messages. In order to
|
||||
receive general trace messages change the value to the
|
||||
appropriate level. "1" gives error messages, "2" gives errors
|
||||
and warnings, "3" gives more detailed error information, and
|
||||
"4" gives verbose trace information -->
|
||||
<add name="TraceLevelSwitch" value="3"/>
|
||||
</switches>
|
||||
</system.diagnostics>
|
||||
<startup><supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.0"/></startup></configuration>
|
File diff suppressed because it is too large
Load diff
|
@ -1,165 +0,0 @@
|
|||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Net;
|
||||
using System.Net.Mail;
|
||||
|
||||
namespace HAILogger
|
||||
{
|
||||
static class Event
|
||||
{
|
||||
public static void WriteVerbose(string value)
|
||||
{
|
||||
Trace.WriteLine(value);
|
||||
LogFile(TraceLevel.Verbose, "VERBOSE", value);
|
||||
}
|
||||
|
||||
public static void WriteVerbose(string source, string value)
|
||||
{
|
||||
Trace.WriteLine("VERBOSE: " + source + ": " + value);
|
||||
LogFile(TraceLevel.Verbose, "VERBOSE: " + source, value);
|
||||
}
|
||||
|
||||
public static void WriteInfo(string source, string value, bool alert)
|
||||
{
|
||||
WriteInfo(source, value);
|
||||
if (alert)
|
||||
{
|
||||
LogEvent(EventLogEntryType.Information, source, value);
|
||||
SendMail("Info", source, value);
|
||||
}
|
||||
}
|
||||
|
||||
public static void WriteInfo(string source, string value)
|
||||
{
|
||||
Trace.WriteLine("INFO: " + source + ": " + value);
|
||||
LogFile(TraceLevel.Info, "INFO: " + source, value);
|
||||
}
|
||||
|
||||
public static void WriteWarn(string source, string value, bool alert)
|
||||
{
|
||||
WriteWarn(source, value);
|
||||
if (alert)
|
||||
SendMail("Warn", source, value);
|
||||
}
|
||||
|
||||
public static void WriteWarn(string source, string value)
|
||||
{
|
||||
Trace.WriteLine("WARN: " + source + ": " + value);
|
||||
LogFile(TraceLevel.Warning, "WARN: " + source, value);
|
||||
LogEvent(EventLogEntryType.Warning, source, value);
|
||||
}
|
||||
|
||||
public static void WriteError(string source, string value)
|
||||
{
|
||||
Trace.WriteLine("ERROR: " + source + ": " + value);
|
||||
LogFile(TraceLevel.Error, "ERROR: " + source, value);
|
||||
LogEvent(EventLogEntryType.Error, source, value);
|
||||
SendMail("Error", source, value);
|
||||
}
|
||||
|
||||
public static void WriteAlarm(string source, string value)
|
||||
{
|
||||
Trace.WriteLine("ALARM: " + source + ": " + value);
|
||||
LogFile(TraceLevel.Error, "ALARM: " + source, value);
|
||||
LogEvent(EventLogEntryType.Warning, source, value);
|
||||
|
||||
if (Global.mail_alarm_to != null && Global.mail_alarm_to.Length > 0)
|
||||
{
|
||||
MailMessage mail = new MailMessage();
|
||||
mail.From = Global.mail_from;
|
||||
foreach (MailAddress address in Global.mail_alarm_to)
|
||||
mail.To.Add(address);
|
||||
mail.Priority = MailPriority.High;
|
||||
mail.Subject = value;
|
||||
mail.Body = value;
|
||||
|
||||
SmtpClient smtp = new SmtpClient(Global.mail_server, Global.mail_port);
|
||||
|
||||
if (!string.IsNullOrEmpty(Global.mail_username))
|
||||
{
|
||||
smtp.UseDefaultCredentials = false;
|
||||
smtp.Credentials = new NetworkCredential(Global.mail_username, Global.mail_password);
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
smtp.Send(mail);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
string error = "An error occurred sending email notification\r\n" + ex.Message;
|
||||
LogFile(TraceLevel.Error, "ERROR: " + source, error);
|
||||
LogEvent(EventLogEntryType.Error, "EventNotification", error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void SendMail(string level, string source, string value)
|
||||
{
|
||||
if (Global.mail_to == null || Global.mail_to.Length == 0)
|
||||
return;
|
||||
|
||||
MailMessage mail = new MailMessage();
|
||||
mail.From = Global.mail_from;
|
||||
foreach (MailAddress address in Global.mail_to)
|
||||
mail.To.Add(address);
|
||||
mail.Subject = Global.event_source + " - " + level;
|
||||
mail.Body = source + ": " + value;
|
||||
|
||||
SmtpClient smtp = new SmtpClient(Global.mail_server, Global.mail_port);
|
||||
|
||||
if (!string.IsNullOrEmpty(Global.mail_username))
|
||||
{
|
||||
smtp.UseDefaultCredentials = false;
|
||||
smtp.Credentials = new NetworkCredential(Global.mail_username, Global.mail_password);
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
smtp.Send(mail);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
string error = "An error occurred sending email notification\r\n" + ex.Message;
|
||||
LogFile(TraceLevel.Error, "ERROR: " + source, error);
|
||||
LogEvent(EventLogEntryType.Error, "EventNotification", error);
|
||||
}
|
||||
}
|
||||
|
||||
private static void LogEvent(EventLogEntryType type, string source, string value)
|
||||
{
|
||||
string event_log = "Application";
|
||||
|
||||
if (!EventLog.SourceExists(Global.event_source))
|
||||
EventLog.CreateEventSource(Global.event_source, event_log);
|
||||
|
||||
string event_msg = source + ": " + value;
|
||||
|
||||
EventLog.WriteEntry(Global.event_source, event_msg, type, 234);
|
||||
}
|
||||
|
||||
private static void LogFile(TraceLevel level, string source, string value)
|
||||
{
|
||||
TraceSwitch tswitch = new TraceSwitch("TraceLevelSwitch", "Trace Level for Entire Application");
|
||||
|
||||
if (tswitch.Level < level)
|
||||
return;
|
||||
|
||||
try
|
||||
{
|
||||
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);
|
||||
|
||||
sw.Close();
|
||||
fs.Close();
|
||||
}
|
||||
catch
|
||||
{
|
||||
LogEvent(EventLogEntryType.Error, "EventLogger", "Unable to write to the file log.");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,54 +0,0 @@
|
|||
using System.Net.Mail;
|
||||
|
||||
namespace HAILogger
|
||||
{
|
||||
public abstract class Global
|
||||
{
|
||||
// Events
|
||||
public static string event_source;
|
||||
|
||||
// Files
|
||||
public static string config_file;
|
||||
public static string log_file;
|
||||
|
||||
// HAI Controller
|
||||
public static string hai_address;
|
||||
public static int hai_port;
|
||||
public static string hai_key1;
|
||||
public static string hai_key2;
|
||||
public static bool hai_time_sync;
|
||||
public static int hai_time_interval;
|
||||
public static int hai_time_drift;
|
||||
|
||||
// mySQL Database
|
||||
public static bool mysql_logging;
|
||||
public static string mysql_connection;
|
||||
|
||||
// Events
|
||||
public static string mail_server;
|
||||
public static int mail_port;
|
||||
public static string mail_username;
|
||||
public static string mail_password;
|
||||
public static MailAddress mail_from;
|
||||
public static MailAddress[] mail_to;
|
||||
public static MailAddress[] mail_alarm_to;
|
||||
|
||||
// Prowl Notifications
|
||||
public static string[] prowl_key;
|
||||
public static bool prowl_messages;
|
||||
|
||||
// Web Service
|
||||
public static bool webapi_enabled;
|
||||
public static int webapi_port;
|
||||
|
||||
// Verbose Output
|
||||
public static bool verbose_unhandled;
|
||||
public static bool verbose_event;
|
||||
public static bool verbose_area;
|
||||
public static bool verbose_zone;
|
||||
public static bool verbose_thermostat_timer;
|
||||
public static bool verbose_thermostat;
|
||||
public static bool verbose_unit;
|
||||
public static bool verbose_message;
|
||||
}
|
||||
}
|
|
@ -1,107 +0,0 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project ToolsVersion="4.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<PropertyGroup>
|
||||
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
|
||||
<Platform Condition=" '$(Platform)' == '' ">x86</Platform>
|
||||
<ProductVersion>8.0.30703</ProductVersion>
|
||||
<SchemaVersion>2.0</SchemaVersion>
|
||||
<ProjectGuid>{0A636707-98BA-45AB-9843-AED430933CEE}</ProjectGuid>
|
||||
<OutputType>Exe</OutputType>
|
||||
<AppDesignerFolder>Properties</AppDesignerFolder>
|
||||
<RootNamespace>HAILogger</RootNamespace>
|
||||
<AssemblyName>HAILogger</AssemblyName>
|
||||
<TargetFrameworkVersion>v4.0</TargetFrameworkVersion>
|
||||
<FileAlignment>512</FileAlignment>
|
||||
<TargetFrameworkProfile />
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|x86' ">
|
||||
<PlatformTarget>x86</PlatformTarget>
|
||||
<DebugSymbols>true</DebugSymbols>
|
||||
<DebugType>full</DebugType>
|
||||
<Optimize>false</Optimize>
|
||||
<OutputPath>bin\Debug\</OutputPath>
|
||||
<DefineConstants>DEBUG;TRACE</DefineConstants>
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
<WarningLevel>4</WarningLevel>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|x86' ">
|
||||
<PlatformTarget>x86</PlatformTarget>
|
||||
<DebugType>pdbonly</DebugType>
|
||||
<Optimize>true</Optimize>
|
||||
<OutputPath>bin\Release\</OutputPath>
|
||||
<DefineConstants>TRACE</DefineConstants>
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
<WarningLevel>4</WarningLevel>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<Reference Include="HAI.Controller">
|
||||
<HintPath>.\HAI.Controller.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="System" />
|
||||
<Reference Include="System.Configuration.Install" />
|
||||
<Reference Include="System.Data" />
|
||||
<Reference Include="System.Management" />
|
||||
<Reference Include="System.Runtime.Serialization" />
|
||||
<Reference Include="System.ServiceModel" />
|
||||
<Reference Include="System.ServiceModel.Web" />
|
||||
<Reference Include="System.ServiceProcess" />
|
||||
<Reference Include="System.Xml" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Compile Include="CoreServer.cs" />
|
||||
<Compile Include="Event.cs" />
|
||||
<Compile Include="Global.cs" />
|
||||
<Compile Include="HAIService.cs" />
|
||||
<Compile Include="Helper.cs" />
|
||||
<Compile Include="CommandContract.cs" />
|
||||
<Compile Include="IHAIService.cs" />
|
||||
<Compile Include="Program.cs" />
|
||||
<Compile Include="ProjectInstaller.cs">
|
||||
<SubType>Component</SubType>
|
||||
</Compile>
|
||||
<Compile Include="ProjectInstaller.Designer.cs">
|
||||
<DependentUpon>ProjectInstaller.cs</DependentUpon>
|
||||
</Compile>
|
||||
<Compile Include="Properties\AssemblyInfo.cs" />
|
||||
<Compile Include="Prowl.cs" />
|
||||
<Compile Include="Service.cs">
|
||||
<SubType>Component</SubType>
|
||||
</Compile>
|
||||
<Compile Include="Service.Designer.cs">
|
||||
<DependentUpon>Service.cs</DependentUpon>
|
||||
</Compile>
|
||||
<Compile Include="Settings.cs" />
|
||||
<Compile Include="SubscribeContract.cs" />
|
||||
<Compile Include="ThermostatContract.cs" />
|
||||
<Compile Include="NameContract.cs" />
|
||||
<Compile Include="AreaContract.cs" />
|
||||
<Compile Include="ZoneContract.cs" />
|
||||
<Compile Include="UnitContract.cs" />
|
||||
<Compile Include="WebNotification.cs" />
|
||||
<Compile Include="WebService.cs" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<None Include="App.config">
|
||||
<SubType>Designer</SubType>
|
||||
</None>
|
||||
<None Include="HAILogger.ini">
|
||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||
</None>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<EmbeddedResource Include="ProjectInstaller.resx">
|
||||
<DependentUpon>ProjectInstaller.cs</DependentUpon>
|
||||
</EmbeddedResource>
|
||||
<EmbeddedResource Include="Service.resx">
|
||||
<DependentUpon>Service.cs</DependentUpon>
|
||||
</EmbeddedResource>
|
||||
</ItemGroup>
|
||||
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
|
||||
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
|
||||
Other similar extension points exist, see Microsoft.Common.targets.
|
||||
<Target Name="BeforeBuild">
|
||||
</Target>
|
||||
<Target Name="AfterBuild">
|
||||
</Target>
|
||||
-->
|
||||
</Project>
|
|
@ -1,55 +0,0 @@
|
|||
# HAI Controller
|
||||
hai_address =
|
||||
hai_port = 4369
|
||||
hai_key1 = 00-00-00-00-00-00-00-00
|
||||
hai_key2 = 00-00-00-00-00-00-00-00
|
||||
|
||||
# HAI Controller Time Sync (yes/no)
|
||||
# hai_time_check is interval in minutes to check controller time
|
||||
# hai_time_adj is the drift in seconds to allow before an adjustment is made
|
||||
hai_time_sync = yes
|
||||
hai_time_interval = 60
|
||||
hai_time_drift = 10
|
||||
|
||||
# mySQL Database Logging (yes/no)
|
||||
mysql_logging = no
|
||||
mysql_server =
|
||||
mysql_database =
|
||||
mysql_user =
|
||||
mysql_password =
|
||||
|
||||
# Event Notifications
|
||||
# mail_username and mail_password optional for authenticated mail
|
||||
# mail_to sent for service notifications
|
||||
# mail_alarm_to sent for FIRE, BURGLARY, AUX, DURESS
|
||||
mail_server =
|
||||
mail_port = 25
|
||||
mail_username =
|
||||
mail_password =
|
||||
mail_from =
|
||||
#mail_to =
|
||||
#mail_to =
|
||||
#mail_alarm_to =
|
||||
#mail_alarm_to =
|
||||
|
||||
# Prowl Notifications
|
||||
# Register for API key at http://www.prowlapp.com
|
||||
# Sent for FIRE, BURGLARY, AUX, DURESS
|
||||
# prowl_messages (yes/no) for console message notifications
|
||||
#prowl_key =
|
||||
#prowl_key =
|
||||
prowl_messages = no
|
||||
|
||||
# Web service
|
||||
# Used for integration with Samsung SmartThings
|
||||
webapi_enabled = yes
|
||||
webapi_port = 8000
|
||||
|
||||
# Verbose Output (yes/no)
|
||||
verbose_unhandled = yes
|
||||
verbose_area = yes
|
||||
verbose_zone = yes
|
||||
verbose_thermostat_timer = yes
|
||||
verbose_thermostat = yes
|
||||
verbose_unit = yes
|
||||
verbose_message = yes
|
|
@ -1,50 +0,0 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Net;
|
||||
|
||||
namespace HAILogger
|
||||
{
|
||||
public enum ProwlPriority
|
||||
{
|
||||
VeryLow = -2,
|
||||
Moderate = -1,
|
||||
Normal = 0,
|
||||
High = 1,
|
||||
Emergency = 2,
|
||||
};
|
||||
|
||||
static class Prowl
|
||||
{
|
||||
public static void Notify(string source, string description)
|
||||
{
|
||||
Notify(source, description, ProwlPriority.Normal);
|
||||
}
|
||||
|
||||
public static void Notify(string source, string description, ProwlPriority priority)
|
||||
{
|
||||
Uri URI = new Uri("https://api.prowlapp.com/publicapi/add");
|
||||
|
||||
foreach (string key in Global.prowl_key)
|
||||
{
|
||||
List<string> parameters = new List<string>();
|
||||
|
||||
parameters.Add("apikey=" + key);
|
||||
parameters.Add("priority= " + (int)priority);
|
||||
parameters.Add("application=" + Global.event_source);
|
||||
parameters.Add("event=" + source);
|
||||
parameters.Add("description=" + description);
|
||||
|
||||
WebClient client = new WebClient();
|
||||
client.Headers[HttpRequestHeader.ContentType] = "application/x-www-form-urlencoded";
|
||||
client.UploadStringAsync(URI, string.Join("&", parameters.ToArray()));
|
||||
client.UploadStringCompleted += client_UploadStringCompleted;
|
||||
}
|
||||
}
|
||||
|
||||
static void client_UploadStringCompleted(object sender, UploadStringCompletedEventArgs e)
|
||||
{
|
||||
if(e.Error != null)
|
||||
Event.WriteError("ProwlNotification", "An error occurred sending notification\r\n" + e.Error.Message);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,60 +0,0 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Net;
|
||||
|
||||
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)
|
||||
{
|
||||
lock (subscriptions_lock)
|
||||
{
|
||||
if (!subscriptions.Contains(callback))
|
||||
{
|
||||
Event.WriteVerbose("WebNotification", "Adding subscription to " + callback);
|
||||
subscriptions.Add(callback);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static void Send(string type, string body)
|
||||
{
|
||||
string[] send;
|
||||
lock (subscriptions_lock)
|
||||
send = subscriptions.ToArray();
|
||||
|
||||
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);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Event.WriteError("WebNotification", "An error occurred sending notification to " + subscription + "\r\n" + ex.ToString());
|
||||
subscriptions.Remove(subscription);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void client_UploadStringCompleted(object sender, UploadStringCompletedEventArgs e)
|
||||
{
|
||||
if (e.Error != null)
|
||||
{
|
||||
Event.WriteError("WebNotification", "An error occurred sending notification to " + e.UserState.ToString() + "\r\n" + e.Error.Message);
|
||||
|
||||
lock (subscriptions_lock)
|
||||
subscriptions.Remove(e.UserState.ToString());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,44 +0,0 @@
|
|||
using HAI_Shared;
|
||||
using System;
|
||||
using System.ServiceModel;
|
||||
using System.ServiceModel.Description;
|
||||
using System.ServiceModel.Web;
|
||||
|
||||
namespace HAILogger
|
||||
{
|
||||
public class WebService
|
||||
{
|
||||
public static clsHAC HAC;
|
||||
WebServiceHost host;
|
||||
|
||||
public WebService(clsHAC hac)
|
||||
{
|
||||
HAC = hac;
|
||||
}
|
||||
|
||||
public void Start()
|
||||
{
|
||||
Uri uri = new Uri("http://0.0.0.0:" + Global.webapi_port + "/");
|
||||
host = new WebServiceHost(typeof(HAIService), uri);
|
||||
|
||||
try
|
||||
{
|
||||
ServiceEndpoint ep = host.AddServiceEndpoint(typeof(IHAIService), new WebHttpBinding(), "");
|
||||
host.Open();
|
||||
|
||||
Event.WriteInfo("WebService", "Listening on " + uri.ToString());
|
||||
}
|
||||
catch (CommunicationException ex)
|
||||
{
|
||||
Event.WriteError("WebService", "An exception occurred: " + ex.Message);
|
||||
host.Abort();
|
||||
}
|
||||
}
|
||||
|
||||
public void Stop()
|
||||
{
|
||||
if (host != null)
|
||||
host.Close();
|
||||
}
|
||||
}
|
||||
}
|
31
OmniLinkBridge.sln
Normal file
31
OmniLinkBridge.sln
Normal file
|
@ -0,0 +1,31 @@
|
|||
|
||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||
# Visual Studio 15
|
||||
VisualStudioVersion = 15.0.27703.2026
|
||||
MinimumVisualStudioVersion = 10.0.40219.1
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "OmniLinkBridge", "OmniLinkBridge\OmniLinkBridge.csproj", "{0A636707-98BA-45AB-9843-AED430933CEE}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "OmniLinkBridgeTest", "OmniLinkBridgeTest\OmniLinkBridgeTest.csproj", "{6E6950E4-35F9-4D99-8ADA-B7E2F29D4172}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
Release|Any CPU = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||
{0A636707-98BA-45AB-9843-AED430933CEE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{0A636707-98BA-45AB-9843-AED430933CEE}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{0A636707-98BA-45AB-9843-AED430933CEE}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{0A636707-98BA-45AB-9843-AED430933CEE}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{6E6950E4-35F9-4D99-8ADA-B7E2F29D4172}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{6E6950E4-35F9-4D99-8ADA-B7E2F29D4172}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{6E6950E4-35F9-4D99-8ADA-B7E2F29D4172}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{6E6950E4-35F9-4D99-8ADA-B7E2F29D4172}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
EndGlobalSection
|
||||
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||
SolutionGuid = {C65BDBEF-1B50-442F-BC26-3A5FE3EDFCA3}
|
||||
EndGlobalSection
|
||||
EndGlobal
|
37
OmniLinkBridge/App.config
Normal file
37
OmniLinkBridge/App.config
Normal file
|
@ -0,0 +1,37 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<configuration>
|
||||
<configSections>
|
||||
<section name="log4net" type="log4net.Config.Log4NetConfigurationSectionHandler, log4net"/>
|
||||
</configSections>
|
||||
<startup>
|
||||
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5.2"/>
|
||||
</startup>
|
||||
<log4net>
|
||||
<appender name="TraceAppender" type="log4net.Appender.TraceAppender">
|
||||
<layout type="log4net.Layout.PatternLayout">
|
||||
<param name="ConversionPattern" value="%date %-5level: %message%newline"/>
|
||||
</layout>
|
||||
</appender>
|
||||
<appender name="RollingFileAppender" type="log4net.Appender.RollingFileAppender">
|
||||
<file value="log.txt"/>
|
||||
<appendToFile value="true"/>
|
||||
<rollingStyle value="Composite"/>
|
||||
<datePattern value="yyyyMMdd"/>
|
||||
<maxSizeRollBackups value="10"/>
|
||||
<maximumFileSize value="5MB"/>
|
||||
<immediateFlush value="true"/>
|
||||
<layout type="log4net.Layout.PatternLayout">
|
||||
<conversionPattern value="%date [%thread] %-5level %logger - %message%newline"/>
|
||||
</layout>
|
||||
<filter type="log4net.Filter.LevelRangeFilter">
|
||||
<levelMin value="WARN"/>
|
||||
<levelMax value="FATAL"/>
|
||||
</filter>
|
||||
</appender>
|
||||
<root>
|
||||
<level value="ALL"/>
|
||||
<appender-ref ref="TraceAppender"/>
|
||||
<appender-ref ref="RollingFileAppender"/>
|
||||
</root>
|
||||
</log4net>
|
||||
</configuration>
|
75
OmniLinkBridge/CoreServer.cs
Normal file
75
OmniLinkBridge/CoreServer.cs
Normal file
|
@ -0,0 +1,75 @@
|
|||
using OmniLinkBridge.Modules;
|
||||
using OmniLinkBridge.OmniLink;
|
||||
using log4net;
|
||||
using System.Collections.Generic;
|
||||
using System.Reflection;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace OmniLinkBridge
|
||||
{
|
||||
public class CoreServer
|
||||
{
|
||||
private static ILog log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);
|
||||
|
||||
private OmniLinkII omnilink;
|
||||
private readonly List<IModule> modules = new List<IModule>();
|
||||
private readonly List<Task> tasks = new List<Task>();
|
||||
|
||||
public CoreServer()
|
||||
{
|
||||
Thread handler = new Thread(Server);
|
||||
handler.Start();
|
||||
}
|
||||
|
||||
private void Server()
|
||||
{
|
||||
Global.running = true;
|
||||
|
||||
log.Debug("Starting up server " +
|
||||
Assembly.GetExecutingAssembly().GetName().Version.ToString());
|
||||
|
||||
// Controller connection
|
||||
modules.Add(omnilink = new OmniLinkII(Global.controller_address, Global.controller_port, Global.controller_key1, Global.controller_key2));
|
||||
|
||||
// Initialize modules
|
||||
modules.Add(new LoggerModule(omnilink));
|
||||
|
||||
if (Global.time_sync)
|
||||
modules.Add(new TimeSyncModule(omnilink));
|
||||
|
||||
if (Global.webapi_enabled)
|
||||
modules.Add(new WebServiceModule(omnilink));
|
||||
|
||||
if(Global.mqtt_enabled)
|
||||
modules.Add(new MQTTModule(omnilink));
|
||||
|
||||
// Startup modules
|
||||
foreach (IModule module in modules)
|
||||
{
|
||||
tasks.Add(Task.Factory.StartNew(() =>
|
||||
{
|
||||
module.Startup();
|
||||
}));
|
||||
}
|
||||
|
||||
// Wait for all threads to stop
|
||||
Task.WaitAll(tasks.ToArray());
|
||||
}
|
||||
|
||||
public void Shutdown()
|
||||
{
|
||||
Global.running = false;
|
||||
|
||||
// Shutdown modules
|
||||
foreach (IModule module in modules)
|
||||
module.Shutdown();
|
||||
|
||||
// Wait for all threads to stop
|
||||
if (tasks != null)
|
||||
Task.WaitAll(tasks.ToArray());
|
||||
|
||||
log.Debug("Shutdown completed");
|
||||
}
|
||||
}
|
||||
}
|
45
OmniLinkBridge/Extensions.cs
Normal file
45
OmniLinkBridge/Extensions.cs
Normal file
|
@ -0,0 +1,45 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Runtime.Serialization.Json;
|
||||
using System.Text;
|
||||
|
||||
namespace OmniLinkBridge
|
||||
{
|
||||
public static class Extensions
|
||||
{
|
||||
public static double ToCelsius(this double f)
|
||||
{
|
||||
// Convert to celsius
|
||||
return (5.0 / 9.0 * (f - 32));
|
||||
}
|
||||
|
||||
public static int ToOmniTemp(this double c)
|
||||
{
|
||||
// Convert to omni temp (0 is -40C and 255 is 87.5C)
|
||||
return (int)Math.Round((c + 40) * 2, 0);
|
||||
}
|
||||
|
||||
public static bool IsBitSet(this byte b, int pos)
|
||||
{
|
||||
return (b & (1 << pos)) != 0;
|
||||
}
|
||||
|
||||
public static List<int> ParseRanges(this string ranges)
|
||||
{
|
||||
string[] groups = ranges.Split(',');
|
||||
return groups.SelectMany(t => ParseRange(t)).ToList();
|
||||
}
|
||||
|
||||
private static List<int> ParseRange(string range)
|
||||
{
|
||||
List<int> RangeNums = range
|
||||
.Split('-')
|
||||
.Select(t => new String(t.Where(Char.IsDigit).ToArray())) // Digits Only
|
||||
.Where(t => !string.IsNullOrWhiteSpace(t)) // Only if has a value
|
||||
.Select(t => int.Parse(t)).ToList(); // digit to int
|
||||
return RangeNums.Count.Equals(2) ? Enumerable.Range(RangeNums.Min(), (RangeNums.Max() + 1) - RangeNums.Min()).ToList() : RangeNums;
|
||||
}
|
||||
}
|
||||
}
|
75
OmniLinkBridge/Global.cs
Normal file
75
OmniLinkBridge/Global.cs
Normal file
|
@ -0,0 +1,75 @@
|
|||
using OmniLinkBridge.MQTT;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Net.Mail;
|
||||
|
||||
namespace OmniLinkBridge
|
||||
{
|
||||
public abstract class Global
|
||||
{
|
||||
public static bool running;
|
||||
|
||||
// Config File
|
||||
public static string config_file;
|
||||
|
||||
// HAI / Leviton Omni Controller
|
||||
public static string controller_address;
|
||||
public static int controller_port;
|
||||
public static string controller_key1;
|
||||
public static string controller_key2;
|
||||
|
||||
// Time Sync
|
||||
public static bool time_sync;
|
||||
public static int time_interval;
|
||||
public static int time_drift;
|
||||
|
||||
// Verbose Console
|
||||
public static bool verbose_unhandled;
|
||||
public static bool verbose_event;
|
||||
public static bool verbose_area;
|
||||
public static bool verbose_zone;
|
||||
public static bool verbose_thermostat_timer;
|
||||
public static bool verbose_thermostat;
|
||||
public static bool verbose_unit;
|
||||
public static bool verbose_message;
|
||||
|
||||
// mySQL Logging
|
||||
public static bool mysql_logging;
|
||||
public static string mysql_connection;
|
||||
|
||||
// Web Service
|
||||
public static bool webapi_enabled;
|
||||
public static int webapi_port;
|
||||
public static string webapi_subscriptions_file;
|
||||
|
||||
// MQTT
|
||||
public static bool mqtt_enabled;
|
||||
public static string mqtt_server;
|
||||
public static int mqtt_port;
|
||||
public static string mqtt_username;
|
||||
public static string mqtt_password;
|
||||
public static string mqtt_discovery_prefix;
|
||||
public static HashSet<int> mqtt_discovery_ignore_zones;
|
||||
public static HashSet<int> mqtt_discovery_ignore_units;
|
||||
public static ConcurrentDictionary<int, OverrideZone> mqtt_discovery_override_zone;
|
||||
|
||||
// Notifications
|
||||
public static bool notify_area;
|
||||
public static bool notify_message;
|
||||
|
||||
// Email Notifications
|
||||
public static string mail_server;
|
||||
public static int mail_port;
|
||||
public static string mail_username;
|
||||
public static string mail_password;
|
||||
public static MailAddress mail_from;
|
||||
public static MailAddress[] mail_to;
|
||||
|
||||
// Prowl Notifications
|
||||
public static string[] prowl_key;
|
||||
|
||||
// Pushover Notifications
|
||||
public static string pushover_token;
|
||||
public static string[] pushover_user;
|
||||
}
|
||||
}
|
15
OmniLinkBridge/MQTT/Alarm.cs
Normal file
15
OmniLinkBridge/MQTT/Alarm.cs
Normal file
|
@ -0,0 +1,15 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace OmniLinkBridge.MQTT
|
||||
{
|
||||
public class Alarm : Device
|
||||
{
|
||||
public string command_topic { get; set; }
|
||||
|
||||
//public string code { get; set; } = string.Empty;
|
||||
}
|
||||
}
|
30
OmniLinkBridge/MQTT/BinarySensor.cs
Normal file
30
OmniLinkBridge/MQTT/BinarySensor.cs
Normal file
|
@ -0,0 +1,30 @@
|
|||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Converters;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace OmniLinkBridge.MQTT
|
||||
{
|
||||
public class BinarySensor : Device
|
||||
{
|
||||
[JsonConverter(typeof(StringEnumConverter))]
|
||||
public enum DeviceClass
|
||||
{
|
||||
battery,
|
||||
door,
|
||||
garage_door,
|
||||
gas,
|
||||
moisture,
|
||||
motion,
|
||||
problem,
|
||||
smoke,
|
||||
window
|
||||
}
|
||||
|
||||
[JsonProperty(NullValueHandling = NullValueHandling.Ignore)]
|
||||
public DeviceClass? device_class { get; set; }
|
||||
}
|
||||
}
|
30
OmniLinkBridge/MQTT/Climate.cs
Normal file
30
OmniLinkBridge/MQTT/Climate.cs
Normal file
|
@ -0,0 +1,30 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace OmniLinkBridge.MQTT
|
||||
{
|
||||
public class Climate : Device
|
||||
{
|
||||
public string current_temperature_topic { get; set; }
|
||||
|
||||
public string temperature_low_state_topic { get; set; }
|
||||
public string temperature_low_command_topic { get; set; }
|
||||
|
||||
public string temperature_high_state_topic { get; set; }
|
||||
public string temperature_high_command_topic { get; set; }
|
||||
|
||||
public string mode_state_topic { get; set; }
|
||||
public string mode_command_topic { get; set; }
|
||||
public List<string> modes { get; set; } = new List<string>(new string[] { "auto", "off", "cool", "heat" });
|
||||
|
||||
public string fan_mode_state_topic { get; set; }
|
||||
public string fan_mode_command_topic { get; set; }
|
||||
public List<string> fan_modes { get; set; } = new List<string>(new string[] { "auto", "on", "cycle" });
|
||||
|
||||
public string hold_state_topic { get; set; }
|
||||
public string hold_command_topic { get; set; }
|
||||
}
|
||||
}
|
17
OmniLinkBridge/MQTT/Device.cs
Normal file
17
OmniLinkBridge/MQTT/Device.cs
Normal file
|
@ -0,0 +1,17 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace OmniLinkBridge.MQTT
|
||||
{
|
||||
public class Device
|
||||
{
|
||||
public string name { get; set; }
|
||||
|
||||
public string state_topic { get; set; }
|
||||
|
||||
public string availability_topic { get; set; } = "omnilink/status";
|
||||
}
|
||||
}
|
19
OmniLinkBridge/MQTT/Light.cs
Normal file
19
OmniLinkBridge/MQTT/Light.cs
Normal file
|
@ -0,0 +1,19 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace OmniLinkBridge.MQTT
|
||||
{
|
||||
public class Light : Device
|
||||
{
|
||||
public string command_topic { get; set; }
|
||||
|
||||
public string brightness_state_topic { get; set; }
|
||||
|
||||
public string brightness_command_topic { get; set; }
|
||||
|
||||
public int brightness_scale { get; private set; } = 100;
|
||||
}
|
||||
}
|
223
OmniLinkBridge/MQTT/MappingExtensions.cs
Normal file
223
OmniLinkBridge/MQTT/MappingExtensions.cs
Normal file
|
@ -0,0 +1,223 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using HAI_Shared;
|
||||
|
||||
namespace OmniLinkBridge.MQTT
|
||||
{
|
||||
public static class MappingExtensions
|
||||
{
|
||||
public static string ToTopic(this clsArea area, Topic topic)
|
||||
{
|
||||
return $"omnilink/area{area.Number.ToString()}/{topic.ToString()}";
|
||||
}
|
||||
|
||||
public static Alarm ToConfig(this clsArea area)
|
||||
{
|
||||
Alarm ret = new Alarm();
|
||||
ret.name = area.Name;
|
||||
ret.state_topic = area.ToTopic(Topic.state);
|
||||
ret.command_topic = area.ToTopic(Topic.command);
|
||||
return ret;
|
||||
}
|
||||
|
||||
public static string ToState(this clsArea area)
|
||||
{
|
||||
if (area.AreaBurglaryAlarmText != "OK")
|
||||
return "triggered";
|
||||
else if (area.ExitTimer > 0)
|
||||
return "pending";
|
||||
|
||||
switch (area.AreaMode)
|
||||
{
|
||||
case enuSecurityMode.Night:
|
||||
case enuSecurityMode.NightDly:
|
||||
return "armed_night";
|
||||
case enuSecurityMode.Day:
|
||||
case enuSecurityMode.DayInst:
|
||||
return "armed_home";
|
||||
case enuSecurityMode.Away:
|
||||
case enuSecurityMode.Vacation:
|
||||
return "armed_away";
|
||||
case enuSecurityMode.Off:
|
||||
default:
|
||||
return "disarmed";
|
||||
}
|
||||
}
|
||||
|
||||
public static string ToTopic(this clsZone zone, Topic topic)
|
||||
{
|
||||
return $"omnilink/zone{zone.Number.ToString()}/{topic.ToString()}";
|
||||
}
|
||||
|
||||
public static Sensor ToConfigTemp(this clsZone zone)
|
||||
{
|
||||
Sensor ret = new Sensor();
|
||||
ret.name = zone.Name;
|
||||
ret.device_class = Sensor.DeviceClass.temperature;
|
||||
ret.state_topic = zone.ToTopic(Topic.state);
|
||||
ret.unit_of_measurement = "°F";
|
||||
return ret;
|
||||
}
|
||||
|
||||
public static Sensor ToConfigHumidity(this clsZone zone)
|
||||
{
|
||||
Sensor ret = new Sensor();
|
||||
ret.name = zone.Name;
|
||||
ret.device_class = Sensor.DeviceClass.humidity;
|
||||
ret.state_topic = zone.ToTopic(Topic.state);
|
||||
ret.unit_of_measurement = "%";
|
||||
return ret;
|
||||
}
|
||||
|
||||
public static BinarySensor ToConfig(this clsZone zone)
|
||||
{
|
||||
BinarySensor ret = new BinarySensor();
|
||||
ret.name = zone.Name;
|
||||
|
||||
Global.mqtt_discovery_override_zone.TryGetValue(zone.Number, out OverrideZone override_zone);
|
||||
|
||||
if (override_zone != null)
|
||||
{
|
||||
ret.device_class = override_zone.device_class;
|
||||
}
|
||||
else
|
||||
{
|
||||
switch (zone.ZoneType)
|
||||
{
|
||||
case enuZoneType.EntryExit:
|
||||
case enuZoneType.X2EntryDelay:
|
||||
case enuZoneType.X4EntryDelay:
|
||||
ret.device_class = BinarySensor.DeviceClass.door;
|
||||
break;
|
||||
case enuZoneType.Perimeter:
|
||||
ret.device_class = BinarySensor.DeviceClass.window;
|
||||
break;
|
||||
case enuZoneType.Tamper:
|
||||
ret.device_class = BinarySensor.DeviceClass.problem;
|
||||
break;
|
||||
case enuZoneType.AwayInt:
|
||||
case enuZoneType.NightInt:
|
||||
ret.device_class = BinarySensor.DeviceClass.motion;
|
||||
break;
|
||||
case enuZoneType.Water:
|
||||
ret.device_class = BinarySensor.DeviceClass.moisture;
|
||||
break;
|
||||
case enuZoneType.Fire:
|
||||
ret.device_class = BinarySensor.DeviceClass.smoke;
|
||||
break;
|
||||
case enuZoneType.Gas:
|
||||
ret.device_class = BinarySensor.DeviceClass.gas;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
ret.state_topic = zone.ToTopic(Topic.state);
|
||||
return ret;
|
||||
}
|
||||
|
||||
public static string ToState(this clsZone zone)
|
||||
{
|
||||
if (zone.IsTemperatureZone() || zone.IsHumidityZone())
|
||||
return zone.TempText();
|
||||
else
|
||||
return zone.Status.IsBitSet(0) ? "ON" : "OFF";
|
||||
}
|
||||
|
||||
public static string ToTopic(this clsUnit unit, Topic topic)
|
||||
{
|
||||
return $"omnilink/unit{unit.Number.ToString()}/{topic.ToString()}";
|
||||
}
|
||||
|
||||
public static Light ToConfig(this clsUnit unit)
|
||||
{
|
||||
Light ret = new Light();
|
||||
ret.name = unit.Name;
|
||||
ret.state_topic = unit.ToTopic(Topic.state);
|
||||
ret.command_topic = unit.ToTopic(Topic.command);
|
||||
ret.brightness_state_topic = unit.ToTopic(Topic.brightness_state);
|
||||
ret.brightness_command_topic = unit.ToTopic(Topic.brightness_command);
|
||||
return ret;
|
||||
}
|
||||
|
||||
public static Switch ToConfigSwitch(this clsUnit unit)
|
||||
{
|
||||
Switch ret = new Switch();
|
||||
ret.name = unit.Name;
|
||||
ret.state_topic = unit.ToTopic(Topic.state);
|
||||
ret.command_topic = unit.ToTopic(Topic.command);
|
||||
return ret;
|
||||
}
|
||||
|
||||
public static string ToState(this clsUnit unit)
|
||||
{
|
||||
return unit.Status == 0 || unit.Status == 100 ? "OFF" : "ON";
|
||||
}
|
||||
|
||||
public static int ToBrightnessState(this clsUnit unit)
|
||||
{
|
||||
if (unit.Status > 100)
|
||||
return (ushort)(unit.Status - 100);
|
||||
else if (unit.Status == 1)
|
||||
return 100;
|
||||
else
|
||||
return 0;
|
||||
}
|
||||
|
||||
public static string ToTopic(this clsThermostat thermostat, Topic topic)
|
||||
{
|
||||
return $"omnilink/thermostat{thermostat.Number.ToString()}/{topic.ToString()}";
|
||||
}
|
||||
|
||||
public static Climate ToConfig(this clsThermostat thermostat)
|
||||
{
|
||||
Climate ret = new Climate();
|
||||
ret.name = thermostat.Name;
|
||||
ret.current_temperature_topic = thermostat.ToTopic(Topic.current_temperature);
|
||||
|
||||
ret.temperature_low_state_topic = thermostat.ToTopic(Topic.temperature_heat_state);
|
||||
ret.temperature_low_command_topic = thermostat.ToTopic(Topic.temperature_heat_command);
|
||||
|
||||
ret.temperature_high_state_topic = thermostat.ToTopic(Topic.temperature_cool_state);
|
||||
ret.temperature_high_command_topic = thermostat.ToTopic(Topic.temperature_cool_command);
|
||||
|
||||
ret.mode_state_topic = thermostat.ToTopic(Topic.mode_state);
|
||||
ret.mode_command_topic = thermostat.ToTopic(Topic.mode_command);
|
||||
|
||||
ret.fan_mode_state_topic = thermostat.ToTopic(Topic.fan_mode_state);
|
||||
ret.fan_mode_command_topic = thermostat.ToTopic(Topic.fan_mode_command);
|
||||
|
||||
ret.hold_state_topic = thermostat.ToTopic(Topic.hold_state);
|
||||
ret.hold_command_topic = thermostat.ToTopic(Topic.hold_command);
|
||||
return ret;
|
||||
}
|
||||
|
||||
public static string ToOperationState(this clsThermostat thermostat)
|
||||
{
|
||||
string status = thermostat.HorC_StatusText();
|
||||
|
||||
if (status.Contains("COOLING"))
|
||||
return "cool";
|
||||
else if (status.Contains("HEATING"))
|
||||
return "heat";
|
||||
else
|
||||
return "idle";
|
||||
}
|
||||
|
||||
public static string ToTopic(this clsButton button, Topic topic)
|
||||
{
|
||||
return $"omnilink/button{button.Number.ToString()}/{topic.ToString()}";
|
||||
}
|
||||
|
||||
public static Switch ToConfig(this clsButton button)
|
||||
{
|
||||
Switch ret = new Switch();
|
||||
ret.name = button.Name;
|
||||
ret.state_topic = button.ToTopic(Topic.state);
|
||||
ret.command_topic = button.ToTopic(Topic.command);
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
}
|
13
OmniLinkBridge/MQTT/OverrideZone.cs
Normal file
13
OmniLinkBridge/MQTT/OverrideZone.cs
Normal file
|
@ -0,0 +1,13 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace OmniLinkBridge.MQTT
|
||||
{
|
||||
public class OverrideZone
|
||||
{
|
||||
public BinarySensor.DeviceClass device_class { get; set; }
|
||||
}
|
||||
}
|
26
OmniLinkBridge/MQTT/Sensor.cs
Normal file
26
OmniLinkBridge/MQTT/Sensor.cs
Normal file
|
@ -0,0 +1,26 @@
|
|||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Converters;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace OmniLinkBridge.MQTT
|
||||
{
|
||||
public class Sensor : Device
|
||||
{
|
||||
[JsonConverter(typeof(StringEnumConverter))]
|
||||
public enum DeviceClass
|
||||
{
|
||||
humidity,
|
||||
temperature
|
||||
}
|
||||
|
||||
[JsonProperty(NullValueHandling = NullValueHandling.Ignore)]
|
||||
public DeviceClass? device_class { get; set; }
|
||||
|
||||
[JsonProperty(NullValueHandling = NullValueHandling.Ignore)]
|
||||
public string unit_of_measurement { get; set; }
|
||||
}
|
||||
}
|
13
OmniLinkBridge/MQTT/Switch.cs
Normal file
13
OmniLinkBridge/MQTT/Switch.cs
Normal file
|
@ -0,0 +1,13 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace OmniLinkBridge.MQTT
|
||||
{
|
||||
public class Switch : Device
|
||||
{
|
||||
public string command_topic { get; set; }
|
||||
}
|
||||
}
|
56
OmniLinkBridge/MQTT/Topics.cs
Normal file
56
OmniLinkBridge/MQTT/Topics.cs
Normal file
|
@ -0,0 +1,56 @@
|
|||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Converters;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace OmniLinkBridge.MQTT
|
||||
{
|
||||
public class Topic
|
||||
{
|
||||
public string Value { get; private set; }
|
||||
|
||||
private Topic(string value)
|
||||
{
|
||||
Value = value;
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return Value;
|
||||
}
|
||||
|
||||
public static Topic state { get { return new Topic("state"); } }
|
||||
public static Topic command { get { return new Topic("command"); } }
|
||||
|
||||
public static Topic brightness_state { get { return new Topic("brightness_state"); } }
|
||||
public static Topic brightness_command { get { return new Topic("brightness_command"); } }
|
||||
|
||||
public static Topic current_operation { get { return new Topic("current_operation"); } }
|
||||
public static Topic current_temperature { get { return new Topic("current_temperature"); } }
|
||||
public static Topic current_humidity { get { return new Topic("current_humidity"); } }
|
||||
|
||||
public static Topic temperature_heat_state { get { return new Topic("temperature_heat_state"); } }
|
||||
public static Topic temperature_heat_command { get { return new Topic("temperature_heat_command"); } }
|
||||
|
||||
public static Topic temperature_cool_state { get { return new Topic("temperature_cool_state"); } }
|
||||
public static Topic temperature_cool_command { get { return new Topic("temperature_cool_command"); } }
|
||||
|
||||
public static Topic humidify_state { get { return new Topic("humidify_state"); } }
|
||||
public static Topic humidify_command { get { return new Topic("humidify_command"); } }
|
||||
|
||||
public static Topic dehumidify_state { get { return new Topic("dehumidify_state"); } }
|
||||
public static Topic dehumidify_command { get { return new Topic("dehumidify_command"); } }
|
||||
|
||||
public static Topic mode_state { get { return new Topic("mode_state"); } }
|
||||
public static Topic mode_command { get { return new Topic("mode_command"); } }
|
||||
|
||||
public static Topic fan_mode_state { get { return new Topic("fan_mode_state"); } }
|
||||
public static Topic fan_mode_command { get { return new Topic("fan_mode_command"); } }
|
||||
|
||||
public static Topic hold_state { get { return new Topic("hold_state"); } }
|
||||
public static Topic hold_command { get { return new Topic("hold_command"); } }
|
||||
}
|
||||
}
|
8
OmniLinkBridge/Modules/IModule.cs
Normal file
8
OmniLinkBridge/Modules/IModule.cs
Normal file
|
@ -0,0 +1,8 @@
|
|||
namespace OmniLinkBridge.Modules
|
||||
{
|
||||
interface IModule
|
||||
{
|
||||
void Startup();
|
||||
void Shutdown();
|
||||
}
|
||||
}
|
344
OmniLinkBridge/Modules/LoggerModule.cs
Normal file
344
OmniLinkBridge/Modules/LoggerModule.cs
Normal file
|
@ -0,0 +1,344 @@
|
|||
using OmniLinkBridge.Notifications;
|
||||
using OmniLinkBridge.OmniLink;
|
||||
using log4net;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Data;
|
||||
using System.Data.Odbc;
|
||||
using System.Reflection;
|
||||
using System.Threading;
|
||||
|
||||
namespace OmniLinkBridge.Modules
|
||||
{
|
||||
public class LoggerModule : IModule
|
||||
{
|
||||
private static ILog log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);
|
||||
|
||||
private OmniLinkII omnilink;
|
||||
private List<string> alarms = new List<string>();
|
||||
|
||||
// mySQL Database
|
||||
private OdbcConnection mysql_conn = null;
|
||||
private DateTime mysql_retry = DateTime.MinValue;
|
||||
private OdbcCommand mysql_command = null;
|
||||
private Queue<string> mysql_queue = new Queue<string>();
|
||||
private object mysql_lock = new object();
|
||||
|
||||
private readonly AutoResetEvent trigger = new AutoResetEvent(false);
|
||||
|
||||
public LoggerModule(OmniLinkII omni)
|
||||
{
|
||||
omnilink = omni;
|
||||
omnilink.OnAreaStatus += Omnilink_OnAreaStatus;
|
||||
omnilink.OnZoneStatus += Omnilink_OnZoneStatus;
|
||||
omnilink.OnThermostatStatus += Omnilink_OnThermostatStatus;
|
||||
omnilink.OnUnitStatus += Omnilink_OnUnitStatus;
|
||||
omnilink.OnMessageStatus += Omnilink_OnMessageStatus;
|
||||
omnilink.OnSystemStatus += Omnilink_OnSystemStatus;
|
||||
}
|
||||
|
||||
public void Startup()
|
||||
{
|
||||
if (Global.mysql_logging)
|
||||
{
|
||||
log.Info("Connecting to database");
|
||||
|
||||
mysql_conn = new OdbcConnection(Global.mysql_connection);
|
||||
|
||||
// Must make an initial connection
|
||||
if (!DBOpen())
|
||||
Environment.Exit(1);
|
||||
}
|
||||
|
||||
while (true)
|
||||
{
|
||||
// End gracefully when not logging or database queue empty
|
||||
if (!Global.running && (!Global.mysql_logging || DBQueueCount() == 0))
|
||||
break;
|
||||
|
||||
// Make sure database connection is active
|
||||
if (Global.mysql_logging && mysql_conn.State != ConnectionState.Open)
|
||||
{
|
||||
// Nothing we can do if shutting down
|
||||
if (!Global.running)
|
||||
break;
|
||||
|
||||
if (mysql_retry < DateTime.Now)
|
||||
DBOpen();
|
||||
|
||||
if (mysql_conn.State != ConnectionState.Open)
|
||||
{
|
||||
// Loop to prevent database queries from executing
|
||||
trigger.WaitOne(new TimeSpan(0, 0, 1));
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
// Sleep when not logging or database queue empty
|
||||
if (!Global.mysql_logging || DBQueueCount() == 0)
|
||||
{
|
||||
trigger.WaitOne(new TimeSpan(0, 0, 1));
|
||||
continue;
|
||||
}
|
||||
|
||||
// Grab a copy in case the database query fails
|
||||
string query;
|
||||
lock (mysql_lock)
|
||||
query = mysql_queue.Peek();
|
||||
|
||||
try
|
||||
{
|
||||
// Execute the database query
|
||||
mysql_command = new OdbcCommand(query, mysql_conn);
|
||||
mysql_command.ExecuteNonQuery();
|
||||
|
||||
// Successful remove query from queue
|
||||
lock (mysql_lock)
|
||||
mysql_queue.Dequeue();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
if (mysql_conn.State != ConnectionState.Open)
|
||||
{
|
||||
log.Warn("Lost connection to database");
|
||||
}
|
||||
else
|
||||
{
|
||||
log.Error("Error executing query\r\n" + query, ex);
|
||||
|
||||
// Prevent an endless loop from failed query
|
||||
lock (mysql_lock)
|
||||
mysql_queue.Dequeue();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (Global.mysql_logging)
|
||||
DBClose();
|
||||
}
|
||||
|
||||
public void Shutdown()
|
||||
{
|
||||
trigger.Set();
|
||||
}
|
||||
|
||||
private void Omnilink_OnAreaStatus(object sender, AreaStatusEventArgs e)
|
||||
{
|
||||
// Alarm notifcation
|
||||
if (e.Area.AreaFireAlarmText != "OK")
|
||||
{
|
||||
Notification.Notify("ALARM", "FIRE " + e.Area.Name + " " + e.Area.AreaFireAlarmText, NotificationPriority.Emergency);
|
||||
|
||||
if (!alarms.Contains("FIRE" + e.ID))
|
||||
alarms.Add("FIRE" + e.ID);
|
||||
}
|
||||
else if (alarms.Contains("FIRE" + e.ID))
|
||||
{
|
||||
Notification.Notify("ALARM CLEARED", "FIRE " + e.Area.Name + " " + e.Area.AreaFireAlarmText, NotificationPriority.High);
|
||||
|
||||
alarms.Remove("FIRE" + e.ID);
|
||||
}
|
||||
|
||||
if (e.Area.AreaBurglaryAlarmText != "OK")
|
||||
{
|
||||
Notification.Notify("ALARM", "BURGLARY " + e.Area.Name + " " + e.Area.AreaBurglaryAlarmText, NotificationPriority.Emergency);
|
||||
|
||||
if (!alarms.Contains("BURGLARY" + e.ID))
|
||||
alarms.Add("BURGLARY" + e.ID);
|
||||
}
|
||||
else if (alarms.Contains("BURGLARY" + e.ID))
|
||||
{
|
||||
Notification.Notify("ALARM CLEARED", "BURGLARY " + e.Area.Name + " " + e.Area.AreaBurglaryAlarmText, NotificationPriority.High);
|
||||
|
||||
alarms.Remove("BURGLARY" + e.ID);
|
||||
}
|
||||
|
||||
if (e.Area.AreaAuxAlarmText != "OK")
|
||||
{
|
||||
Notification.Notify("ALARM", "AUX " + e.Area.Name + " " + e.Area.AreaAuxAlarmText, NotificationPriority.Emergency);
|
||||
|
||||
if (!alarms.Contains("AUX" + e.ID))
|
||||
alarms.Add("AUX" + e.ID);
|
||||
}
|
||||
else if (alarms.Contains("AUX" + e.ID))
|
||||
{
|
||||
Notification.Notify("ALARM CLEARED", "AUX " + e.Area.Name + " " + e.Area.AreaAuxAlarmText, NotificationPriority.High);
|
||||
|
||||
alarms.Remove("AUX" + e.ID);
|
||||
}
|
||||
|
||||
if (e.Area.AreaDuressAlarmText != "OK")
|
||||
{
|
||||
Notification.Notify("ALARM", "DURESS " + e.Area.Name + " " + e.Area.AreaDuressAlarmText, NotificationPriority.Emergency);
|
||||
|
||||
if (!alarms.Contains("DURESS" + e.ID))
|
||||
alarms.Add("DURESS" + e.ID);
|
||||
}
|
||||
else if (alarms.Contains("DURESS" + e.ID))
|
||||
{
|
||||
Notification.Notify("ALARM CLEARED", "DURESS " + e.Area.Name + " " + e.Area.AreaDuressAlarmText, NotificationPriority.High);
|
||||
|
||||
alarms.Remove("DURESS" + e.ID);
|
||||
}
|
||||
|
||||
string status = e.Area.ModeText();
|
||||
|
||||
if (e.Area.ExitTimer > 0)
|
||||
status = "ARMING " + status;
|
||||
|
||||
if (e.Area.EntryTimer > 0)
|
||||
status = "TRIPPED " + status;
|
||||
|
||||
DBQueue(@"
|
||||
INSERT INTO log_areas (timestamp, e.AreaID, name,
|
||||
fire, police, auxiliary,
|
||||
duress, security)
|
||||
VALUES ('" + DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss") + "','" + e.ID.ToString() + "','" + e.Area.Name + "','" +
|
||||
e.Area.AreaFireAlarmText + "','" + e.Area.AreaBurglaryAlarmText + "','" + e.Area.AreaAuxAlarmText + "','" +
|
||||
e.Area.AreaDuressAlarmText + "','" + status + "')");
|
||||
|
||||
if (Global.verbose_area)
|
||||
log.Debug("AreaStatus " + e.ID + " " + e.Area.Name + ", Status: " + status);
|
||||
|
||||
if (Global.notify_area && e.Area.LastMode != e.Area.AreaMode)
|
||||
Notification.Notify("Security", e.Area.Name + " " + e.Area.ModeText());
|
||||
}
|
||||
|
||||
private void Omnilink_OnZoneStatus(object sender, ZoneStatusEventArgs e)
|
||||
{
|
||||
DBQueue(@"
|
||||
INSERT INTO log_zones (timestamp, id, name, status)
|
||||
VALUES ('" + DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss") + "','" + e.ID + "','" + e.Zone.Name + "','" + e.Zone.StatusText() + "')");
|
||||
|
||||
if (Global.verbose_zone)
|
||||
{
|
||||
if (e.Zone.IsTemperatureZone())
|
||||
log.Debug("ZoneStatus " + e.ID + " " + e.Zone.Name + ", Temp: " + e.Zone.TempText());
|
||||
else
|
||||
log.Debug("ZoneStatus" + e.ID + " " + e.Zone.Name + ", Status: " + e.Zone.StatusText());
|
||||
}
|
||||
}
|
||||
|
||||
private void Omnilink_OnThermostatStatus(object sender, ThermostatStatusEventArgs e)
|
||||
{
|
||||
if (e.EventTimer)
|
||||
return;
|
||||
|
||||
int temp, heat, cool, humidity, humidify, dehumidify;
|
||||
|
||||
Int32.TryParse(e.Thermostat.TempText(), out temp);
|
||||
Int32.TryParse(e.Thermostat.HeatSetpointText(), out heat);
|
||||
Int32.TryParse(e.Thermostat.CoolSetpointText(), out cool);
|
||||
Int32.TryParse(e.Thermostat.HumidityText(), out humidity);
|
||||
Int32.TryParse(e.Thermostat.HumidifySetpointText(), out humidify);
|
||||
Int32.TryParse(e.Thermostat.DehumidifySetpointText(), out dehumidify);
|
||||
|
||||
DBQueue(@"
|
||||
INSERT INTO log_thermostats (timestamp, id, name,
|
||||
status, temp, heat, cool,
|
||||
humidity, humidify, dehumidify,
|
||||
mode, fan, hold)
|
||||
VALUES ('" + DateTime.Now.ToString("yyyy-MM-dd HH:mm") + "','" + e.ID + "','" + e.Thermostat.Name + "','" +
|
||||
e.Thermostat.HorC_StatusText() + "','" + temp.ToString() + "','" + heat + "','" + cool + "','" +
|
||||
humidity + "','" + humidify + "','" + dehumidify + "','" +
|
||||
e.Thermostat.ModeText() + "','" + e.Thermostat.FanModeText() + "','" + e.Thermostat.HoldStatusText() + "')");
|
||||
|
||||
if (Global.verbose_thermostat)
|
||||
log.Debug("ThermostatStatus " + e.ID + " " + e.Thermostat.Name +
|
||||
", Status: " + e.Thermostat.TempText() + " " + e.Thermostat.HorC_StatusText() +
|
||||
", Heat: " + e.Thermostat.HeatSetpointText() +
|
||||
", Cool: " + e.Thermostat.CoolSetpointText() +
|
||||
", Mode: " + e.Thermostat.ModeText() +
|
||||
", Fan: " + e.Thermostat.FanModeText() +
|
||||
", Hold: " + e.Thermostat.HoldStatusText());
|
||||
}
|
||||
|
||||
private void Omnilink_OnUnitStatus(object sender, UnitStatusEventArgs e)
|
||||
{
|
||||
string status = e.Unit.StatusText;
|
||||
|
||||
if (e.Unit.Status == 100 && e.Unit.StatusTime == 0)
|
||||
status = "OFF";
|
||||
else if (e.Unit.Status == 200 && e.Unit.StatusTime == 0)
|
||||
status = "ON";
|
||||
|
||||
DBQueue(@"
|
||||
INSERT INTO log_e.Units (timestamp, id, name,
|
||||
status, statusvalue, statustime)
|
||||
VALUES ('" + DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss") + "','" + e.ID + "','" + e.Unit.Name + "','" +
|
||||
status + "','" + e.Unit.Status + "','" + e.Unit.StatusTime + "')");
|
||||
|
||||
if (Global.verbose_unit)
|
||||
log.Debug("UnitStatus " + e.ID + " " + e.Unit.Name + ", Status: " + status);
|
||||
}
|
||||
|
||||
private void Omnilink_OnMessageStatus(object sender, MessageStatusEventArgs e)
|
||||
{
|
||||
DBQueue(@"
|
||||
INSERT INTO log_messages (timestamp, id, name, status)
|
||||
VALUES ('" + DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss") + "','" + e.ID + "','" + e.Message.Name + "','" + e.Message.StatusText() + "')");
|
||||
|
||||
if (Global.verbose_message)
|
||||
log.Debug("MessageStatus " + e.Message.Name + ", " + e.Message.StatusText());
|
||||
|
||||
if (Global.notify_message)
|
||||
Notification.Notify("Message", e.ID + " " + e.Message.Name + ", " + e.Message.StatusText());
|
||||
}
|
||||
|
||||
private void Omnilink_OnSystemStatus(object sender, SystemStatusEventArgs e)
|
||||
{
|
||||
DBQueue(@"
|
||||
INSERT INTO log_events (timestamp, name, status)
|
||||
VALUES ('" + DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss") + "','" + e.Type.ToString() + "','" + e.Value + "')");
|
||||
|
||||
if (Global.verbose_event)
|
||||
log.Debug("SystemEvent " + e.Type.ToString() + " " + e.Value);
|
||||
|
||||
if (e.SendNotification)
|
||||
Notification.Notify("SystemEvent", e.Type.ToString() + " " + e.Value);
|
||||
}
|
||||
|
||||
public bool DBOpen()
|
||||
{
|
||||
try
|
||||
{
|
||||
if (mysql_conn.State != ConnectionState.Open)
|
||||
mysql_conn.Open();
|
||||
|
||||
mysql_retry = DateTime.MinValue;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
log.Error("Failed to connect to database", ex);
|
||||
mysql_retry = DateTime.Now.AddMinutes(1);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public void DBClose()
|
||||
{
|
||||
if (mysql_conn.State != ConnectionState.Closed)
|
||||
mysql_conn.Close();
|
||||
}
|
||||
|
||||
public void DBQueue(string query)
|
||||
{
|
||||
if (!Global.mysql_logging)
|
||||
return;
|
||||
|
||||
lock (mysql_lock)
|
||||
mysql_queue.Enqueue(query);
|
||||
}
|
||||
|
||||
private int DBQueueCount()
|
||||
{
|
||||
int count;
|
||||
lock (mysql_lock)
|
||||
count = mysql_queue.Count;
|
||||
|
||||
return count;
|
||||
}
|
||||
}
|
||||
}
|
422
OmniLinkBridge/Modules/MQTTModule.cs
Normal file
422
OmniLinkBridge/Modules/MQTTModule.cs
Normal file
|
@ -0,0 +1,422 @@
|
|||
using HAI_Shared;
|
||||
using OmniLinkBridge.OmniLink;
|
||||
using log4net;
|
||||
using MQTTnet;
|
||||
using MQTTnet.Client;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Reflection;
|
||||
using System.Threading;
|
||||
using Newtonsoft.Json;
|
||||
using MQTTnet.Extensions.ManagedClient;
|
||||
using OmniLinkBridge.MQTT;
|
||||
using MQTTnet.Protocol;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Text;
|
||||
|
||||
namespace OmniLinkBridge.Modules
|
||||
{
|
||||
public class MQTTModule : IModule
|
||||
{
|
||||
private static ILog log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);
|
||||
|
||||
private OmniLinkII OmniLink { get; set; }
|
||||
private IManagedMqttClient MqttClient { get; set; }
|
||||
|
||||
private Regex regexTopic = new Regex("omnilink/([A-Za-z]+)([0-9]+)/(.*)", RegexOptions.Compiled);
|
||||
|
||||
private readonly AutoResetEvent trigger = new AutoResetEvent(false);
|
||||
|
||||
public MQTTModule(OmniLinkII omni)
|
||||
{
|
||||
OmniLink = omni;
|
||||
OmniLink.OnConnect += OmniLink_OnConnect;
|
||||
OmniLink.OnAreaStatus += Omnilink_OnAreaStatus;
|
||||
OmniLink.OnZoneStatus += Omnilink_OnZoneStatus;
|
||||
OmniLink.OnUnitStatus += Omnilink_OnUnitStatus;
|
||||
OmniLink.OnThermostatStatus += Omnilink_OnThermostatStatus;
|
||||
}
|
||||
|
||||
public void Startup()
|
||||
{
|
||||
MqttClientOptionsBuilder options = new MqttClientOptionsBuilder()
|
||||
.WithTcpServer(Global.mqtt_server);
|
||||
|
||||
if (!string.IsNullOrEmpty(Global.mqtt_username))
|
||||
options = options
|
||||
.WithCredentials(Global.mqtt_username, Global.mqtt_password);
|
||||
|
||||
ManagedMqttClientOptions manoptions = new ManagedMqttClientOptionsBuilder()
|
||||
.WithAutoReconnectDelay(TimeSpan.FromSeconds(5))
|
||||
.WithClientOptions(options.Build())
|
||||
.Build();
|
||||
|
||||
MqttClient = new MqttFactory().CreateManagedMqttClient();
|
||||
MqttClient.Connected += (sender, e) => { log.Debug("Connected"); };
|
||||
MqttClient.ConnectingFailed += (sender, e) => { log.Debug("Error " + e.Exception.Message); };
|
||||
|
||||
MqttClient.StartAsync(manoptions);
|
||||
|
||||
MqttClient.ApplicationMessageReceived += MqttClient_ApplicationMessageReceived;
|
||||
|
||||
MqttClient.SubscribeAsync(new TopicFilterBuilder().WithTopic("omnilink/+/" + Topic.command).Build());
|
||||
MqttClient.SubscribeAsync(new TopicFilterBuilder().WithTopic("omnilink/+/" + Topic.brightness_command).Build());
|
||||
MqttClient.SubscribeAsync(new TopicFilterBuilder().WithTopic("omnilink/+/" + Topic.temperature_heat_command).Build());
|
||||
MqttClient.SubscribeAsync(new TopicFilterBuilder().WithTopic("omnilink/+/" + Topic.temperature_cool_command).Build());
|
||||
MqttClient.SubscribeAsync(new TopicFilterBuilder().WithTopic("omnilink/+/" + Topic.humidify_command).Build());
|
||||
MqttClient.SubscribeAsync(new TopicFilterBuilder().WithTopic("omnilink/+/" + Topic.dehumidify_command).Build());
|
||||
MqttClient.SubscribeAsync(new TopicFilterBuilder().WithTopic("omnilink/+/" + Topic.mode_command).Build());
|
||||
MqttClient.SubscribeAsync(new TopicFilterBuilder().WithTopic("omnilink/+/" + Topic.fan_mode_command).Build());
|
||||
MqttClient.SubscribeAsync(new TopicFilterBuilder().WithTopic("omnilink/+/" + Topic.hold_command).Build());
|
||||
|
||||
// Wait until shutdown
|
||||
trigger.WaitOne();
|
||||
|
||||
MqttClient.PublishAsync("omnilink/status", "offline", MqttQualityOfServiceLevel.AtMostOnce, true);
|
||||
}
|
||||
|
||||
private void MqttClient_ApplicationMessageReceived(object sender, MqttApplicationMessageReceivedEventArgs e)
|
||||
{
|
||||
Match match = regexTopic.Match(e.ApplicationMessage.Topic);
|
||||
|
||||
if (!match.Success)
|
||||
return;
|
||||
|
||||
string payload = Encoding.UTF8.GetString(e.ApplicationMessage.Payload);
|
||||
|
||||
log.Debug($"Received: Type: {match.Groups[1].Value}, Id: {match.Groups[2].Value}, Command: {match.Groups[3].Value}, Value: {payload}");
|
||||
|
||||
if (match.Groups[1].Value == "area" && ushort.TryParse(match.Groups[2].Value, out ushort areaId) && areaId < OmniLink.Controller.Areas.Count)
|
||||
{
|
||||
ProcessAreaReceived(OmniLink.Controller.Areas[areaId], match.Groups[3].Value, payload);
|
||||
}
|
||||
else if (match.Groups[1].Value == "unit" && ushort.TryParse(match.Groups[2].Value, out ushort unitId) && unitId < OmniLink.Controller.Units.Count)
|
||||
{
|
||||
ProcessUnitReceived(OmniLink.Controller.Units[unitId], match.Groups[3].Value, payload);
|
||||
}
|
||||
else if (match.Groups[1].Value == "thermostat" && ushort.TryParse(match.Groups[2].Value, out ushort thermostatId) && thermostatId < OmniLink.Controller.Thermostats.Count)
|
||||
{
|
||||
ProcessThermostatReceived(OmniLink.Controller.Thermostats[thermostatId], match.Groups[3].Value, payload);
|
||||
}
|
||||
else if (match.Groups[1].Value == "button" && ushort.TryParse(match.Groups[2].Value, out ushort buttonId) && buttonId < OmniLink.Controller.Buttons.Count)
|
||||
{
|
||||
ProcessButtonReceived(OmniLink.Controller.Buttons[buttonId], match.Groups[3].Value, payload);
|
||||
}
|
||||
}
|
||||
|
||||
private void ProcessAreaReceived(clsArea area, string command, string payload)
|
||||
{
|
||||
if (string.Compare(command, Topic.command.ToString()) == 0)
|
||||
{
|
||||
switch(payload)
|
||||
{
|
||||
case "ARM_HOME":
|
||||
log.Debug("SetArea: " + area.Number + " to home");
|
||||
OmniLink.Controller.SendCommand(enuUnitCommand.SecurityDay, 0, (ushort)area.Number);
|
||||
break;
|
||||
case "ARM_AWAY":
|
||||
log.Debug("SetArea: " + area.Number + " to away");
|
||||
OmniLink.Controller.SendCommand(enuUnitCommand.SecurityAway, 0, (ushort)area.Number);
|
||||
break;
|
||||
case "ARM_NIGHT":
|
||||
log.Debug("SetArea: " + area.Number + " to night");
|
||||
OmniLink.Controller.SendCommand(enuUnitCommand.SecurityNight, 0, (ushort)area.Number);
|
||||
break;
|
||||
case "DISARM":
|
||||
log.Debug("SetArea: " + area.Number + " to disarm");
|
||||
OmniLink.Controller.SendCommand(enuUnitCommand.SecurityOff, 0, (ushort)area.Number);
|
||||
break;
|
||||
|
||||
// The below aren't supported by Home Assistant
|
||||
case "ARM_HOME_INSTANT":
|
||||
log.Debug("SetArea: " + area.Number + " to home instant");
|
||||
OmniLink.Controller.SendCommand(enuUnitCommand.SecurityDyi, 0, (ushort)area.Number);
|
||||
break;
|
||||
case "ARM_NIGHT_DELAY":
|
||||
log.Debug("SetArea: " + area.Number + " to night delay");
|
||||
OmniLink.Controller.SendCommand(enuUnitCommand.SecurityNtd, 0, (ushort)area.Number);
|
||||
break;
|
||||
case "ARM_VACATION":
|
||||
log.Debug("SetArea: " + area.Number + " to vacation");
|
||||
OmniLink.Controller.SendCommand(enuUnitCommand.SecurityVac, 0, (ushort)area.Number);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void ProcessUnitReceived(clsUnit unit, string command, string payload)
|
||||
{
|
||||
if (string.Compare(command, Topic.command.ToString()) == 0 && (payload == "ON" || payload == "OFF"))
|
||||
{
|
||||
if (unit.ToState() != payload)
|
||||
{
|
||||
log.Debug("SetUnit: " + unit.Number + " to " + payload);
|
||||
|
||||
if (payload == "ON")
|
||||
OmniLink.Controller.SendCommand(enuUnitCommand.On, 0, (ushort)unit.Number);
|
||||
else
|
||||
OmniLink.Controller.SendCommand(enuUnitCommand.Off, 0, (ushort)unit.Number);
|
||||
}
|
||||
}
|
||||
else if (string.Compare(command, Topic.brightness_command.ToString()) == 0 && Int32.TryParse(payload, out int unitValue))
|
||||
{
|
||||
log.Debug("SetUnit: " + unit.Number + " to " + payload + "%");
|
||||
|
||||
OmniLink.Controller.SendCommand(enuUnitCommand.Level, BitConverter.GetBytes(unitValue)[0], (ushort)unit.Number);
|
||||
|
||||
// Force status change instead of waiting on controller to update
|
||||
// Home Assistant sends brightness immediately followed by ON,
|
||||
// which will cause light to go to 100% brightness
|
||||
unit.Status = (byte)(100 + unitValue);
|
||||
}
|
||||
}
|
||||
|
||||
private void ProcessThermostatReceived(clsThermostat thermostat, string command, string payload)
|
||||
{
|
||||
if (string.Compare(command, Topic.temperature_heat_command.ToString()) == 0 && double.TryParse(payload, out double tempLow))
|
||||
{
|
||||
int temp = tempLow.ToCelsius().ToOmniTemp();
|
||||
log.Debug("SetThermostatHeatSetpoint: " + thermostat.Number + " to " + payload + "F (" + temp + ")");
|
||||
OmniLink.Controller.SendCommand(enuUnitCommand.SetLowSetPt, BitConverter.GetBytes(temp)[0], (ushort)thermostat.Number);
|
||||
}
|
||||
else if (string.Compare(command, Topic.temperature_cool_command.ToString()) == 0 && double.TryParse(payload, out double tempHigh))
|
||||
{
|
||||
int temp = tempHigh.ToCelsius().ToOmniTemp();
|
||||
log.Debug("SetThermostatCoolSetpoint: " + thermostat.Number + " to " + payload + "F (" + temp + ")");
|
||||
OmniLink.Controller.SendCommand(enuUnitCommand.SetHighSetPt, BitConverter.GetBytes(temp)[0], (ushort)thermostat.Number);
|
||||
}
|
||||
else if (string.Compare(command, Topic.humidify_command.ToString()) == 0 && double.TryParse(payload, out double humidify))
|
||||
{
|
||||
int level = humidify.ToCelsius().ToOmniTemp();
|
||||
log.Debug("SetThermostatHumidifySetpoint: " + thermostat.Number + " to " + payload + "% (" + level + ")");
|
||||
OmniLink.Controller.SendCommand(enuUnitCommand.SetHumidifySetPt, BitConverter.GetBytes(level)[0], (ushort)thermostat.Number);
|
||||
}
|
||||
else if (string.Compare(command, Topic.dehumidify_command.ToString()) == 0 && double.TryParse(payload, out double dehumidify))
|
||||
{
|
||||
int level = dehumidify.ToCelsius().ToOmniTemp();
|
||||
log.Debug("SetThermostatDehumidifySetpoint: " + thermostat.Number + " to " + payload + "% (" + level + ")");
|
||||
OmniLink.Controller.SendCommand(enuUnitCommand.SetDeHumidifySetPt, BitConverter.GetBytes(level)[0], (ushort)thermostat.Number);
|
||||
}
|
||||
else if (string.Compare(command, Topic.mode_command.ToString()) == 0 && Enum.TryParse(payload, true, out enuThermostatMode mode))
|
||||
{
|
||||
log.Debug("SetThermostatMode: " + thermostat.Number + " to " + payload);
|
||||
OmniLink.Controller.SendCommand(enuUnitCommand.Mode, BitConverter.GetBytes((int)mode)[0], (ushort)thermostat.Number);
|
||||
}
|
||||
else if (string.Compare(command, Topic.fan_mode_command.ToString()) == 0 && Enum.TryParse(payload, true, out enuThermostatFanMode fanMode))
|
||||
{
|
||||
log.Debug("SetThermostatFanMode: " + thermostat.Number + " to " + payload);
|
||||
OmniLink.Controller.SendCommand(enuUnitCommand.Fan, BitConverter.GetBytes((int)fanMode)[0], (ushort)thermostat.Number);
|
||||
}
|
||||
else if (string.Compare(command, Topic.hold_command.ToString()) == 0 && Enum.TryParse(payload, true, out enuThermostatHoldMode holdMode))
|
||||
{
|
||||
log.Debug("SetThermostatHold: " + thermostat.Number + " to " + payload);
|
||||
OmniLink.Controller.SendCommand(enuUnitCommand.Hold, BitConverter.GetBytes((int)holdMode)[0], (ushort)thermostat.Number);
|
||||
}
|
||||
}
|
||||
|
||||
private void ProcessButtonReceived(clsButton button, string command, string payload)
|
||||
{
|
||||
if (string.Compare(command, Topic.command.ToString()) == 0 && payload == "ON")
|
||||
{
|
||||
log.Debug("PushButton: " + button.Number);
|
||||
OmniLink.Controller.SendCommand(enuUnitCommand.Button, 0, (ushort)button.Number);
|
||||
}
|
||||
}
|
||||
|
||||
public void Shutdown()
|
||||
{
|
||||
trigger.Set();
|
||||
}
|
||||
|
||||
private void OmniLink_OnConnect(object sender, EventArgs e)
|
||||
{
|
||||
PublishConfig();
|
||||
|
||||
MqttClient.PublishAsync("omnilink/status", "online", MqttQualityOfServiceLevel.AtMostOnce, true);
|
||||
}
|
||||
|
||||
private void PublishConfig()
|
||||
{
|
||||
PublishAreas();
|
||||
PublishZones();
|
||||
PublishUnits();
|
||||
PublishThermostats();
|
||||
PublishButtons();
|
||||
}
|
||||
|
||||
private void PublishAreas()
|
||||
{
|
||||
log.Debug("Publishing areas");
|
||||
|
||||
for (ushort i = 1; i < OmniLink.Controller.Areas.Count; i++)
|
||||
{
|
||||
clsArea area = OmniLink.Controller.Areas[i];
|
||||
|
||||
if (area.DefaultProperties == true)
|
||||
{
|
||||
MqttClient.PublishAsync($"{Global.mqtt_discovery_prefix}/alarm_control_panel/omnilink/area{i.ToString()}/config", null, MqttQualityOfServiceLevel.AtMostOnce, true);
|
||||
continue;
|
||||
}
|
||||
|
||||
PublishAreaState(area);
|
||||
|
||||
MqttClient.PublishAsync($"{Global.mqtt_discovery_prefix}/alarm_control_panel/omnilink/area{i.ToString()}/config",
|
||||
JsonConvert.SerializeObject(area.ToConfig()), MqttQualityOfServiceLevel.AtMostOnce, true);
|
||||
}
|
||||
}
|
||||
|
||||
private void PublishZones()
|
||||
{
|
||||
log.Debug("Publishing zones");
|
||||
|
||||
for (ushort i = 1; i < OmniLink.Controller.Zones.Count; i++)
|
||||
{
|
||||
clsZone zone = OmniLink.Controller.Zones[i];
|
||||
|
||||
if (zone.DefaultProperties == true || Global.mqtt_discovery_ignore_zones.Contains(zone.Number))
|
||||
{
|
||||
MqttClient.PublishAsync($"{Global.mqtt_discovery_prefix}/sensor/omnilink/zone{i.ToString()}/config", null, MqttQualityOfServiceLevel.AtMostOnce, true);
|
||||
MqttClient.PublishAsync($"{Global.mqtt_discovery_prefix}/binary_sensor/omnilink/zone{i.ToString()}/config", null, MqttQualityOfServiceLevel.AtMostOnce, true);
|
||||
continue;
|
||||
}
|
||||
|
||||
PublishZoneState(zone);
|
||||
|
||||
if (zone.IsTemperatureZone())
|
||||
{
|
||||
MqttClient.PublishAsync($"{Global.mqtt_discovery_prefix}/sensor/omnilink/zone{i.ToString()}/config",
|
||||
JsonConvert.SerializeObject(zone.ToConfigTemp()), MqttQualityOfServiceLevel.AtMostOnce, true);
|
||||
}
|
||||
else if (zone.IsHumidityZone())
|
||||
{
|
||||
MqttClient.PublishAsync($"{Global.mqtt_discovery_prefix}/sensor/omnilink/zone{i.ToString()}/config",
|
||||
JsonConvert.SerializeObject(zone.ToConfigHumidity()), MqttQualityOfServiceLevel.AtMostOnce, true);
|
||||
}
|
||||
else
|
||||
{
|
||||
MqttClient.PublishAsync($"{Global.mqtt_discovery_prefix}/binary_sensor/omnilink/zone{i.ToString()}/config",
|
||||
JsonConvert.SerializeObject(zone.ToConfig()), MqttQualityOfServiceLevel.AtMostOnce, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void PublishUnits()
|
||||
{
|
||||
log.Debug("Publishing units");
|
||||
|
||||
for (ushort i = 1; i < OmniLink.Controller.Units.Count; i++)
|
||||
{
|
||||
string type = i < 385 ? "light" : "switch";
|
||||
|
||||
clsUnit unit = OmniLink.Controller.Units[i];
|
||||
|
||||
if (unit.DefaultProperties == true || Global.mqtt_discovery_ignore_units.Contains(unit.Number))
|
||||
{
|
||||
MqttClient.PublishAsync($"{Global.mqtt_discovery_prefix}/{type}/omnilink/unit{i.ToString()}/config", null, MqttQualityOfServiceLevel.AtMostOnce, true);
|
||||
continue;
|
||||
}
|
||||
|
||||
PublishUnitState(unit);
|
||||
|
||||
MqttClient.PublishAsync($"{Global.mqtt_discovery_prefix}/{type}/omnilink/unit{i.ToString()}/config",
|
||||
JsonConvert.SerializeObject(unit.ToConfig()), MqttQualityOfServiceLevel.AtMostOnce, true);
|
||||
}
|
||||
}
|
||||
|
||||
private void PublishThermostats()
|
||||
{
|
||||
log.Debug("Publishing thermostats");
|
||||
|
||||
for (ushort i = 1; i < OmniLink.Controller.Thermostats.Count; i++)
|
||||
{
|
||||
clsThermostat thermostat = OmniLink.Controller.Thermostats[i];
|
||||
|
||||
if (thermostat.DefaultProperties == true)
|
||||
{
|
||||
MqttClient.PublishAsync($"{Global.mqtt_discovery_prefix}/climate/omnilink/thermostat{i.ToString()}/config", null, MqttQualityOfServiceLevel.AtMostOnce, true);
|
||||
continue;
|
||||
}
|
||||
|
||||
PublishThermostatState(thermostat);
|
||||
|
||||
MqttClient.PublishAsync($"{Global.mqtt_discovery_prefix}/climate/omnilink/thermostat{i.ToString()}/config",
|
||||
JsonConvert.SerializeObject(thermostat.ToConfig()), MqttQualityOfServiceLevel.AtMostOnce, true);
|
||||
}
|
||||
}
|
||||
|
||||
private void PublishButtons()
|
||||
{
|
||||
log.Debug("Publishing buttons");
|
||||
|
||||
for (ushort i = 1; i < OmniLink.Controller.Buttons.Count; i++)
|
||||
{
|
||||
clsButton button = OmniLink.Controller.Buttons[i];
|
||||
|
||||
if (button.DefaultProperties == true)
|
||||
{
|
||||
MqttClient.PublishAsync($"{Global.mqtt_discovery_prefix}/switch/omnilink/button{i.ToString()}/config", null, MqttQualityOfServiceLevel.AtMostOnce, true);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Buttons are always off
|
||||
MqttClient.PublishAsync(button.ToTopic(Topic.state), "OFF", MqttQualityOfServiceLevel.AtMostOnce, true);
|
||||
|
||||
MqttClient.PublishAsync($"{Global.mqtt_discovery_prefix}/switch/omnilink/button{i.ToString()}/config",
|
||||
JsonConvert.SerializeObject(button.ToConfig()), MqttQualityOfServiceLevel.AtMostOnce, true);
|
||||
}
|
||||
}
|
||||
|
||||
private void Omnilink_OnAreaStatus(object sender, AreaStatusEventArgs e)
|
||||
{
|
||||
PublishAreaState(e.Area);
|
||||
}
|
||||
|
||||
private void Omnilink_OnZoneStatus(object sender, ZoneStatusEventArgs e)
|
||||
{
|
||||
PublishZoneState(e.Zone);
|
||||
}
|
||||
|
||||
private void Omnilink_OnUnitStatus(object sender, UnitStatusEventArgs e)
|
||||
{
|
||||
PublishUnitState(e.Unit);
|
||||
}
|
||||
|
||||
private void Omnilink_OnThermostatStatus(object sender, ThermostatStatusEventArgs e)
|
||||
{
|
||||
if(!e.EventTimer)
|
||||
PublishThermostatState(e.Thermostat);
|
||||
}
|
||||
|
||||
private void PublishAreaState(clsArea area)
|
||||
{
|
||||
MqttClient.PublishAsync(area.ToTopic(Topic.state), area.ToState(), MqttQualityOfServiceLevel.AtMostOnce, true);
|
||||
}
|
||||
|
||||
private void PublishZoneState(clsZone zone)
|
||||
{
|
||||
MqttClient.PublishAsync(zone.ToTopic(Topic.state), zone.ToState(), MqttQualityOfServiceLevel.AtMostOnce, true);
|
||||
}
|
||||
|
||||
private void PublishUnitState(clsUnit unit)
|
||||
{
|
||||
MqttClient.PublishAsync(unit.ToTopic(Topic.state), unit.ToState(), MqttQualityOfServiceLevel.AtMostOnce, true);
|
||||
|
||||
if(unit.Number < 385)
|
||||
MqttClient.PublishAsync(unit.ToTopic(Topic.brightness_state), unit.ToBrightnessState().ToString(), MqttQualityOfServiceLevel.AtMostOnce, true);
|
||||
}
|
||||
|
||||
private void PublishThermostatState(clsThermostat thermostat)
|
||||
{
|
||||
MqttClient.PublishAsync(thermostat.ToTopic(Topic.current_operation), thermostat.ToOperationState(), MqttQualityOfServiceLevel.AtMostOnce, true);
|
||||
MqttClient.PublishAsync(thermostat.ToTopic(Topic.current_temperature), thermostat.TempText(), MqttQualityOfServiceLevel.AtMostOnce, true);
|
||||
MqttClient.PublishAsync(thermostat.ToTopic(Topic.current_humidity), thermostat.HumidityText(), MqttQualityOfServiceLevel.AtMostOnce, true);
|
||||
MqttClient.PublishAsync(thermostat.ToTopic(Topic.temperature_heat_state), thermostat.HeatSetpointText(), MqttQualityOfServiceLevel.AtMostOnce, true);
|
||||
MqttClient.PublishAsync(thermostat.ToTopic(Topic.temperature_cool_state), thermostat.CoolSetpointText(), MqttQualityOfServiceLevel.AtMostOnce, true);
|
||||
MqttClient.PublishAsync(thermostat.ToTopic(Topic.humidify_state), thermostat.HumidifySetpointText(), MqttQualityOfServiceLevel.AtMostOnce, true);
|
||||
MqttClient.PublishAsync(thermostat.ToTopic(Topic.dehumidify_state), thermostat.DehumidifySetpointText(), MqttQualityOfServiceLevel.AtMostOnce, true);
|
||||
MqttClient.PublishAsync(thermostat.ToTopic(Topic.mode_state), thermostat.ModeText().ToLower(), MqttQualityOfServiceLevel.AtMostOnce, true);
|
||||
MqttClient.PublishAsync(thermostat.ToTopic(Topic.fan_mode_state), thermostat.FanModeText().ToLower(), MqttQualityOfServiceLevel.AtMostOnce, true);
|
||||
MqttClient.PublishAsync(thermostat.ToTopic(Topic.hold_state), thermostat.HoldStatusText().ToLower(), MqttQualityOfServiceLevel.AtMostOnce, true);
|
||||
}
|
||||
}
|
||||
}
|
837
OmniLinkBridge/Modules/OmniLinkII.cs
Normal file
837
OmniLinkBridge/Modules/OmniLinkII.cs
Normal file
|
@ -0,0 +1,837 @@
|
|||
using HAI_Shared;
|
||||
using OmniLinkBridge.OmniLink;
|
||||
using log4net;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Reflection;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace OmniLinkBridge.Modules
|
||||
{
|
||||
public class OmniLinkII : IModule
|
||||
{
|
||||
private static ILog log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);
|
||||
|
||||
// OmniLink Controller
|
||||
public clsHAC Controller { get; private set; }
|
||||
private DateTime retry = DateTime.MinValue;
|
||||
|
||||
// Thermostats
|
||||
private Dictionary<ushort, DateTime> tstats = new Dictionary<ushort, DateTime>();
|
||||
private System.Timers.Timer tstat_timer = new System.Timers.Timer();
|
||||
private object tstat_lock = new object();
|
||||
|
||||
// Events
|
||||
public event EventHandler<EventArgs> OnConnect;
|
||||
public event EventHandler<AreaStatusEventArgs> OnAreaStatus;
|
||||
public event EventHandler<ZoneStatusEventArgs> OnZoneStatus;
|
||||
public event EventHandler<ThermostatStatusEventArgs> OnThermostatStatus;
|
||||
public event EventHandler<UnitStatusEventArgs> OnUnitStatus;
|
||||
public event EventHandler<MessageStatusEventArgs> OnMessageStatus;
|
||||
public event EventHandler<SystemStatusEventArgs> OnSystemStatus;
|
||||
|
||||
private readonly AutoResetEvent trigger = new AutoResetEvent(false);
|
||||
private readonly AutoResetEvent nameWait = new AutoResetEvent(false);
|
||||
|
||||
public OmniLinkII(string address, int port, string key1, string key2)
|
||||
{
|
||||
Controller = new clsHAC();
|
||||
|
||||
Controller.Connection.NetworkAddress = address;
|
||||
Controller.Connection.NetworkPort = (ushort)port;
|
||||
Controller.Connection.ControllerKey = clsUtil.HexString2ByteArray(String.Concat(key1, key2));
|
||||
|
||||
Controller.PreferredNetworkProtocol = clsHAC.enuPreferredNetworkProtocol.TCP;
|
||||
Controller.Connection.ConnectionType = enuOmniLinkConnectionType.Network_TCP;
|
||||
|
||||
tstat_timer.Elapsed += tstat_timer_Elapsed;
|
||||
tstat_timer.AutoReset = false;
|
||||
}
|
||||
|
||||
public void Startup()
|
||||
{
|
||||
while (Global.running)
|
||||
{
|
||||
// Make sure controller connection is active
|
||||
if (Controller.Connection.ConnectionState == enuOmniLinkConnectionState.Offline &&
|
||||
retry < DateTime.Now)
|
||||
{
|
||||
Connect();
|
||||
}
|
||||
|
||||
trigger.WaitOne(new TimeSpan(0, 0, 5));
|
||||
}
|
||||
}
|
||||
|
||||
public void Shutdown()
|
||||
{
|
||||
trigger.Set();
|
||||
}
|
||||
|
||||
#region Connection
|
||||
private void Connect()
|
||||
{
|
||||
if (Controller.Connection.ConnectionState == enuOmniLinkConnectionState.Offline)
|
||||
{
|
||||
retry = DateTime.Now.AddMinutes(1);
|
||||
|
||||
Controller.Connection.Connect(HandleConnectStatus, HandleUnsolicitedPackets);
|
||||
}
|
||||
}
|
||||
|
||||
private void Disconnect()
|
||||
{
|
||||
if (Controller.Connection.ConnectionState != enuOmniLinkConnectionState.Offline)
|
||||
Controller.Connection.Disconnect();
|
||||
}
|
||||
|
||||
private void HandleConnectStatus(enuOmniLinkCommStatus CS)
|
||||
{
|
||||
switch (CS)
|
||||
{
|
||||
case enuOmniLinkCommStatus.NoReply:
|
||||
log.Error("CONNECTION STATUS: No Reply");
|
||||
break;
|
||||
case enuOmniLinkCommStatus.UnrecognizedReply:
|
||||
log.Error("CONNECTION STATUS: Unrecognized Reply");
|
||||
break;
|
||||
case enuOmniLinkCommStatus.UnsupportedProtocol:
|
||||
log.Error("CONNECTION STATUS: Unsupported Protocol");
|
||||
break;
|
||||
case enuOmniLinkCommStatus.ClientSessionTerminated:
|
||||
log.Error("CONNECTION STATUS: Client Session Terminated");
|
||||
break;
|
||||
case enuOmniLinkCommStatus.ControllerSessionTerminated:
|
||||
log.Error("CONNECTION STATUS: Controller Session Terminated");
|
||||
break;
|
||||
case enuOmniLinkCommStatus.CannotStartNewSession:
|
||||
log.Error("CONNECTION STATUS: Cannot Start New Session");
|
||||
break;
|
||||
case enuOmniLinkCommStatus.LoginFailed:
|
||||
log.Error("CONNECTION STATUS: Login Failed");
|
||||
break;
|
||||
case enuOmniLinkCommStatus.UnableToOpenSocket:
|
||||
log.Error("CONNECTION STATUS: Unable To Open Socket");
|
||||
break;
|
||||
case enuOmniLinkCommStatus.UnableToConnect:
|
||||
log.Error("CONNECTION STATUS: Unable To Connect");
|
||||
break;
|
||||
case enuOmniLinkCommStatus.SocketClosed:
|
||||
log.Error("CONNECTION STATUS: Socket Closed");
|
||||
break;
|
||||
case enuOmniLinkCommStatus.UnexpectedError:
|
||||
log.Error("CONNECTION STATUS: Unexpected Error");
|
||||
break;
|
||||
case enuOmniLinkCommStatus.UnableToCreateSocket:
|
||||
log.Error("CONNECTION STATUS: Unable To Create Socket");
|
||||
break;
|
||||
case enuOmniLinkCommStatus.Retrying:
|
||||
log.Warn("CONNECTION STATUS: Retrying");
|
||||
break;
|
||||
case enuOmniLinkCommStatus.Connected:
|
||||
IdentifyController();
|
||||
break;
|
||||
case enuOmniLinkCommStatus.Connecting:
|
||||
log.Info("CONNECTION STATUS: Connecting");
|
||||
break;
|
||||
case enuOmniLinkCommStatus.Disconnected:
|
||||
log.Info("CONNECTION STATUS: Disconnected");
|
||||
break;
|
||||
case enuOmniLinkCommStatus.InterruptedFunctionCall:
|
||||
if (Global.running)
|
||||
log.Error("CONNECTION STATUS: Interrupted Function Call");
|
||||
break;
|
||||
case enuOmniLinkCommStatus.PermissionDenied:
|
||||
log.Error("CONNECTION STATUS: Permission Denied");
|
||||
break;
|
||||
case enuOmniLinkCommStatus.BadAddress:
|
||||
log.Error("CONNECTION STATUS: Bad Address");
|
||||
break;
|
||||
case enuOmniLinkCommStatus.InvalidArgument:
|
||||
log.Error("CONNECTION STATUS: Invalid Argument");
|
||||
break;
|
||||
case enuOmniLinkCommStatus.TooManyOpenFiles:
|
||||
log.Error("CONNECTION STATUS: Too Many Open Files");
|
||||
break;
|
||||
case enuOmniLinkCommStatus.ResourceTemporarilyUnavailable:
|
||||
log.Error("CONNECTION STATUS: Resource Temporarily Unavailable");
|
||||
break;
|
||||
case enuOmniLinkCommStatus.OperationNowInProgress:
|
||||
log.Warn("CONNECTION STATUS: Operation Now In Progress");
|
||||
break;
|
||||
case enuOmniLinkCommStatus.OperationAlreadyInProgress:
|
||||
log.Warn("CONNECTION STATUS: Operation Already In Progress");
|
||||
break;
|
||||
case enuOmniLinkCommStatus.SocketOperationOnNonSocket:
|
||||
log.Error("CONNECTION STATUS: Socket Operation On Non Socket");
|
||||
break;
|
||||
case enuOmniLinkCommStatus.DestinationAddressRequired:
|
||||
log.Error("CONNECTION STATUS: Destination Address Required");
|
||||
break;
|
||||
case enuOmniLinkCommStatus.MessgeTooLong:
|
||||
log.Error("CONNECTION STATUS: Message Too Long");
|
||||
break;
|
||||
case enuOmniLinkCommStatus.WrongProtocolType:
|
||||
log.Error("CONNECTION STATUS: Wrong Protocol Type");
|
||||
break;
|
||||
case enuOmniLinkCommStatus.BadProtocolOption:
|
||||
log.Error("CONNECTION STATUS: Bad Protocol Option");
|
||||
break;
|
||||
case enuOmniLinkCommStatus.ProtocolNotSupported:
|
||||
log.Error("CONNECTION STATUS: Protocol Not Supported");
|
||||
break;
|
||||
case enuOmniLinkCommStatus.SocketTypeNotSupported:
|
||||
log.Error("CONNECTION STATUS: Socket Type Not Supported");
|
||||
break;
|
||||
case enuOmniLinkCommStatus.OperationNotSupported:
|
||||
log.Error("CONNECTION STATUS: Operation Not Supported");
|
||||
break;
|
||||
case enuOmniLinkCommStatus.ProtocolFamilyNotSupported:
|
||||
log.Error("CONNECTION STATUS: Protocol Family Not Supported");
|
||||
break;
|
||||
case enuOmniLinkCommStatus.AddressFamilyNotSupported:
|
||||
log.Error("CONNECTION STATUS: Address Family Not Supported");
|
||||
break;
|
||||
case enuOmniLinkCommStatus.AddressInUse:
|
||||
log.Error("CONNECTION STATUS: Address In Use");
|
||||
break;
|
||||
case enuOmniLinkCommStatus.AddressNotAvailable:
|
||||
log.Error("CONNECTION STATUS: Address Not Available");
|
||||
break;
|
||||
case enuOmniLinkCommStatus.NetworkIsDown:
|
||||
log.Error("CONNECTION STATUS: Network Is Down");
|
||||
break;
|
||||
case enuOmniLinkCommStatus.NetworkIsUnreachable:
|
||||
log.Error("CONNECTION STATUS: Network Is Unreachable");
|
||||
break;
|
||||
case enuOmniLinkCommStatus.NetworkReset:
|
||||
log.Error("CONNECTION STATUS: Network Reset");
|
||||
break;
|
||||
case enuOmniLinkCommStatus.ConnectionAborted:
|
||||
log.Error("CONNECTION STATUS: Connection Aborted");
|
||||
break;
|
||||
case enuOmniLinkCommStatus.ConnectionResetByPeer:
|
||||
log.Error("CONNECTION STATUS: Connection Reset By Peer");
|
||||
break;
|
||||
case enuOmniLinkCommStatus.NoBufferSpaceAvailable:
|
||||
log.Error("CONNECTION STATUS: No Buffer Space Available");
|
||||
break;
|
||||
case enuOmniLinkCommStatus.AlreadyConnected:
|
||||
log.Warn("CONNECTION STATUS: Already Connected");
|
||||
break;
|
||||
case enuOmniLinkCommStatus.NotConnected:
|
||||
log.Error("CONNECTION STATUS: Not Connected");
|
||||
break;
|
||||
case enuOmniLinkCommStatus.CannotSendAfterShutdown:
|
||||
log.Error("CONNECTION STATUS: Cannot Send After Shutdown");
|
||||
break;
|
||||
case enuOmniLinkCommStatus.ConnectionTimedOut:
|
||||
log.Error("CONNECTION STATUS: Connection Timed Out");
|
||||
break;
|
||||
case enuOmniLinkCommStatus.ConnectionRefused:
|
||||
log.Error("CONNECTION STATUS: Connection Refused");
|
||||
break;
|
||||
case enuOmniLinkCommStatus.HostIsDown:
|
||||
log.Error("CONNECTION STATUS: Host Is Down");
|
||||
break;
|
||||
case enuOmniLinkCommStatus.HostUnreachable:
|
||||
log.Error("CONNECTION STATUS: Host Unreachable");
|
||||
break;
|
||||
case enuOmniLinkCommStatus.TooManyProcesses:
|
||||
log.Error("CONNECTION STATUS: Too Many Processes");
|
||||
break;
|
||||
case enuOmniLinkCommStatus.NetworkSubsystemIsUnavailable:
|
||||
log.Error("CONNECTION STATUS: Network Subsystem Is Unavailable");
|
||||
break;
|
||||
case enuOmniLinkCommStatus.UnsupportedVersion:
|
||||
log.Error("CONNECTION STATUS: Unsupported Version");
|
||||
break;
|
||||
case enuOmniLinkCommStatus.NotInitialized:
|
||||
log.Error("CONNECTION STATUS: Not Initialized");
|
||||
break;
|
||||
case enuOmniLinkCommStatus.ShutdownInProgress:
|
||||
log.Error("CONNECTION STATUS: Shutdown In Progress");
|
||||
break;
|
||||
case enuOmniLinkCommStatus.ClassTypeNotFound:
|
||||
log.Error("CONNECTION STATUS: Class Type Not Found");
|
||||
break;
|
||||
case enuOmniLinkCommStatus.HostNotFound:
|
||||
log.Error("CONNECTION STATUS: Host Not Found");
|
||||
break;
|
||||
case enuOmniLinkCommStatus.HostNotFoundTryAgain:
|
||||
log.Error("CONNECTION STATUS: Host Not Found Try Again");
|
||||
break;
|
||||
case enuOmniLinkCommStatus.NonRecoverableError:
|
||||
log.Error("CONNECTION STATUS: Non Recoverable Error");
|
||||
break;
|
||||
case enuOmniLinkCommStatus.NoDataOfRequestedType:
|
||||
log.Error("CONNECTION STATUS: No Data Of Requested Type");
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private void IdentifyController()
|
||||
{
|
||||
if (Controller.Connection.ConnectionState == enuOmniLinkConnectionState.Online ||
|
||||
Controller.Connection.ConnectionState == enuOmniLinkConnectionState.OnlineSecure)
|
||||
{
|
||||
Controller.Connection.Send(new clsOL2MsgRequestSystemInformation(Controller.Connection), HandleIdentifyController);
|
||||
}
|
||||
}
|
||||
|
||||
private void HandleIdentifyController(clsOmniLinkMessageQueueItem M, byte[] B, bool Timeout)
|
||||
{
|
||||
if (Timeout)
|
||||
return;
|
||||
|
||||
if ((B.Length > 3) && (B[2] == (byte)enuOmniLink2MessageType.SystemInformation))
|
||||
{
|
||||
clsOL2MsgSystemInformation MSG = new clsOL2MsgSystemInformation(Controller.Connection, B);
|
||||
|
||||
foreach (enuModel enu in Enum.GetValues(typeof(enuModel)))
|
||||
{
|
||||
if (enu == MSG.ModelNumber)
|
||||
{
|
||||
Controller.Model = enu;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (Controller.Model == MSG.ModelNumber)
|
||||
{
|
||||
Controller.CopySystemInformation(MSG);
|
||||
log.Info("CONTROLLER IS: " + Controller.GetModelText() + " (" + Controller.GetVersionText() + ")");
|
||||
|
||||
_ = Connected();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
log.Error("Model does not match file");
|
||||
Controller.Connection.Disconnect();
|
||||
}
|
||||
}
|
||||
|
||||
private async Task Connected()
|
||||
{
|
||||
retry = DateTime.MinValue;
|
||||
|
||||
await GetNamedProperties();
|
||||
UnsolicitedNotifications(true);
|
||||
|
||||
tstat_timer.Interval = ThermostatTimerInterval();
|
||||
tstat_timer.Start();
|
||||
|
||||
OnConnect?.Invoke(this, new EventArgs());
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Names
|
||||
private async Task GetNamedProperties()
|
||||
{
|
||||
log.Debug("Retrieving named units");
|
||||
|
||||
await GetNamed(enuObjectType.Area);
|
||||
await GetNamed(enuObjectType.Zone);
|
||||
await GetNamed(enuObjectType.Thermostat);
|
||||
await GetNamed(enuObjectType.Unit);
|
||||
await GetNamed(enuObjectType.Message);
|
||||
await GetNamed(enuObjectType.Button);
|
||||
}
|
||||
|
||||
private async Task GetNamed(enuObjectType type)
|
||||
{
|
||||
GetNextNamed(type, 0);
|
||||
|
||||
await Task.Run(() =>
|
||||
{
|
||||
log.Debug("Waiting for named units " + type.ToString());
|
||||
nameWait.WaitOne(new TimeSpan(0, 0, 10));
|
||||
});
|
||||
}
|
||||
|
||||
private void GetNextNamed(enuObjectType type, int ix)
|
||||
{
|
||||
clsOL2MsgRequestProperties MSG = new clsOL2MsgRequestProperties(Controller.Connection);
|
||||
MSG.ObjectType = type;
|
||||
MSG.IndexNumber = (UInt16)ix;
|
||||
MSG.RelativeDirection = 1; // next object after IndexNumber
|
||||
MSG.Filter1 = 1; // (0=Named or Unnamed, 1=Named, 2=Unnamed).
|
||||
MSG.Filter2 = 0; // Any Area
|
||||
MSG.Filter3 = 0; // Any Room
|
||||
Controller.Connection.Send(MSG, HandleNamedPropertiesResponse);
|
||||
}
|
||||
|
||||
private void HandleNamedPropertiesResponse(clsOmniLinkMessageQueueItem M, byte[] B, bool Timeout)
|
||||
{
|
||||
if (Timeout)
|
||||
return;
|
||||
|
||||
// does it look like a valid response
|
||||
if ((B.Length > 3) && (B[0] == 0x21))
|
||||
{
|
||||
switch ((enuOmniLink2MessageType)B[2])
|
||||
{
|
||||
case enuOmniLink2MessageType.EOD:
|
||||
nameWait.Set();
|
||||
break;
|
||||
case enuOmniLink2MessageType.Properties:
|
||||
|
||||
clsOL2MsgProperties MSG = new clsOL2MsgProperties(Controller.Connection, B);
|
||||
|
||||
switch (MSG.ObjectType)
|
||||
{
|
||||
case enuObjectType.Area:
|
||||
Controller.Areas.CopyProperties(MSG);
|
||||
break;
|
||||
case enuObjectType.Zone:
|
||||
Controller.Zones.CopyProperties(MSG);
|
||||
|
||||
if (Controller.Zones[MSG.ObjectNumber].IsTemperatureZone())
|
||||
Controller.Connection.Send(new clsOL2MsgRequestExtendedStatus(Controller.Connection, enuObjectType.Auxillary, MSG.ObjectNumber, MSG.ObjectNumber), HandleRequestAuxillaryStatus);
|
||||
|
||||
break;
|
||||
case enuObjectType.Thermostat:
|
||||
Controller.Thermostats.CopyProperties(MSG);
|
||||
|
||||
if (!tstats.ContainsKey(MSG.ObjectNumber))
|
||||
tstats.Add(MSG.ObjectNumber, DateTime.MinValue);
|
||||
else
|
||||
tstats[MSG.ObjectNumber] = DateTime.MinValue;
|
||||
|
||||
Controller.Connection.Send(new clsOL2MsgRequestExtendedStatus(Controller.Connection, enuObjectType.Thermostat, MSG.ObjectNumber, MSG.ObjectNumber), HandleRequestThermostatStatus);
|
||||
log.Debug("Added thermostat to watch list " + Controller.Thermostats[MSG.ObjectNumber].Name);
|
||||
break;
|
||||
case enuObjectType.Unit:
|
||||
Controller.Units.CopyProperties(MSG);
|
||||
break;
|
||||
case enuObjectType.Message:
|
||||
Controller.Messages.CopyProperties(MSG);
|
||||
break;
|
||||
case enuObjectType.Button:
|
||||
Controller.Buttons.CopyProperties(MSG);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
GetNextNamed(MSG.ObjectType, MSG.ObjectNumber);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Notifications
|
||||
private void UnsolicitedNotifications(bool enable)
|
||||
{
|
||||
log.Info("Unsolicited notifications " + (enable ? "enabled" : "disabled"));
|
||||
Controller.Connection.Send(new clsOL2EnableNotifications(Controller.Connection, enable), null);
|
||||
}
|
||||
|
||||
private bool HandleUnsolicitedPackets(byte[] B)
|
||||
{
|
||||
if ((B.Length > 3) && (B[0] == 0x21))
|
||||
{
|
||||
bool handled = false;
|
||||
|
||||
switch ((enuOmniLink2MessageType)B[2])
|
||||
{
|
||||
case enuOmniLink2MessageType.ClearNames:
|
||||
break;
|
||||
case enuOmniLink2MessageType.DownloadNames:
|
||||
break;
|
||||
case enuOmniLink2MessageType.UploadNames:
|
||||
break;
|
||||
case enuOmniLink2MessageType.NameData:
|
||||
break;
|
||||
case enuOmniLink2MessageType.ClearVoices:
|
||||
break;
|
||||
case enuOmniLink2MessageType.DownloadVoices:
|
||||
break;
|
||||
case enuOmniLink2MessageType.UploadVoices:
|
||||
break;
|
||||
case enuOmniLink2MessageType.VoiceData:
|
||||
break;
|
||||
case enuOmniLink2MessageType.Command:
|
||||
break;
|
||||
case enuOmniLink2MessageType.EnableNotifications:
|
||||
break;
|
||||
case enuOmniLink2MessageType.SystemInformation:
|
||||
break;
|
||||
case enuOmniLink2MessageType.SystemStatus:
|
||||
break;
|
||||
case enuOmniLink2MessageType.SystemTroubles:
|
||||
break;
|
||||
case enuOmniLink2MessageType.SystemFeatures:
|
||||
break;
|
||||
case enuOmniLink2MessageType.Capacities:
|
||||
break;
|
||||
case enuOmniLink2MessageType.Properties:
|
||||
break;
|
||||
case enuOmniLink2MessageType.Status:
|
||||
break;
|
||||
case enuOmniLink2MessageType.EventLogItem:
|
||||
break;
|
||||
case enuOmniLink2MessageType.ValidateCode:
|
||||
break;
|
||||
case enuOmniLink2MessageType.SystemFormats:
|
||||
break;
|
||||
case enuOmniLink2MessageType.Login:
|
||||
break;
|
||||
case enuOmniLink2MessageType.Logout:
|
||||
break;
|
||||
case enuOmniLink2MessageType.ActivateKeypadEmg:
|
||||
break;
|
||||
case enuOmniLink2MessageType.ExtSecurityStatus:
|
||||
break;
|
||||
case enuOmniLink2MessageType.CmdExtSecurity:
|
||||
break;
|
||||
case enuOmniLink2MessageType.AudioSourceStatus:
|
||||
break;
|
||||
case enuOmniLink2MessageType.SystemEvents:
|
||||
HandleUnsolicitedSystemEvent(B);
|
||||
handled = true;
|
||||
break;
|
||||
case enuOmniLink2MessageType.ZoneReadyStatus:
|
||||
break;
|
||||
case enuOmniLink2MessageType.ExtendedStatus:
|
||||
HandleUnsolicitedExtendedStatus(B);
|
||||
handled = true;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
if (Global.verbose_unhandled && !handled)
|
||||
log.Debug("Unhandled notification: " + ((enuOmniLink2MessageType)B[2]).ToString());
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private void HandleUnsolicitedSystemEvent(byte[] B)
|
||||
{
|
||||
clsOL2SystemEvent MSG = new clsOL2SystemEvent(Controller.Connection, B);
|
||||
|
||||
SystemStatusEventArgs eventargs = new SystemStatusEventArgs();
|
||||
|
||||
if (MSG.SystemEvent >= 1 && MSG.SystemEvent <= 255)
|
||||
{
|
||||
eventargs.Type = enuEventType.USER_MACRO_BUTTON;
|
||||
eventargs.Value = ((int)MSG.SystemEvent).ToString() + " " + Controller.Buttons[MSG.SystemEvent].Name;
|
||||
|
||||
OnSystemStatus?.Invoke(this, eventargs);
|
||||
}
|
||||
else if (MSG.SystemEvent >= 768 && MSG.SystemEvent <= 771)
|
||||
{
|
||||
eventargs.Type = enuEventType.PHONE_;
|
||||
|
||||
if (MSG.SystemEvent == 768)
|
||||
{
|
||||
eventargs.Value = "DEAD";
|
||||
eventargs.SendNotification = true;
|
||||
}
|
||||
else if (MSG.SystemEvent == 769)
|
||||
eventargs.Value = "RING";
|
||||
else if (MSG.SystemEvent == 770)
|
||||
eventargs.Value = "OFF HOOK";
|
||||
else if (MSG.SystemEvent == 771)
|
||||
eventargs.Value = "ON HOOK";
|
||||
|
||||
OnSystemStatus?.Invoke(this, eventargs);
|
||||
}
|
||||
else if (MSG.SystemEvent >= 772 && MSG.SystemEvent <= 773)
|
||||
{
|
||||
eventargs.Type = enuEventType.AC_POWER_;
|
||||
eventargs.SendNotification = true;
|
||||
|
||||
if (MSG.SystemEvent == 772)
|
||||
eventargs.Value = "OFF";
|
||||
else if (MSG.SystemEvent == 773)
|
||||
eventargs.Value = "RESTORED";
|
||||
|
||||
OnSystemStatus?.Invoke(this, eventargs);
|
||||
}
|
||||
else if (MSG.SystemEvent >= 774 && MSG.SystemEvent <= 775)
|
||||
{
|
||||
eventargs.Type = enuEventType.BATTERY_;
|
||||
eventargs.SendNotification = true;
|
||||
|
||||
if (MSG.SystemEvent == 774)
|
||||
eventargs.Value = "LOW";
|
||||
else if (MSG.SystemEvent == 775)
|
||||
eventargs.Value = "OK";
|
||||
|
||||
OnSystemStatus?.Invoke(this, eventargs);
|
||||
}
|
||||
else if (MSG.SystemEvent >= 776 && MSG.SystemEvent <= 777)
|
||||
{
|
||||
eventargs.Type = enuEventType.DCM_;
|
||||
eventargs.SendNotification = true;
|
||||
|
||||
if (MSG.SystemEvent == 776)
|
||||
eventargs.Value = "TROUBLE";
|
||||
else if (MSG.SystemEvent == 777)
|
||||
eventargs.Value = "OK";
|
||||
|
||||
OnSystemStatus?.Invoke(this, eventargs);
|
||||
}
|
||||
else if (MSG.SystemEvent >= 778 && MSG.SystemEvent <= 781)
|
||||
{
|
||||
eventargs.Type = enuEventType.ENERGY_COST_;
|
||||
|
||||
if (MSG.SystemEvent == 778)
|
||||
eventargs.Value = "LOW";
|
||||
else if (MSG.SystemEvent == 779)
|
||||
eventargs.Value = "MID";
|
||||
else if (MSG.SystemEvent == 780)
|
||||
eventargs.Value = "HIGH";
|
||||
else if (MSG.SystemEvent == 781)
|
||||
eventargs.Value = "CRITICAL";
|
||||
|
||||
OnSystemStatus?.Invoke(this, eventargs);
|
||||
}
|
||||
else if (MSG.SystemEvent >= 782 && MSG.SystemEvent <= 787)
|
||||
{
|
||||
eventargs.Type = enuEventType.CAMERA;
|
||||
eventargs.Value = (MSG.SystemEvent - 781).ToString();
|
||||
|
||||
OnSystemStatus?.Invoke(this, eventargs);
|
||||
}
|
||||
else if (MSG.SystemEvent >= 61440 && MSG.SystemEvent <= 64511)
|
||||
{
|
||||
eventargs.Type = enuEventType.SWITCH_PRESS;
|
||||
int state = (int)MSG.Data[1] - 240;
|
||||
int id = (int)MSG.Data[2];
|
||||
|
||||
eventargs.Value = "Unit: " + id + ", State: " + state;
|
||||
|
||||
OnSystemStatus?.Invoke(this, eventargs);
|
||||
}
|
||||
else if (MSG.SystemEvent >= 64512 && MSG.SystemEvent <= 65535)
|
||||
{
|
||||
eventargs.Type = enuEventType.UPB_LINK;
|
||||
int state = (int)MSG.Data[1] - 252;
|
||||
int id = (int)MSG.Data[2];
|
||||
|
||||
eventargs.Value = "Link: " + id + ", State: " + state;
|
||||
|
||||
OnSystemStatus?.Invoke(this, eventargs);
|
||||
}
|
||||
else if (Global.verbose_unhandled)
|
||||
{
|
||||
StringBuilder sb = new StringBuilder();
|
||||
for (int i = 0; i < MSG.MessageLength; i++)
|
||||
sb.Append(MSG.Data[i].ToString() + " ");
|
||||
log.Debug("Unhandled SystemEvent Raw: " + sb.ToString() + "Num: " + MSG.SystemEvent);
|
||||
|
||||
int num = ((int)MSG.MessageLength - 1) / 2;
|
||||
for (int i = 0; i < num; i++)
|
||||
{
|
||||
log.Debug("Unhandled SystemEvent: " +
|
||||
(int)MSG.Data[(i * 2) + 1] + " " + (int)MSG.Data[(i * 2) + 2] + ": " +
|
||||
Convert.ToString(MSG.Data[(i * 2) + 1], 2).PadLeft(8, '0') + " " + Convert.ToString(MSG.Data[(i * 2) + 2], 2).PadLeft(8, '0'));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void HandleUnsolicitedExtendedStatus(byte[] B)
|
||||
{
|
||||
clsOL2MsgExtendedStatus MSG = new clsOL2MsgExtendedStatus(Controller.Connection, B);
|
||||
|
||||
switch (MSG.ObjectType)
|
||||
{
|
||||
case enuObjectType.Area:
|
||||
for (byte i = 0; i < MSG.AreaCount(); i++)
|
||||
{
|
||||
Controller.Areas[MSG.ObjectNumber(i)].CopyExtendedStatus(MSG, i);
|
||||
OnAreaStatus?.Invoke(this, new AreaStatusEventArgs()
|
||||
{
|
||||
ID = MSG.ObjectNumber(i),
|
||||
Area = Controller.Areas[MSG.ObjectNumber(i)]
|
||||
});
|
||||
}
|
||||
break;
|
||||
case enuObjectType.Auxillary:
|
||||
for (byte i = 0; i < MSG.AuxStatusCount(); i++)
|
||||
{
|
||||
Controller.Zones[MSG.ObjectNumber(i)].CopyAuxExtendedStatus(MSG, i);
|
||||
OnZoneStatus?.Invoke(this, new ZoneStatusEventArgs()
|
||||
{
|
||||
ID = MSG.ObjectNumber(i),
|
||||
Zone = Controller.Zones[MSG.ObjectNumber(i)]
|
||||
});
|
||||
}
|
||||
break;
|
||||
case enuObjectType.Zone:
|
||||
for (byte i = 0; i < MSG.ZoneStatusCount(); i++)
|
||||
{
|
||||
Controller.Zones[MSG.ObjectNumber(i)].CopyExtendedStatus(MSG, i);
|
||||
OnZoneStatus?.Invoke(this, new ZoneStatusEventArgs()
|
||||
{
|
||||
ID = MSG.ObjectNumber(i),
|
||||
Zone = Controller.Zones[MSG.ObjectNumber(i)]
|
||||
});
|
||||
}
|
||||
break;
|
||||
case enuObjectType.Thermostat:
|
||||
for (byte i = 0; i < MSG.ThermostatStatusCount(); i++)
|
||||
{
|
||||
lock (tstat_lock)
|
||||
{
|
||||
Controller.Thermostats[MSG.ObjectNumber(i)].CopyExtendedStatus(MSG, i);
|
||||
OnThermostatStatus?.Invoke(this, new ThermostatStatusEventArgs()
|
||||
{
|
||||
ID = MSG.ObjectNumber(i),
|
||||
Thermostat = Controller.Thermostats[MSG.ObjectNumber(i)],
|
||||
EventTimer = false
|
||||
});
|
||||
|
||||
if (!tstats.ContainsKey(MSG.ObjectNumber(i)))
|
||||
tstats.Add(MSG.ObjectNumber(i), DateTime.Now);
|
||||
else
|
||||
tstats[MSG.ObjectNumber(i)] = DateTime.Now;
|
||||
|
||||
if (Global.verbose_thermostat_timer)
|
||||
log.Debug("Unsolicited status received for Thermostat " + Controller.Thermostats[MSG.ObjectNumber(i)].Name);
|
||||
}
|
||||
}
|
||||
break;
|
||||
case enuObjectType.Unit:
|
||||
for (byte i = 0; i < MSG.UnitStatusCount(); i++)
|
||||
{
|
||||
Controller.Units[MSG.ObjectNumber(i)].CopyExtendedStatus(MSG, i);
|
||||
OnUnitStatus?.Invoke(this, new UnitStatusEventArgs()
|
||||
{
|
||||
ID = MSG.ObjectNumber(i),
|
||||
Unit = Controller.Units[MSG.ObjectNumber(i)]
|
||||
});
|
||||
}
|
||||
break;
|
||||
case enuObjectType.Message:
|
||||
for (byte i = 0; i < MSG.MessageCount(); i++)
|
||||
{
|
||||
Controller.Messages[MSG.ObjectNumber(i)].CopyExtendedStatus(MSG, i);
|
||||
OnMessageStatus?.Invoke(this, new MessageStatusEventArgs()
|
||||
{
|
||||
ID = MSG.ObjectNumber(i),
|
||||
Message = Controller.Messages[MSG.ObjectNumber(i)]
|
||||
});
|
||||
}
|
||||
break;
|
||||
default:
|
||||
if (Global.verbose_unhandled)
|
||||
{
|
||||
StringBuilder sb = new StringBuilder();
|
||||
foreach (byte b in MSG.ToByteArray())
|
||||
sb.Append(b.ToString() + " ");
|
||||
|
||||
log.Debug("Unhandled ExtendedStatus" + MSG.ObjectType.ToString() + " " + sb.ToString());
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Thermostats
|
||||
static double ThermostatTimerInterval()
|
||||
{
|
||||
DateTime now = DateTime.Now;
|
||||
return ((60 - now.Second) * 1000 - now.Millisecond) + new TimeSpan(0, 0, 30).TotalMilliseconds;
|
||||
}
|
||||
|
||||
private static DateTime RoundToMinute(DateTime dt)
|
||||
{
|
||||
return new DateTime(dt.Year, dt.Month, dt.Day, dt.Hour, dt.Minute, 0);
|
||||
}
|
||||
|
||||
private void tstat_timer_Elapsed(object sender, System.Timers.ElapsedEventArgs e)
|
||||
{
|
||||
lock (tstat_lock)
|
||||
{
|
||||
foreach (KeyValuePair<ushort, DateTime> tstat in tstats)
|
||||
{
|
||||
// Poll every 4 minutes if no prior update
|
||||
if (RoundToMinute(tstat.Value).AddMinutes(4) <= RoundToMinute(DateTime.Now))
|
||||
{
|
||||
Controller.Connection.Send(new clsOL2MsgRequestExtendedStatus(Controller.Connection, enuObjectType.Thermostat, tstat.Key, tstat.Key), HandleRequestThermostatStatus);
|
||||
|
||||
if (Global.verbose_thermostat_timer)
|
||||
log.Debug("Polling status for Thermostat " + Controller.Thermostats[tstat.Key].Name);
|
||||
}
|
||||
|
||||
// Log every minute if update within 5 minutes and connected
|
||||
if (RoundToMinute(tstat.Value).AddMinutes(5) > RoundToMinute(DateTime.Now) &&
|
||||
(Controller.Connection.ConnectionState == enuOmniLinkConnectionState.Online ||
|
||||
Controller.Connection.ConnectionState == enuOmniLinkConnectionState.OnlineSecure))
|
||||
{
|
||||
if (Controller.Thermostats[tstat.Key].Temp > 0)
|
||||
{
|
||||
OnThermostatStatus?.Invoke(this, new ThermostatStatusEventArgs()
|
||||
{
|
||||
ID = tstat.Key,
|
||||
Thermostat = Controller.Thermostats[tstat.Key],
|
||||
EventTimer = true
|
||||
});
|
||||
}
|
||||
else if (Global.verbose_thermostat_timer)
|
||||
log.Warn("Not logging unknown temp for Thermostat " + Controller.Thermostats[tstat.Key].Name);
|
||||
}
|
||||
else if (Global.verbose_thermostat_timer)
|
||||
log.Warn("Not logging out of date status for Thermostat " + Controller.Thermostats[tstat.Key].Name);
|
||||
}
|
||||
}
|
||||
|
||||
tstat_timer.Interval = ThermostatTimerInterval();
|
||||
tstat_timer.Start();
|
||||
}
|
||||
|
||||
private void HandleRequestThermostatStatus(clsOmniLinkMessageQueueItem M, byte[] B, bool Timeout)
|
||||
{
|
||||
if (Timeout)
|
||||
return;
|
||||
|
||||
clsOL2MsgExtendedStatus MSG = new clsOL2MsgExtendedStatus(Controller.Connection, B);
|
||||
|
||||
for (byte i = 0; i < MSG.ThermostatStatusCount(); i++)
|
||||
{
|
||||
lock (tstat_lock)
|
||||
{
|
||||
Controller.Thermostats[MSG.ObjectNumber(i)].CopyExtendedStatus(MSG, i);
|
||||
|
||||
if (!tstats.ContainsKey(MSG.ObjectNumber(i)))
|
||||
tstats.Add(MSG.ObjectNumber(i), DateTime.Now);
|
||||
else
|
||||
tstats[MSG.ObjectNumber(i)] = DateTime.Now;
|
||||
|
||||
if (Global.verbose_thermostat_timer)
|
||||
log.Debug("Polling status received for Thermostat " + Controller.Thermostats[MSG.ObjectNumber(i)].Name);
|
||||
}
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Auxiliary
|
||||
private void HandleRequestAuxillaryStatus(clsOmniLinkMessageQueueItem M, byte[] B, bool Timeout)
|
||||
{
|
||||
if (Timeout)
|
||||
return;
|
||||
|
||||
clsOL2MsgExtendedStatus MSG = new clsOL2MsgExtendedStatus(Controller.Connection, B);
|
||||
|
||||
for (byte i = 0; i < MSG.AuxStatusCount(); i++)
|
||||
{
|
||||
Controller.Zones[MSG.ObjectNumber(i)].CopyAuxExtendedStatus(MSG, i);
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
}
|
111
OmniLinkBridge/Modules/TimeSyncModule.cs
Normal file
111
OmniLinkBridge/Modules/TimeSyncModule.cs
Normal file
|
@ -0,0 +1,111 @@
|
|||
using HAI_Shared;
|
||||
using OmniLinkBridge.OmniLink;
|
||||
using log4net;
|
||||
using System;
|
||||
using System.Reflection;
|
||||
using System.Threading;
|
||||
|
||||
namespace OmniLinkBridge.Modules
|
||||
{
|
||||
public class TimeSyncModule : IModule
|
||||
{
|
||||
private static ILog log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);
|
||||
|
||||
private OmniLinkII OmniLink { get; set; }
|
||||
|
||||
private System.Timers.Timer tsync_timer = new System.Timers.Timer();
|
||||
private DateTime tsync_check = DateTime.MinValue;
|
||||
|
||||
private readonly AutoResetEvent trigger = new AutoResetEvent(false);
|
||||
|
||||
public TimeSyncModule(OmniLinkII omni)
|
||||
{
|
||||
OmniLink = omni;
|
||||
}
|
||||
|
||||
public void Startup()
|
||||
{
|
||||
tsync_timer.Elapsed += tsync_timer_Elapsed;
|
||||
tsync_timer.AutoReset = false;
|
||||
|
||||
tsync_check = DateTime.MinValue;
|
||||
|
||||
tsync_timer.Interval = TimeTimerInterval();
|
||||
tsync_timer.Start();
|
||||
|
||||
// Wait until shutdown
|
||||
trigger.WaitOne();
|
||||
|
||||
tsync_timer.Stop();
|
||||
}
|
||||
|
||||
public void Shutdown()
|
||||
{
|
||||
trigger.Set();
|
||||
}
|
||||
|
||||
static double TimeTimerInterval()
|
||||
{
|
||||
DateTime now = DateTime.Now;
|
||||
return ((60 - now.Second) * 1000 - now.Millisecond);
|
||||
}
|
||||
|
||||
private void tsync_timer_Elapsed(object sender, System.Timers.ElapsedEventArgs e)
|
||||
{
|
||||
if (tsync_check.AddMinutes(Global.time_interval) < DateTime.Now)
|
||||
OmniLink.Controller.Connection.Send(new clsOL2MsgRequestSystemStatus(OmniLink.Controller.Connection), HandleRequestSystemStatus);
|
||||
|
||||
tsync_timer.Interval = TimeTimerInterval();
|
||||
tsync_timer.Start();
|
||||
}
|
||||
|
||||
private void HandleRequestSystemStatus(clsOmniLinkMessageQueueItem M, byte[] B, bool Timeout)
|
||||
{
|
||||
if (Timeout)
|
||||
return;
|
||||
|
||||
tsync_check = DateTime.Now;
|
||||
|
||||
clsOL2MsgSystemStatus MSG = new clsOL2MsgSystemStatus(OmniLink.Controller.Connection, B);
|
||||
|
||||
DateTime time;
|
||||
try
|
||||
{
|
||||
// The controller uses 2 digit years and C# uses 4 digit years
|
||||
// Extract the 2 digit prefix to use when parsing the time
|
||||
int year = DateTime.Now.Year / 100;
|
||||
|
||||
time = new DateTime((int)MSG.Year + (year * 100), (int)MSG.Month, (int)MSG.Day, (int)MSG.Hour, (int)MSG.Minute, (int)MSG.Second);
|
||||
}
|
||||
catch
|
||||
{
|
||||
log.Warn("Controller time could not be parsed");
|
||||
|
||||
DateTime now = DateTime.Now;
|
||||
OmniLink.Controller.Connection.Send(new clsOL2MsgSetTime(OmniLink.Controller.Connection, (byte)(now.Year % 100), (byte)now.Month, (byte)now.Day, (byte)now.DayOfWeek,
|
||||
(byte)now.Hour, (byte)now.Minute, (byte)(now.IsDaylightSavingTime() ? 1 : 0)), HandleSetTime);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
double adj = (DateTime.Now - time).Duration().TotalSeconds;
|
||||
|
||||
if (adj > Global.time_drift)
|
||||
{
|
||||
log.Warn("Controller time " + time.ToString("MM/dd/yyyy HH:mm:ss") + " out of sync by " + adj + " seconds");
|
||||
|
||||
DateTime now = DateTime.Now;
|
||||
OmniLink.Controller.Connection.Send(new clsOL2MsgSetTime(OmniLink.Controller.Connection, (byte)(now.Year % 100), (byte)now.Month, (byte)now.Day, (byte)now.DayOfWeek,
|
||||
(byte)now.Hour, (byte)now.Minute, (byte)(now.IsDaylightSavingTime() ? 1 : 0)), HandleSetTime);
|
||||
}
|
||||
}
|
||||
|
||||
private void HandleSetTime(clsOmniLinkMessageQueueItem M, byte[] B, bool Timeout)
|
||||
{
|
||||
if (Timeout)
|
||||
return;
|
||||
|
||||
log.Debug("Controller time has been successfully set");
|
||||
}
|
||||
}
|
||||
}
|
119
OmniLinkBridge/Modules/WebServiceModule.cs
Normal file
119
OmniLinkBridge/Modules/WebServiceModule.cs
Normal file
|
@ -0,0 +1,119 @@
|
|||
using HAI_Shared;
|
||||
using OmniLinkBridge.Modules;
|
||||
using OmniLinkBridge.OmniLink;
|
||||
using OmniLinkBridge.WebAPI;
|
||||
using log4net;
|
||||
using Newtonsoft.Json;
|
||||
using System;
|
||||
using System.Reflection;
|
||||
using System.ServiceModel;
|
||||
using System.ServiceModel.Description;
|
||||
using System.ServiceModel.Web;
|
||||
using System.Threading;
|
||||
|
||||
namespace OmniLinkBridge
|
||||
{
|
||||
public class WebServiceModule : IModule
|
||||
{
|
||||
private static ILog log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);
|
||||
|
||||
public static OmniLinkII OmniLink { get; private set; }
|
||||
|
||||
private WebServiceHost host;
|
||||
|
||||
private readonly AutoResetEvent trigger = new AutoResetEvent(false);
|
||||
|
||||
public WebServiceModule(OmniLinkII omni)
|
||||
{
|
||||
OmniLink = omni;
|
||||
OmniLink.OnAreaStatus += Omnilink_OnAreaStatus;
|
||||
OmniLink.OnZoneStatus += Omnilink_OnZoneStatus;
|
||||
OmniLink.OnUnitStatus += Omnilink_OnUnitStatus;
|
||||
OmniLink.OnThermostatStatus += Omnilink_OnThermostatStatus;
|
||||
}
|
||||
|
||||
public void Startup()
|
||||
{
|
||||
WebNotification.RestoreSubscriptions();
|
||||
|
||||
Uri uri = new Uri("http://0.0.0.0:" + Global.webapi_port + "/");
|
||||
host = new WebServiceHost(typeof(OmniLinkService), uri);
|
||||
|
||||
try
|
||||
{
|
||||
ServiceEndpoint ep = host.AddServiceEndpoint(typeof(IOmniLinkService), new WebHttpBinding(), "");
|
||||
host.Open();
|
||||
|
||||
log.Info("Listening on " + uri.ToString());
|
||||
}
|
||||
catch (CommunicationException ex)
|
||||
{
|
||||
log.Error("An exception occurred starting web service", ex);
|
||||
host.Abort();
|
||||
}
|
||||
|
||||
// Wait until shutdown
|
||||
trigger.WaitOne();
|
||||
|
||||
if (host != null)
|
||||
host.Close();
|
||||
|
||||
WebNotification.SaveSubscriptions();
|
||||
}
|
||||
|
||||
public void Shutdown()
|
||||
{
|
||||
trigger.Set();
|
||||
}
|
||||
|
||||
private void Omnilink_OnAreaStatus(object sender, AreaStatusEventArgs e)
|
||||
{
|
||||
WebNotification.Send("area", JsonConvert.SerializeObject(e.Area.ToContract()));
|
||||
}
|
||||
|
||||
private void Omnilink_OnZoneStatus(object sender, ZoneStatusEventArgs e)
|
||||
{
|
||||
if (e.Zone.IsTemperatureZone())
|
||||
{
|
||||
WebNotification.Send("temp", JsonConvert.SerializeObject(e.Zone.ToContract()));
|
||||
return;
|
||||
}
|
||||
|
||||
switch (e.Zone.ZoneType)
|
||||
{
|
||||
case enuZoneType.EntryExit:
|
||||
case enuZoneType.X2EntryDelay:
|
||||
case enuZoneType.X4EntryDelay:
|
||||
case enuZoneType.Perimeter:
|
||||
case enuZoneType.Tamper:
|
||||
case enuZoneType.Auxiliary:
|
||||
WebNotification.Send("contact", JsonConvert.SerializeObject(e.Zone.ToContract()));
|
||||
break;
|
||||
case enuZoneType.AwayInt:
|
||||
case enuZoneType.NightInt:
|
||||
WebNotification.Send("motion", JsonConvert.SerializeObject(e.Zone.ToContract()));
|
||||
break;
|
||||
case enuZoneType.Water:
|
||||
WebNotification.Send("water", JsonConvert.SerializeObject(e.Zone.ToContract()));
|
||||
break;
|
||||
case enuZoneType.Fire:
|
||||
WebNotification.Send("smoke", JsonConvert.SerializeObject(e.Zone.ToContract()));
|
||||
break;
|
||||
case enuZoneType.Gas:
|
||||
WebNotification.Send("co", JsonConvert.SerializeObject(e.Zone.ToContract()));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private void Omnilink_OnUnitStatus(object sender, UnitStatusEventArgs e)
|
||||
{
|
||||
WebNotification.Send("unit", JsonConvert.SerializeObject(e.Unit.ToContract()));
|
||||
}
|
||||
|
||||
private void Omnilink_OnThermostatStatus(object sender, ThermostatStatusEventArgs e)
|
||||
{
|
||||
if(!e.EventTimer)
|
||||
WebNotification.Send("thermostat", JsonConvert.SerializeObject(e.Thermostat.ToContract()));
|
||||
}
|
||||
}
|
||||
}
|
43
OmniLinkBridge/Notifications/EmailNotification.cs
Normal file
43
OmniLinkBridge/Notifications/EmailNotification.cs
Normal file
|
@ -0,0 +1,43 @@
|
|||
using log4net;
|
||||
using System;
|
||||
using System.Net;
|
||||
using System.Net.Mail;
|
||||
using System.Reflection;
|
||||
|
||||
namespace OmniLinkBridge.Notifications
|
||||
{
|
||||
public class EmailNotification : INotification
|
||||
{
|
||||
private static ILog log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);
|
||||
|
||||
public void Notify(string source, string description, NotificationPriority priority)
|
||||
{
|
||||
foreach (MailAddress address in Global.mail_to)
|
||||
{
|
||||
MailMessage mail = new MailMessage();
|
||||
mail.From = Global.mail_from;
|
||||
mail.To.Add(address);
|
||||
mail.Subject = "OmniLinkBridge - " + source;
|
||||
mail.Body = source + ": " + description;
|
||||
|
||||
using (SmtpClient smtp = new SmtpClient(Global.mail_server, Global.mail_port))
|
||||
{
|
||||
if (!string.IsNullOrEmpty(Global.mail_username))
|
||||
{
|
||||
smtp.UseDefaultCredentials = false;
|
||||
smtp.Credentials = new NetworkCredential(Global.mail_username, Global.mail_password);
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
smtp.Send(mail);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
log.Error("An error occurred sending notification", ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
7
OmniLinkBridge/Notifications/INotification.cs
Normal file
7
OmniLinkBridge/Notifications/INotification.cs
Normal file
|
@ -0,0 +1,7 @@
|
|||
namespace OmniLinkBridge.Notifications
|
||||
{
|
||||
public interface INotification
|
||||
{
|
||||
void Notify(string source, string description, NotificationPriority priority);
|
||||
}
|
||||
}
|
23
OmniLinkBridge/Notifications/Notification.cs
Normal file
23
OmniLinkBridge/Notifications/Notification.cs
Normal file
|
@ -0,0 +1,23 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace OmniLinkBridge.Notifications
|
||||
{
|
||||
public static class Notification
|
||||
{
|
||||
private static readonly List<INotification> providers = new List<INotification>()
|
||||
{
|
||||
new EmailNotification(),
|
||||
new ProwlNotification(),
|
||||
new PushoverNotification()
|
||||
};
|
||||
|
||||
public static void Notify(string source, string description, NotificationPriority priority = NotificationPriority.Normal)
|
||||
{
|
||||
Parallel.ForEach(providers, (provider) =>
|
||||
{
|
||||
provider.Notify(source, description, priority);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
30
OmniLinkBridge/Notifications/NotificationPriority.cs
Normal file
30
OmniLinkBridge/Notifications/NotificationPriority.cs
Normal file
|
@ -0,0 +1,30 @@
|
|||
namespace OmniLinkBridge.Notifications
|
||||
{
|
||||
public enum NotificationPriority
|
||||
{
|
||||
/// <summary>
|
||||
/// Generate no notification/alert
|
||||
/// </summary>
|
||||
VeryLow = -2,
|
||||
|
||||
/// <summary>
|
||||
/// Always send as a quiet notification
|
||||
/// </summary>
|
||||
Moderate = -1,
|
||||
|
||||
/// <summary>
|
||||
/// Trigger sound, vibration, and display an alert according to the user's device settings
|
||||
/// </summary>
|
||||
Normal = 0,
|
||||
|
||||
/// <summary>
|
||||
/// Display as high-priority and bypass the user's quiet hours
|
||||
/// </summary>
|
||||
High = 1,
|
||||
|
||||
/// <summary>
|
||||
/// Require confirmation from the user
|
||||
/// </summary>
|
||||
Emergency = 2,
|
||||
};
|
||||
}
|
42
OmniLinkBridge/Notifications/ProwlNotification.cs
Normal file
42
OmniLinkBridge/Notifications/ProwlNotification.cs
Normal file
|
@ -0,0 +1,42 @@
|
|||
using log4net;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Net;
|
||||
using System.Reflection;
|
||||
|
||||
namespace OmniLinkBridge.Notifications
|
||||
{
|
||||
public class ProwlNotification : INotification
|
||||
{
|
||||
private static ILog log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);
|
||||
|
||||
private static Uri URI = new Uri("https://api.prowlapp.com/publicapi/add");
|
||||
|
||||
public void Notify(string source, string description, NotificationPriority priority)
|
||||
{
|
||||
foreach (string key in Global.prowl_key)
|
||||
{
|
||||
List<string> parameters = new List<string>();
|
||||
|
||||
parameters.Add("apikey=" + key);
|
||||
parameters.Add("priority= " + (int)priority);
|
||||
parameters.Add("application=OmniLinkBridge");
|
||||
parameters.Add("event=" + source);
|
||||
parameters.Add("description=" + description);
|
||||
|
||||
using (WebClient client = new WebClient())
|
||||
{
|
||||
client.Headers[HttpRequestHeader.ContentType] = "application/x-www-form-urlencoded";
|
||||
client.UploadStringAsync(URI, string.Join("&", parameters.ToArray()));
|
||||
client.UploadStringCompleted += client_UploadStringCompleted;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void client_UploadStringCompleted(object sender, UploadStringCompletedEventArgs e)
|
||||
{
|
||||
if (e.Error != null)
|
||||
log.Error("An error occurred sending notification", e.Error);
|
||||
}
|
||||
}
|
||||
}
|
41
OmniLinkBridge/Notifications/PushoverNotification.cs
Normal file
41
OmniLinkBridge/Notifications/PushoverNotification.cs
Normal file
|
@ -0,0 +1,41 @@
|
|||
using log4net;
|
||||
using System;
|
||||
using System.Collections.Specialized;
|
||||
using System.Net;
|
||||
using System.Reflection;
|
||||
|
||||
namespace OmniLinkBridge.Notifications
|
||||
{
|
||||
public class PushoverNotification : INotification
|
||||
{
|
||||
private static ILog log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);
|
||||
|
||||
private static Uri URI = new Uri("https://api.pushover.net/1/messages.json");
|
||||
|
||||
public void Notify(string source, string description, NotificationPriority priority)
|
||||
{
|
||||
foreach (string key in Global.pushover_user)
|
||||
{
|
||||
var parameters = new NameValueCollection {
|
||||
{ "token", Global.pushover_token },
|
||||
{ "user", key },
|
||||
{ "priority", ((int)priority).ToString() },
|
||||
{ "title", source },
|
||||
{ "message", description }
|
||||
};
|
||||
|
||||
using (WebClient client = new WebClient())
|
||||
{
|
||||
client.UploadValues(URI, parameters);
|
||||
client.UploadStringCompleted += client_UploadStringCompleted;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void client_UploadStringCompleted(object sender, UploadStringCompletedEventArgs e)
|
||||
{
|
||||
if (e.Error != null)
|
||||
log.Error("An error occurred sending notification", e.Error);
|
||||
}
|
||||
}
|
||||
}
|
11
OmniLinkBridge/OmniLink/AreaStatusEventArgs.cs
Normal file
11
OmniLinkBridge/OmniLink/AreaStatusEventArgs.cs
Normal file
|
@ -0,0 +1,11 @@
|
|||
using HAI_Shared;
|
||||
using System;
|
||||
|
||||
namespace OmniLinkBridge.OmniLink
|
||||
{
|
||||
public class AreaStatusEventArgs : EventArgs
|
||||
{
|
||||
public ushort ID { get; set; }
|
||||
public clsArea Area { get; set; }
|
||||
}
|
||||
}
|
11
OmniLinkBridge/OmniLink/MessageStatusEventArgs.cs
Normal file
11
OmniLinkBridge/OmniLink/MessageStatusEventArgs.cs
Normal file
|
@ -0,0 +1,11 @@
|
|||
using HAI_Shared;
|
||||
using System;
|
||||
|
||||
namespace OmniLinkBridge.OmniLink
|
||||
{
|
||||
public class MessageStatusEventArgs : EventArgs
|
||||
{
|
||||
public ushort ID { get; set; }
|
||||
public clsMessage Message { get; set; }
|
||||
}
|
||||
}
|
12
OmniLinkBridge/OmniLink/SystemStatusEventArgs.cs
Normal file
12
OmniLinkBridge/OmniLink/SystemStatusEventArgs.cs
Normal file
|
@ -0,0 +1,12 @@
|
|||
using HAI_Shared;
|
||||
using System;
|
||||
|
||||
namespace OmniLinkBridge.OmniLink
|
||||
{
|
||||
public class SystemStatusEventArgs : EventArgs
|
||||
{
|
||||
public enuEventType Type { get; set; }
|
||||
public string Value { get; set; }
|
||||
public bool SendNotification { get; set; }
|
||||
}
|
||||
}
|
12
OmniLinkBridge/OmniLink/ThermostatStatusEventArgs.cs
Normal file
12
OmniLinkBridge/OmniLink/ThermostatStatusEventArgs.cs
Normal file
|
@ -0,0 +1,12 @@
|
|||
using HAI_Shared;
|
||||
using System;
|
||||
|
||||
namespace OmniLinkBridge.OmniLink
|
||||
{
|
||||
public class ThermostatStatusEventArgs : EventArgs
|
||||
{
|
||||
public ushort ID { get; set; }
|
||||
public clsThermostat Thermostat { get; set; }
|
||||
public bool EventTimer { get; set; }
|
||||
}
|
||||
}
|
11
OmniLinkBridge/OmniLink/UnitStatusEventArgs.cs
Normal file
11
OmniLinkBridge/OmniLink/UnitStatusEventArgs.cs
Normal file
|
@ -0,0 +1,11 @@
|
|||
using HAI_Shared;
|
||||
using System;
|
||||
|
||||
namespace OmniLinkBridge.OmniLink
|
||||
{
|
||||
public class UnitStatusEventArgs : EventArgs
|
||||
{
|
||||
public ushort ID { get; set; }
|
||||
public clsUnit Unit { get; set; }
|
||||
}
|
||||
}
|
11
OmniLinkBridge/OmniLink/ZoneStatusEventArgs.cs
Normal file
11
OmniLinkBridge/OmniLink/ZoneStatusEventArgs.cs
Normal file
|
@ -0,0 +1,11 @@
|
|||
using HAI_Shared;
|
||||
using System;
|
||||
|
||||
namespace OmniLinkBridge.OmniLink
|
||||
{
|
||||
public class ZoneStatusEventArgs : EventArgs
|
||||
{
|
||||
public ushort ID { get; set; }
|
||||
public clsZone Zone { get; set; }
|
||||
}
|
||||
}
|
170
OmniLinkBridge/OmniLinkBridge.csproj
Normal file
170
OmniLinkBridge/OmniLinkBridge.csproj
Normal file
|
@ -0,0 +1,170 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project ToolsVersion="12.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<PropertyGroup>
|
||||
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
|
||||
<Platform Condition=" '$(Platform)' == '' ">x86</Platform>
|
||||
<ProductVersion>8.0.30703</ProductVersion>
|
||||
<SchemaVersion>2.0</SchemaVersion>
|
||||
<ProjectGuid>{0A636707-98BA-45AB-9843-AED430933CEE}</ProjectGuid>
|
||||
<OutputType>Exe</OutputType>
|
||||
<AppDesignerFolder>Properties</AppDesignerFolder>
|
||||
<RootNamespace>OmniLinkBridge</RootNamespace>
|
||||
<AssemblyName>OmniLinkBridge</AssemblyName>
|
||||
<TargetFrameworkVersion>v4.5.2</TargetFrameworkVersion>
|
||||
<FileAlignment>512</FileAlignment>
|
||||
<TargetFrameworkProfile />
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|x86' ">
|
||||
<PlatformTarget>x86</PlatformTarget>
|
||||
<DebugSymbols>true</DebugSymbols>
|
||||
<DebugType>full</DebugType>
|
||||
<Optimize>false</Optimize>
|
||||
<OutputPath>bin\Debug\</OutputPath>
|
||||
<DefineConstants>DEBUG;TRACE</DefineConstants>
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
<WarningLevel>4</WarningLevel>
|
||||
<Prefer32Bit>false</Prefer32Bit>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|x86' ">
|
||||
<PlatformTarget>x86</PlatformTarget>
|
||||
<DebugType>pdbonly</DebugType>
|
||||
<Optimize>true</Optimize>
|
||||
<OutputPath>bin\Release\</OutputPath>
|
||||
<DefineConstants>TRACE</DefineConstants>
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
<WarningLevel>4</WarningLevel>
|
||||
<Prefer32Bit>false</Prefer32Bit>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Debug|AnyCPU'">
|
||||
<DebugSymbols>true</DebugSymbols>
|
||||
<OutputPath>bin\Debug\</OutputPath>
|
||||
<DefineConstants>DEBUG;TRACE</DefineConstants>
|
||||
<DebugType>full</DebugType>
|
||||
<PlatformTarget>AnyCPU</PlatformTarget>
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
<CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Release|AnyCPU'">
|
||||
<OutputPath>bin\Release\</OutputPath>
|
||||
<DefineConstants>TRACE</DefineConstants>
|
||||
<Optimize>true</Optimize>
|
||||
<DebugType>pdbonly</DebugType>
|
||||
<PlatformTarget>AnyCPU</PlatformTarget>
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
<CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup>
|
||||
<AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup>
|
||||
<StartupObject>OmniLinkBridge.Program</StartupObject>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<Reference Include="HAI.Controller">
|
||||
<HintPath>.\HAI.Controller.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="System" />
|
||||
<Reference Include="System.Configuration.Install" />
|
||||
<Reference Include="System.Data" />
|
||||
<Reference Include="System.Management" />
|
||||
<Reference Include="System.Runtime.Serialization" />
|
||||
<Reference Include="System.ServiceModel" />
|
||||
<Reference Include="System.ServiceModel.Web" />
|
||||
<Reference Include="System.ServiceProcess" />
|
||||
<Reference Include="System.Xml" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Compile Include="CoreServer.cs" />
|
||||
<Compile Include="Modules\TimeSyncModule.cs" />
|
||||
<Compile Include="MQTT\Alarm.cs" />
|
||||
<Compile Include="MQTT\BinarySensor.cs" />
|
||||
<Compile Include="MQTT\Device.cs" />
|
||||
<Compile Include="MQTT\Climate.cs" />
|
||||
<Compile Include="MQTT\OverrideZone.cs" />
|
||||
<Compile Include="MQTT\Switch.cs" />
|
||||
<Compile Include="MQTT\Light.cs" />
|
||||
<Compile Include="MQTT\MappingExtensions.cs" />
|
||||
<Compile Include="MQTT\Sensor.cs" />
|
||||
<Compile Include="MQTT\Topics.cs" />
|
||||
<Compile Include="Notifications\EmailNotification.cs" />
|
||||
<Compile Include="Notifications\INotification.cs" />
|
||||
<Compile Include="Notifications\Notification.cs" />
|
||||
<Compile Include="Notifications\NotificationPriority.cs" />
|
||||
<Compile Include="Notifications\PushoverNotification.cs" />
|
||||
<Compile Include="OmniLink\UnitStatusEventArgs.cs" />
|
||||
<Compile Include="OmniLink\ThermostatStatusEventArgs.cs" />
|
||||
<Compile Include="OmniLink\MessageStatusEventArgs.cs" />
|
||||
<Compile Include="OmniLink\SystemStatusEventArgs.cs" />
|
||||
<Compile Include="OmniLink\ZoneStatusEventArgs.cs" />
|
||||
<Compile Include="OmniLink\AreaStatusEventArgs.cs" />
|
||||
<Compile Include="Global.cs" />
|
||||
<Compile Include="Modules\IModule.cs" />
|
||||
<Compile Include="Modules\LoggerModule.cs" />
|
||||
<Compile Include="Modules\MQTTModule.cs" />
|
||||
<Compile Include="Modules\OmniLinkII.cs" />
|
||||
<Compile Include="WebService\OmniLinkService.cs" />
|
||||
<Compile Include="Extensions.cs" />
|
||||
<Compile Include="WebService\CommandContract.cs" />
|
||||
<Compile Include="WebService\IOmniLinkService.cs" />
|
||||
<Compile Include="Program.cs" />
|
||||
<Compile Include="ProjectInstaller.cs">
|
||||
<SubType>Component</SubType>
|
||||
</Compile>
|
||||
<Compile Include="ProjectInstaller.Designer.cs">
|
||||
<DependentUpon>ProjectInstaller.cs</DependentUpon>
|
||||
</Compile>
|
||||
<Compile Include="Properties\AssemblyInfo.cs" />
|
||||
<Compile Include="Notifications\ProwlNotification.cs" />
|
||||
<Compile Include="Service.cs">
|
||||
<SubType>Component</SubType>
|
||||
</Compile>
|
||||
<Compile Include="Service.Designer.cs">
|
||||
<DependentUpon>Service.cs</DependentUpon>
|
||||
</Compile>
|
||||
<Compile Include="Settings.cs" />
|
||||
<Compile Include="WebService\MappingExtensions.cs" />
|
||||
<Compile Include="WebService\SubscribeContract.cs" />
|
||||
<Compile Include="WebService\ThermostatContract.cs" />
|
||||
<Compile Include="WebService\NameContract.cs" />
|
||||
<Compile Include="WebService\AreaContract.cs" />
|
||||
<Compile Include="WebService\ZoneContract.cs" />
|
||||
<Compile Include="WebService\UnitContract.cs" />
|
||||
<Compile Include="WebService\WebNotification.cs" />
|
||||
<Compile Include="Modules\WebServiceModule.cs" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<None Include="App.config">
|
||||
<SubType>Designer</SubType>
|
||||
</None>
|
||||
<None Include="OmniLinkBridge.ini">
|
||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||
</None>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<EmbeddedResource Include="ProjectInstaller.resx">
|
||||
<DependentUpon>ProjectInstaller.cs</DependentUpon>
|
||||
</EmbeddedResource>
|
||||
<EmbeddedResource Include="Service.resx">
|
||||
<DependentUpon>Service.cs</DependentUpon>
|
||||
</EmbeddedResource>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="log4net">
|
||||
<Version>2.0.8</Version>
|
||||
</PackageReference>
|
||||
<PackageReference Include="MQTTnet.Extensions.ManagedClient">
|
||||
<Version>2.8.4</Version>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Newtonsoft.Json">
|
||||
<Version>11.0.2</Version>
|
||||
</PackageReference>
|
||||
</ItemGroup>
|
||||
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
|
||||
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
|
||||
Other similar extension points exist, see Microsoft.Common.targets.
|
||||
<Target Name="BeforeBuild">
|
||||
</Target>
|
||||
<Target Name="AfterBuild">
|
||||
</Target>
|
||||
-->
|
||||
</Project>
|
72
OmniLinkBridge/OmniLinkBridge.ini
Normal file
72
OmniLinkBridge/OmniLinkBridge.ini
Normal file
|
@ -0,0 +1,72 @@
|
|||
# HAI / Leviton Omni Controller
|
||||
controller_address =
|
||||
controller_port = 4369
|
||||
controller_key1 = 00-00-00-00-00-00-00-00
|
||||
controller_key2 = 00-00-00-00-00-00-00-00
|
||||
|
||||
# Controller Time Sync (yes/no)
|
||||
# time_check is interval in minutes to check controller time
|
||||
# time_adj is the drift in seconds to allow before an adjustment is made
|
||||
time_sync = yes
|
||||
time_interval = 60
|
||||
time_drift = 10
|
||||
|
||||
# Verbose Console (yes/no)
|
||||
verbose_unhandled = yes
|
||||
verbose_area = yes
|
||||
verbose_zone = yes
|
||||
verbose_thermostat_timer = yes
|
||||
verbose_thermostat = yes
|
||||
verbose_unit = yes
|
||||
verbose_message = yes
|
||||
|
||||
# mySQL Logging (yes/no)
|
||||
mysql_logging = no
|
||||
mysql_connection =
|
||||
|
||||
# Web Service (yes/no)
|
||||
# Can be used for integration with Samsung SmartThings
|
||||
webapi_enabled = yes
|
||||
webapi_port = 8000
|
||||
|
||||
# MQTT
|
||||
# Can be used for integration with Home Assistant
|
||||
mqtt_enabled = no
|
||||
mqtt_server =
|
||||
mqtt_port = 1883
|
||||
mqtt_username =
|
||||
mqtt_password =
|
||||
mqtt_discovery_prefix = homeassistant
|
||||
# specify a range of numbers like 1,2,3,5-10
|
||||
mqtt_discovery_ignore_zones =
|
||||
mqtt_discovery_ignore_units =
|
||||
# device_class must be battery, door, garage_door, gas, moisture, motion, problem, smoke, or window
|
||||
#mqtt_discovery_override_zone = id=5;device_class=garage_door
|
||||
#mqtt_discovery_override_zone = id=6;device_class=garage_door
|
||||
|
||||
# Notifications (yes/no)
|
||||
# Always sent for area alarms and critical system events
|
||||
# Optionally enable for area status changes and console messages
|
||||
notify_area = no
|
||||
notify_message = no
|
||||
|
||||
# Email Notifications
|
||||
# mail_username and mail_password optional for authenticated mail
|
||||
mail_server =
|
||||
mail_port = 25
|
||||
mail_username =
|
||||
mail_password =
|
||||
mail_from = OmniLinkBridge@localhost
|
||||
#mail_to =
|
||||
#mail_to =
|
||||
|
||||
# Prowl Notifications
|
||||
# Register for API key at http://www.prowlapp.com
|
||||
#prowl_key =
|
||||
#prowl_key =
|
||||
|
||||
# Pushover Notifications
|
||||
# Register for API token at http://www.pushover.net
|
||||
#pushover_token =
|
||||
#pushover_user =
|
||||
#pushover_user =
|
|
@ -4,7 +4,7 @@ using System.IO;
|
|||
using System.Reflection;
|
||||
using System.ServiceProcess;
|
||||
|
||||
namespace HAILogger
|
||||
namespace OmniLinkBridge
|
||||
{
|
||||
class Program
|
||||
{
|
||||
|
@ -26,8 +26,8 @@ namespace HAILogger
|
|||
case "-c":
|
||||
Global.config_file = args[++i];
|
||||
break;
|
||||
case "-l":
|
||||
Global.config_file = args[++i];
|
||||
case "-s":
|
||||
Global.webapi_subscriptions_file = args[++i];
|
||||
break;
|
||||
case "-i":
|
||||
interactive = true;
|
||||
|
@ -35,15 +35,15 @@ 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";
|
||||
Path.DirectorySeparatorChar + "OmniLinkBridge.ini";
|
||||
|
||||
Global.event_source = "HAI Logger";
|
||||
if (string.IsNullOrEmpty(Global.webapi_subscriptions_file))
|
||||
Global.webapi_subscriptions_file = Path.GetDirectoryName(Assembly.GetEntryAssembly().Location) +
|
||||
Path.DirectorySeparatorChar + "WebSubscriptions.json";
|
||||
|
||||
log4net.Config.XmlConfigurator.Configure();
|
||||
|
||||
try
|
||||
{
|
||||
|
@ -89,9 +89,9 @@ namespace HAILogger
|
|||
static void ShowHelp()
|
||||
{
|
||||
Console.WriteLine(
|
||||
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" +
|
||||
AppDomain.CurrentDomain.FriendlyName + " [-c config_file] [-s subscriptions_file] [-i]\n" +
|
||||
"\t-c Specifies the configuration file. Default is OmniLinkBridge.ini\n" +
|
||||
"\t-s Specifies the web api subscriptions file. Default is WebSubscriptions.json\n" +
|
||||
"\t-i Run in interactive mode");
|
||||
}
|
||||
}
|
|
@ -1,4 +1,4 @@
|
|||
namespace HAILogger
|
||||
namespace OmniLinkBridge
|
||||
{
|
||||
partial class ProjectInstaller
|
||||
{
|
||||
|
@ -38,7 +38,7 @@
|
|||
//
|
||||
// serviceInstaller
|
||||
//
|
||||
this.serviceInstaller.ServiceName = "HAILogger";
|
||||
this.serviceInstaller.ServiceName = "OmniLinkBridge";
|
||||
//
|
||||
// ProjectInstaller
|
||||
//
|
|
@ -4,7 +4,7 @@ using System.Collections.Generic;
|
|||
using System.ComponentModel;
|
||||
using System.Configuration.Install;
|
||||
|
||||
namespace HAILogger
|
||||
namespace OmniLinkBridge
|
||||
{
|
||||
[RunInstaller(true)]
|
||||
public partial class ProjectInstaller : System.Configuration.Install.Installer
|
|
@ -5,12 +5,12 @@ using System.Runtime.InteropServices;
|
|||
// General Information about an assembly is controlled through the following
|
||||
// set of attributes. Change these attribute values to modify the information
|
||||
// associated with an assembly.
|
||||
[assembly: AssemblyTitle("HAILogger")]
|
||||
[assembly: AssemblyTitle("OmniLinkBridge")]
|
||||
[assembly: AssemblyDescription("")]
|
||||
[assembly: AssemblyConfiguration("")]
|
||||
[assembly: AssemblyCompany("Excalibur Partners, LLC")]
|
||||
[assembly: AssemblyProduct("HAILogger")]
|
||||
[assembly: AssemblyCopyright("Copyright © Excalibur Partners, LLC 2016")]
|
||||
[assembly: AssemblyProduct("OmniLinkBridge")]
|
||||
[assembly: AssemblyCopyright("Copyright © Excalibur Partners, LLC 2018")]
|
||||
[assembly: AssemblyTrademark("")]
|
||||
[assembly: AssemblyCulture("")]
|
||||
|
||||
|
@ -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.8.0")]
|
||||
[assembly: AssemblyFileVersion("1.0.8.0")]
|
||||
[assembly: AssemblyVersion("1.1.0.0")]
|
||||
[assembly: AssemblyFileVersion("1.1.0.0")]
|
|
@ -1,4 +1,4 @@
|
|||
namespace HAILogger
|
||||
namespace OmniLinkBridge
|
||||
{
|
||||
partial class Service
|
||||
{
|
||||
|
@ -33,7 +33,7 @@
|
|||
//
|
||||
this.AutoLog = false;
|
||||
this.CanShutdown = true;
|
||||
this.ServiceName = "HAILogger";
|
||||
this.ServiceName = "OmniLinkBridge";
|
||||
|
||||
}
|
||||
|
|
@ -6,7 +6,7 @@ using System.Diagnostics;
|
|||
using System.ServiceProcess;
|
||||
using System.Text;
|
||||
|
||||
namespace HAILogger
|
||||
namespace OmniLinkBridge
|
||||
{
|
||||
partial class Service : ServiceBase
|
||||
{
|
|
@ -1,48 +1,37 @@
|
|||
using log4net;
|
||||
using OmniLinkBridge.MQTT;
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Specialized;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Net.Mail;
|
||||
using System.Reflection;
|
||||
|
||||
namespace HAILogger
|
||||
namespace OmniLinkBridge
|
||||
{
|
||||
static class Settings
|
||||
public static class Settings
|
||||
{
|
||||
private static ILog log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);
|
||||
|
||||
public static void LoadSettings()
|
||||
{
|
||||
NameValueCollection settings = LoadCollection(Global.config_file);
|
||||
|
||||
// HAI Controller
|
||||
Global.hai_address = settings["hai_address"];
|
||||
Global.hai_port = ValidatePort(settings, "hai_port");
|
||||
Global.hai_key1 = settings["hai_key1"];
|
||||
Global.hai_key2 = settings["hai_key2"];
|
||||
Global.hai_time_sync = ValidateYesNo(settings, "hai_time_sync");
|
||||
Global.hai_time_interval = ValidateInt(settings, "hai_time_interval");
|
||||
Global.hai_time_drift = ValidateInt(settings, "hai_time_drift");
|
||||
// HAI / Leviton Omni Controller
|
||||
Global.controller_address = settings["controller_address"];
|
||||
Global.controller_port = ValidatePort(settings, "controller_port");
|
||||
Global.controller_key1 = settings["controller_key1"];
|
||||
Global.controller_key2 = settings["controller_key2"];
|
||||
|
||||
// mySQL Database
|
||||
Global.mysql_logging = ValidateYesNo(settings, "mysql_logging");
|
||||
Global.mysql_connection = settings["mysql_connection"];
|
||||
// Controller Time Sync
|
||||
Global.time_sync = ValidateYesNo(settings, "time_sync");
|
||||
Global.time_interval = ValidateInt(settings, "time_interval");
|
||||
Global.time_drift = ValidateInt(settings, "time_drift");
|
||||
|
||||
// Events
|
||||
Global.mail_server = settings["mail_server"];
|
||||
Global.mail_port = ValidatePort(settings, "mail_port");
|
||||
Global.mail_username = settings["mail_username"];
|
||||
Global.mail_password = settings["mail_password"];
|
||||
Global.mail_from = ValidateMailFrom(settings, "mail_from");
|
||||
Global.mail_to = ValidateMailTo(settings, "mail_to");
|
||||
Global.mail_alarm_to = ValidateMailTo(settings, "mail_alarm_to");
|
||||
|
||||
// Prowl Notifications
|
||||
Global.prowl_key = ValidateMultipleStrings(settings, "prowl_key");
|
||||
Global.prowl_messages = ValidateYesNo(settings, "prowl_messages");
|
||||
|
||||
// Web Service
|
||||
Global.webapi_enabled = ValidateYesNo(settings, "webapi_enabled");
|
||||
Global.webapi_port = ValidatePort(settings, "webapi_port");
|
||||
|
||||
// Verbose Output
|
||||
// Verbose Console
|
||||
Global.verbose_unhandled = ValidateYesNo(settings, "verbose_unhandled");
|
||||
Global.verbose_event = ValidateYesNo(settings, "verbose_event");
|
||||
Global.verbose_area = ValidateYesNo(settings, "verbose_area");
|
||||
|
@ -51,6 +40,82 @@ namespace HAILogger
|
|||
Global.verbose_thermostat = ValidateYesNo(settings, "verbose_thermostat");
|
||||
Global.verbose_unit = ValidateYesNo(settings, "verbose_unit");
|
||||
Global.verbose_message = ValidateYesNo(settings, "verbose_message");
|
||||
|
||||
// mySQL Logging
|
||||
Global.mysql_logging = ValidateYesNo(settings, "mysql_logging");
|
||||
Global.mysql_connection = settings["mysql_connection"];
|
||||
|
||||
// Web Service
|
||||
Global.webapi_enabled = ValidateYesNo(settings, "webapi_enabled");
|
||||
Global.webapi_port = ValidatePort(settings, "webapi_port");
|
||||
|
||||
// MQTT
|
||||
Global.mqtt_enabled = ValidateYesNo(settings, "mqtt_enabled");
|
||||
Global.mqtt_server = settings["mqtt_server"];
|
||||
Global.mqtt_port = ValidatePort(settings, "mqtt_port");
|
||||
Global.mqtt_username = settings["mqtt_username"];
|
||||
Global.mqtt_password = settings["mqtt_password"];
|
||||
Global.mqtt_discovery_prefix = settings["mqtt_discovery_prefix"];
|
||||
Global.mqtt_discovery_ignore_zones = ValidateRange(settings, "mqtt_discovery_ignore_zones");
|
||||
Global.mqtt_discovery_ignore_units = ValidateRange(settings, "mqtt_discovery_ignore_units");
|
||||
Global.mqtt_discovery_override_zone = LoadOverrideZone(settings, "mqtt_discovery_override_zone");
|
||||
|
||||
// Notifications
|
||||
Global.notify_area = ValidateYesNo(settings, "notify_area");
|
||||
Global.notify_message = ValidateYesNo(settings, "notify_message");
|
||||
|
||||
// Email Notifications
|
||||
Global.mail_server = settings["mail_server"];
|
||||
Global.mail_port = ValidatePort(settings, "mail_port");
|
||||
Global.mail_username = settings["mail_username"];
|
||||
Global.mail_password = settings["mail_password"];
|
||||
Global.mail_from = ValidateMailFrom(settings, "mail_from");
|
||||
Global.mail_to = ValidateMailTo(settings, "mail_to");
|
||||
|
||||
// Prowl Notifications
|
||||
Global.prowl_key = ValidateMultipleStrings(settings, "prowl_key");
|
||||
|
||||
// Pushover Notifications
|
||||
Global.pushover_token = settings["pushover_token"];
|
||||
Global.pushover_user = ValidateMultipleStrings(settings, "pushover_user");
|
||||
}
|
||||
|
||||
private static ConcurrentDictionary<int, OverrideZone> LoadOverrideZone(NameValueCollection settings, string section)
|
||||
{
|
||||
try
|
||||
{
|
||||
ConcurrentDictionary<int, OverrideZone> ret = new ConcurrentDictionary<int, OverrideZone>();
|
||||
|
||||
if (settings[section] == null)
|
||||
return ret;
|
||||
|
||||
string[] ids = settings[section].Split(',');
|
||||
|
||||
for (int i = 0; i < ids.Length; i++)
|
||||
{
|
||||
Dictionary<string, string> attributes = ids[i].TrimEnd(new char[] { ';' }).Split(';')
|
||||
.Select(s => s.Split('='))
|
||||
.ToDictionary(a => a[0].Trim(), a => a[1].Trim(), StringComparer.InvariantCultureIgnoreCase);
|
||||
|
||||
if (!attributes.ContainsKey("id") || !Int32.TryParse(attributes["id"], out int attrib_id))
|
||||
throw new Exception("Missing or invalid id attribute");
|
||||
|
||||
if (!attributes.ContainsKey("device_class") || !Enum.TryParse(attributes["device_class"], out BinarySensor.DeviceClass attrib_device_class))
|
||||
throw new Exception("Missing or invalid device_class attribute");
|
||||
|
||||
ret.TryAdd(attrib_id, new OverrideZone()
|
||||
{
|
||||
device_class = attrib_device_class,
|
||||
});
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
log.Error("Invalid override zone specified for " + section, ex);
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
private static int ValidateInt(NameValueCollection settings, string section)
|
||||
|
@ -61,7 +126,20 @@ namespace HAILogger
|
|||
}
|
||||
catch
|
||||
{
|
||||
Event.WriteError("Settings", "Invalid integer specified for " + section);
|
||||
log.Error("Invalid integer specified for " + section);
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
private static HashSet<int> ValidateRange(NameValueCollection settings, string section)
|
||||
{
|
||||
try
|
||||
{
|
||||
return new HashSet<int>(settings[section].ParseRanges());
|
||||
}
|
||||
catch
|
||||
{
|
||||
log.Error("Invalid range specified for " + section);
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
@ -79,7 +157,7 @@ namespace HAILogger
|
|||
}
|
||||
catch
|
||||
{
|
||||
Event.WriteError("Settings", "Invalid port specified for " + section);
|
||||
log.Error("Invalid port specified for " + section);
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
@ -92,7 +170,7 @@ namespace HAILogger
|
|||
}
|
||||
catch
|
||||
{
|
||||
Event.WriteError("Settings", "Invalid bool specified for " + section);
|
||||
log.Error("Invalid bool specified for " + section);
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
@ -111,7 +189,7 @@ namespace HAILogger
|
|||
}
|
||||
catch
|
||||
{
|
||||
Event.WriteError("Settings", "Invalid IP specified for " + section);
|
||||
log.Error("Invalid IP specified for " + section);
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
@ -127,7 +205,7 @@ namespace HAILogger
|
|||
}
|
||||
catch
|
||||
{
|
||||
Event.WriteError("Settings", "Invalid directory specified for " + section);
|
||||
log.Error("Invalid directory specified for " + section);
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
@ -140,7 +218,7 @@ namespace HAILogger
|
|||
}
|
||||
catch
|
||||
{
|
||||
Event.WriteError("Settings", "Invalid email specified for " + section);
|
||||
log.Error("Invalid email specified for " + section);
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
@ -162,7 +240,7 @@ namespace HAILogger
|
|||
}
|
||||
catch
|
||||
{
|
||||
Event.WriteError("Settings", "Invalid email specified for " + section);
|
||||
log.Error("Invalid email specified for " + section);
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
@ -178,7 +256,7 @@ namespace HAILogger
|
|||
}
|
||||
catch
|
||||
{
|
||||
Event.WriteError("Settings", "Invalid string specified for " + section);
|
||||
log.Error("Invalid string specified for " + section);
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
@ -193,7 +271,7 @@ namespace HAILogger
|
|||
return false;
|
||||
else
|
||||
{
|
||||
Event.WriteError("Settings", "Invalid yes/no specified for " + section);
|
||||
log.Error("Invalid yes/no specified for " + section);
|
||||
throw new Exception();
|
||||
}
|
||||
}
|
||||
|
@ -231,9 +309,9 @@ namespace HAILogger
|
|||
sr.Close();
|
||||
fs.Close();
|
||||
}
|
||||
catch (FileNotFoundException)
|
||||
catch (FileNotFoundException ex)
|
||||
{
|
||||
Event.WriteError("Settings", "Unable to parse settings file " + sFile);
|
||||
log.Error("Unable to parse settings file " + sFile, ex);
|
||||
throw;
|
||||
}
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
using System.Runtime.Serialization;
|
||||
|
||||
namespace HAILogger
|
||||
namespace OmniLinkBridge.WebAPI
|
||||
{
|
||||
[DataContract]
|
||||
public class AreaContract
|
|
@ -1,6 +1,6 @@
|
|||
using System.Runtime.Serialization;
|
||||
|
||||
namespace HAILogger
|
||||
namespace OmniLinkBridge.WebAPI
|
||||
{
|
||||
[DataContract]
|
||||
public class CommandContract
|
|
@ -2,10 +2,10 @@
|
|||
using System.ServiceModel;
|
||||
using System.ServiceModel.Web;
|
||||
|
||||
namespace HAILogger
|
||||
namespace OmniLinkBridge.WebAPI
|
||||
{
|
||||
[ServiceContract]
|
||||
public interface IHAIService
|
||||
public interface IOmniLinkService
|
||||
{
|
||||
[OperationContract]
|
||||
[WebInvoke(Method = "POST", RequestFormat = WebMessageFormat.Json, ResponseFormat = WebMessageFormat.Json)]
|
|
@ -1,21 +1,22 @@
|
|||
using HAI_Shared;
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Runtime.Serialization.Json;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace HAILogger
|
||||
namespace OmniLinkBridge.WebAPI
|
||||
{
|
||||
static class Helper
|
||||
public static class MappingExtensions
|
||||
{
|
||||
private static string lastmode = "OFF";
|
||||
|
||||
public static AreaContract ConvertArea(ushort id, clsArea area)
|
||||
public static AreaContract ToContract(this clsArea area)
|
||||
{
|
||||
AreaContract ret = new AreaContract();
|
||||
|
||||
ret.id = id;
|
||||
ret.name = area.Name;
|
||||
ret.id = (ushort)area.Number;
|
||||
ret.name = area.Name;
|
||||
ret.burglary = area.AreaBurglaryAlarmText;
|
||||
ret.co = area.AreaGasAlarmText;
|
||||
ret.fire = area.AreaFireAlarmText;
|
||||
|
@ -34,11 +35,11 @@ namespace HAILogger
|
|||
return ret;
|
||||
}
|
||||
|
||||
public static ZoneContract ConvertZone(ushort id, clsZone zone)
|
||||
public static ZoneContract ToContract(this clsZone zone)
|
||||
{
|
||||
ZoneContract ret = new ZoneContract();
|
||||
|
||||
ret.id = id;
|
||||
ret.id = (ushort)zone.Number;
|
||||
ret.zonetype = zone.ZoneType;
|
||||
ret.name = zone.Name;
|
||||
ret.status = zone.StatusText();
|
||||
|
@ -47,11 +48,11 @@ namespace HAILogger
|
|||
return ret;
|
||||
}
|
||||
|
||||
public static UnitContract ConvertUnit(ushort id, clsUnit unit)
|
||||
public static UnitContract ToContract(this clsUnit unit)
|
||||
{
|
||||
UnitContract ret = new UnitContract();
|
||||
|
||||
ret.id = id;
|
||||
ret.id = (ushort)unit.Number;
|
||||
ret.name = unit.Name;
|
||||
|
||||
if (unit.Status > 100)
|
||||
|
@ -64,15 +65,15 @@ namespace HAILogger
|
|||
return ret;
|
||||
}
|
||||
|
||||
public static ThermostatContract ConvertThermostat(ushort id, clsThermostat unit)
|
||||
public static ThermostatContract ToContract(this clsThermostat unit)
|
||||
{
|
||||
ThermostatContract ret = new ThermostatContract();
|
||||
|
||||
ret.id = id;
|
||||
ret.id = (ushort)unit.Number;
|
||||
ret.name = unit.Name;
|
||||
|
||||
ushort temp, heat, cool, humidity;
|
||||
|
||||
|
||||
ushort.TryParse(unit.TempText(), out temp);
|
||||
ushort.TryParse(unit.HeatSetpointText(), out heat);
|
||||
ushort.TryParse(unit.CoolSetpointText(), out cool);
|
||||
|
@ -97,22 +98,5 @@ namespace HAILogger
|
|||
|
||||
return ret;
|
||||
}
|
||||
|
||||
public static int ConvertTemperature(int f)
|
||||
{
|
||||
// Convert to celsius
|
||||
double c = 5.0 / 9.0 * (f - 32);
|
||||
|
||||
// Convert to omni temp (0 is -40C and 255 is 87.5C)
|
||||
return (int)Math.Round((c + 40) * 2, 0);
|
||||
}
|
||||
|
||||
public static string Serialize<T>(T obj)
|
||||
{
|
||||
MemoryStream stream = new MemoryStream();
|
||||
DataContractJsonSerializer ser = new DataContractJsonSerializer(typeof(T));
|
||||
ser.WriteObject(stream, obj);
|
||||
return Encoding.UTF8.GetString(stream.ToArray());
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,6 +1,6 @@
|
|||
using System.Runtime.Serialization;
|
||||
|
||||
namespace HAILogger
|
||||
namespace OmniLinkBridge.WebAPI
|
||||
{
|
||||
[DataContract]
|
||||
public class NameContract
|
|
@ -1,28 +1,32 @@
|
|||
using HAI_Shared;
|
||||
using log4net;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Reflection;
|
||||
using System.ServiceModel;
|
||||
using System.ServiceModel.Web;
|
||||
|
||||
namespace HAILogger
|
||||
namespace OmniLinkBridge.WebAPI
|
||||
{
|
||||
[ServiceBehavior(IncludeExceptionDetailInFaults = true)]
|
||||
public class HAIService : IHAIService
|
||||
public class OmniLinkService : IOmniLinkService
|
||||
{
|
||||
private static ILog log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);
|
||||
|
||||
public void Subscribe(SubscribeContract contract)
|
||||
{
|
||||
Event.WriteVerbose("WebService", "Subscribe");
|
||||
log.Debug("Subscribe");
|
||||
WebNotification.AddSubscription(contract.callback);
|
||||
}
|
||||
|
||||
public List<NameContract> ListAreas()
|
||||
{
|
||||
Event.WriteVerbose("WebService", "ListAreas");
|
||||
log.Debug("ListAreas");
|
||||
|
||||
List<NameContract> names = new List<NameContract>();
|
||||
for (ushort i = 1; i < WebService.HAC.Areas.Count; i++)
|
||||
for (ushort i = 1; i < WebServiceModule.OmniLink.Controller.Areas.Count; i++)
|
||||
{
|
||||
clsArea area = WebService.HAC.Areas[i];
|
||||
clsArea area = WebServiceModule.OmniLink.Controller.Areas[i];
|
||||
|
||||
if (area.DefaultProperties == false)
|
||||
names.Add(new NameContract() { id = i, name = area.Name });
|
||||
|
@ -32,23 +36,22 @@ namespace HAILogger
|
|||
|
||||
public AreaContract GetArea(ushort id)
|
||||
{
|
||||
Event.WriteVerbose("WebService", "GetArea: " + id);
|
||||
log.Debug("GetArea: " + id);
|
||||
|
||||
WebOperationContext ctx = WebOperationContext.Current;
|
||||
ctx.OutgoingResponse.Headers.Add("type", "area");
|
||||
|
||||
clsArea area = WebService.HAC.Areas[id];
|
||||
return Helper.ConvertArea(id, area);
|
||||
return WebServiceModule.OmniLink.Controller.Areas[id].ToContract();
|
||||
}
|
||||
|
||||
public List<NameContract> ListZonesContact()
|
||||
{
|
||||
Event.WriteVerbose("WebService", "ListZonesContact");
|
||||
log.Debug("ListZonesContact");
|
||||
|
||||
List<NameContract> names = new List<NameContract>();
|
||||
for (ushort i = 1; i < WebService.HAC.Zones.Count; i++)
|
||||
for (ushort i = 1; i < WebServiceModule.OmniLink.Controller.Zones.Count; i++)
|
||||
{
|
||||
clsZone zone = WebService.HAC.Zones[i];
|
||||
clsZone zone = WebServiceModule.OmniLink.Controller.Zones[i];
|
||||
|
||||
if ((zone.ZoneType == enuZoneType.EntryExit ||
|
||||
zone.ZoneType == enuZoneType.X2EntryDelay ||
|
||||
|
@ -63,12 +66,12 @@ namespace HAILogger
|
|||
|
||||
public List<NameContract> ListZonesMotion()
|
||||
{
|
||||
Event.WriteVerbose("WebService", "ListZonesMotion");
|
||||
log.Debug("ListZonesMotion");
|
||||
|
||||
List<NameContract> names = new List<NameContract>();
|
||||
for (ushort i = 1; i < WebService.HAC.Zones.Count; i++)
|
||||
for (ushort i = 1; i < WebServiceModule.OmniLink.Controller.Zones.Count; i++)
|
||||
{
|
||||
clsZone zone = WebService.HAC.Zones[i];
|
||||
clsZone zone = WebServiceModule.OmniLink.Controller.Zones[i];
|
||||
|
||||
if ((zone.ZoneType == enuZoneType.AwayInt ||
|
||||
zone.ZoneType == enuZoneType.NightInt) && zone.DefaultProperties == false)
|
||||
|
@ -79,12 +82,12 @@ namespace HAILogger
|
|||
|
||||
public List<NameContract> ListZonesWater()
|
||||
{
|
||||
Event.WriteVerbose("WebService", "ListZonesWater");
|
||||
log.Debug("ListZonesWater");
|
||||
|
||||
List<NameContract> names = new List<NameContract>();
|
||||
for (ushort i = 1; i < WebService.HAC.Zones.Count; i++)
|
||||
for (ushort i = 1; i < WebServiceModule.OmniLink.Controller.Zones.Count; i++)
|
||||
{
|
||||
clsZone zone = WebService.HAC.Zones[i];
|
||||
clsZone zone = WebServiceModule.OmniLink.Controller.Zones[i];
|
||||
|
||||
if (zone.ZoneType == enuZoneType.Water && zone.DefaultProperties == false)
|
||||
names.Add(new NameContract() { id = i, name = zone.Name });
|
||||
|
@ -94,12 +97,12 @@ namespace HAILogger
|
|||
|
||||
public List<NameContract> ListZonesSmoke()
|
||||
{
|
||||
Event.WriteVerbose("WebService", "ListZonesSmoke");
|
||||
log.Debug("ListZonesSmoke");
|
||||
|
||||
List<NameContract> names = new List<NameContract>();
|
||||
for (ushort i = 1; i < WebService.HAC.Zones.Count; i++)
|
||||
for (ushort i = 1; i < WebServiceModule.OmniLink.Controller.Zones.Count; i++)
|
||||
{
|
||||
clsZone zone = WebService.HAC.Zones[i];
|
||||
clsZone zone = WebServiceModule.OmniLink.Controller.Zones[i];
|
||||
|
||||
if (zone.ZoneType == enuZoneType.Fire && zone.DefaultProperties == false)
|
||||
names.Add(new NameContract() { id = i, name = zone.Name });
|
||||
|
@ -109,12 +112,12 @@ namespace HAILogger
|
|||
|
||||
public List<NameContract> ListZonesCO()
|
||||
{
|
||||
Event.WriteVerbose("WebService", "ListZonesCO");
|
||||
log.Debug("ListZonesCO");
|
||||
|
||||
List<NameContract> names = new List<NameContract>();
|
||||
for (ushort i = 1; i < WebService.HAC.Zones.Count; i++)
|
||||
for (ushort i = 1; i < WebServiceModule.OmniLink.Controller.Zones.Count; i++)
|
||||
{
|
||||
clsZone zone = WebService.HAC.Zones[i];
|
||||
clsZone zone = WebServiceModule.OmniLink.Controller.Zones[i];
|
||||
|
||||
if (zone.ZoneType == enuZoneType.Gas && zone.DefaultProperties == false)
|
||||
names.Add(new NameContract() { id = i, name = zone.Name });
|
||||
|
@ -124,12 +127,12 @@ namespace HAILogger
|
|||
|
||||
public List<NameContract> ListZonesTemp()
|
||||
{
|
||||
Event.WriteVerbose("WebService", "ListZonesTemp");
|
||||
log.Debug("ListZonesTemp");
|
||||
|
||||
List<NameContract> names = new List<NameContract>();
|
||||
for (ushort i = 1; i < WebService.HAC.Zones.Count; i++)
|
||||
for (ushort i = 1; i < WebServiceModule.OmniLink.Controller.Zones.Count; i++)
|
||||
{
|
||||
clsZone zone = WebService.HAC.Zones[i];
|
||||
clsZone zone = WebServiceModule.OmniLink.Controller.Zones[i];
|
||||
|
||||
if (zone.IsTemperatureZone() && zone.DefaultProperties == false)
|
||||
names.Add(new NameContract() { id = i, name = zone.Name });
|
||||
|
@ -139,17 +142,17 @@ namespace HAILogger
|
|||
|
||||
public ZoneContract GetZone(ushort id)
|
||||
{
|
||||
Event.WriteVerbose("WebService", "GetZone: " + id);
|
||||
log.Debug("GetZone: " + id);
|
||||
|
||||
WebOperationContext ctx = WebOperationContext.Current;
|
||||
|
||||
if (WebService.HAC.Zones[id].IsTemperatureZone())
|
||||
if (WebServiceModule.OmniLink.Controller.Zones[id].IsTemperatureZone())
|
||||
{
|
||||
ctx.OutgoingResponse.Headers.Add("type", "temp");
|
||||
}
|
||||
else
|
||||
{
|
||||
switch (WebService.HAC.Zones[id].ZoneType)
|
||||
switch (WebServiceModule.OmniLink.Controller.Zones[id].ZoneType)
|
||||
{
|
||||
case enuZoneType.EntryExit:
|
||||
case enuZoneType.X2EntryDelay:
|
||||
|
@ -178,18 +181,17 @@ namespace HAILogger
|
|||
}
|
||||
}
|
||||
|
||||
clsZone unit = WebService.HAC.Zones[id];
|
||||
return Helper.ConvertZone(id, unit);
|
||||
return WebServiceModule.OmniLink.Controller.Zones[id].ToContract();
|
||||
}
|
||||
|
||||
public List<NameContract> ListUnits()
|
||||
{
|
||||
Event.WriteVerbose("WebService", "ListUnits");
|
||||
log.Debug("ListUnits");
|
||||
|
||||
List<NameContract> names = new List<NameContract>();
|
||||
for (ushort i = 1; i < WebService.HAC.Units.Count; i++)
|
||||
for (ushort i = 1; i < WebServiceModule.OmniLink.Controller.Units.Count; i++)
|
||||
{
|
||||
clsUnit unit = WebService.HAC.Units[i];
|
||||
clsUnit unit = WebServiceModule.OmniLink.Controller.Units[i];
|
||||
|
||||
if (unit.DefaultProperties == false)
|
||||
names.Add(new NameContract() { id = i, name = unit.Name });
|
||||
|
@ -199,42 +201,41 @@ namespace HAILogger
|
|||
|
||||
public UnitContract GetUnit(ushort id)
|
||||
{
|
||||
Event.WriteVerbose("WebService", "GetUnit: " + id);
|
||||
log.Debug("GetUnit: " + id);
|
||||
|
||||
WebOperationContext ctx = WebOperationContext.Current;
|
||||
ctx.OutgoingResponse.Headers.Add("type", "unit");
|
||||
|
||||
clsUnit unit = WebService.HAC.Units[id];
|
||||
return Helper.ConvertUnit(id, unit);
|
||||
return WebServiceModule.OmniLink.Controller.Units[id].ToContract();
|
||||
}
|
||||
|
||||
public void SetUnit(CommandContract unit)
|
||||
{
|
||||
Event.WriteVerbose("WebService", "SetUnit: " + unit.id + " to " + unit.value + "%");
|
||||
log.Debug("SetUnit: " + unit.id + " to " + unit.value + "%");
|
||||
|
||||
if (unit.value == 0)
|
||||
WebService.HAC.SendCommand(enuUnitCommand.Off, 0, unit.id);
|
||||
WebServiceModule.OmniLink.Controller.SendCommand(enuUnitCommand.Off, 0, unit.id);
|
||||
else if (unit.value == 100)
|
||||
WebService.HAC.SendCommand(enuUnitCommand.On, 0, unit.id);
|
||||
WebServiceModule.OmniLink.Controller.SendCommand(enuUnitCommand.On, 0, unit.id);
|
||||
else
|
||||
WebService.HAC.SendCommand(enuUnitCommand.Level, BitConverter.GetBytes(unit.value)[0], unit.id);
|
||||
WebServiceModule.OmniLink.Controller.SendCommand(enuUnitCommand.Level, BitConverter.GetBytes(unit.value)[0], unit.id);
|
||||
}
|
||||
|
||||
|
||||
public void SetUnitKeypadPress(CommandContract unit)
|
||||
{
|
||||
Event.WriteVerbose("WebService", "SetUnitKeypadPress: " + unit.id + " to " + unit.value + " button");
|
||||
WebService.HAC.SendCommand(enuUnitCommand.LutronHomeWorksKeypadButtonPress, BitConverter.GetBytes(unit.value)[0], unit.id);
|
||||
log.Debug("SetUnitKeypadPress: " + unit.id + " to " + unit.value + " button");
|
||||
WebServiceModule.OmniLink.Controller.SendCommand(enuUnitCommand.LutronHomeWorksKeypadButtonPress, BitConverter.GetBytes(unit.value)[0], unit.id);
|
||||
}
|
||||
|
||||
public List<NameContract> ListThermostats()
|
||||
{
|
||||
Event.WriteVerbose("WebService", "ListThermostats");
|
||||
log.Debug("ListThermostats");
|
||||
|
||||
List<NameContract> names = new List<NameContract>();
|
||||
for (ushort i = 1; i < WebService.HAC.Thermostats.Count; i++)
|
||||
for (ushort i = 1; i < WebServiceModule.OmniLink.Controller.Thermostats.Count; i++)
|
||||
{
|
||||
clsThermostat unit = WebService.HAC.Thermostats[i];
|
||||
clsThermostat unit = WebServiceModule.OmniLink.Controller.Thermostats[i];
|
||||
|
||||
if (unit.DefaultProperties == false)
|
||||
names.Add(new NameContract() { id = i, name = unit.Name });
|
||||
|
@ -244,55 +245,54 @@ namespace HAILogger
|
|||
|
||||
public ThermostatContract GetThermostat(ushort id)
|
||||
{
|
||||
Event.WriteVerbose("WebService", "GetThermostat: " + id);
|
||||
log.Debug("GetThermostat: " + id);
|
||||
|
||||
WebOperationContext ctx = WebOperationContext.Current;
|
||||
ctx.OutgoingResponse.Headers.Add("type", "thermostat");
|
||||
|
||||
clsThermostat unit = WebService.HAC.Thermostats[id];
|
||||
return Helper.ConvertThermostat(id, unit);
|
||||
return WebServiceModule.OmniLink.Controller.Thermostats[id].ToContract();
|
||||
}
|
||||
|
||||
public void SetThermostatCoolSetpoint(CommandContract unit)
|
||||
{
|
||||
int temp = Helper.ConvertTemperature(unit.value);
|
||||
Event.WriteVerbose("WebService", "SetThermostatCoolSetpoint: " + unit.id + " to " + unit.value + "F (" + temp + ")");
|
||||
WebService.HAC.SendCommand(enuUnitCommand.SetHighSetPt, BitConverter.GetBytes(temp)[0], unit.id);
|
||||
int temp = ((double)unit.value).ToCelsius().ToOmniTemp();
|
||||
log.Debug("SetThermostatCoolSetpoint: " + unit.id + " to " + unit.value + "F (" + temp + ")");
|
||||
WebServiceModule.OmniLink.Controller.SendCommand(enuUnitCommand.SetHighSetPt, BitConverter.GetBytes(temp)[0], unit.id);
|
||||
}
|
||||
|
||||
public void SetThermostatHeatSetpoint(CommandContract unit)
|
||||
{
|
||||
int temp = Helper.ConvertTemperature(unit.value);
|
||||
Event.WriteVerbose("WebService", "SetThermostatCoolSetpoint: " + unit.id + " to " + unit.value + "F (" + temp + ")");
|
||||
WebService.HAC.SendCommand(enuUnitCommand.SetLowSetPt, BitConverter.GetBytes(temp)[0], unit.id);
|
||||
int temp = ((double)unit.value).ToCelsius().ToOmniTemp();
|
||||
log.Debug("SetThermostatCoolSetpoint: " + unit.id + " to " + unit.value + "F (" + temp + ")");
|
||||
WebServiceModule.OmniLink.Controller.SendCommand(enuUnitCommand.SetLowSetPt, BitConverter.GetBytes(temp)[0], unit.id);
|
||||
}
|
||||
|
||||
public void SetThermostatMode(CommandContract unit)
|
||||
{
|
||||
Event.WriteVerbose("WebService", "SetThermostatMode: " + unit.id + " to " + unit.value);
|
||||
WebService.HAC.SendCommand(enuUnitCommand.Mode, BitConverter.GetBytes(unit.value)[0], unit.id);
|
||||
log.Debug("SetThermostatMode: " + unit.id + " to " + unit.value);
|
||||
WebServiceModule.OmniLink.Controller.SendCommand(enuUnitCommand.Mode, BitConverter.GetBytes(unit.value)[0], unit.id);
|
||||
}
|
||||
|
||||
public void SetThermostatFanMode(CommandContract unit)
|
||||
{
|
||||
Event.WriteVerbose("WebService", "SetThermostatFanMode: " + unit.id + " to " + unit.value);
|
||||
WebService.HAC.SendCommand(enuUnitCommand.Fan, BitConverter.GetBytes(unit.value)[0], unit.id);
|
||||
log.Debug("SetThermostatFanMode: " + unit.id + " to " + unit.value);
|
||||
WebServiceModule.OmniLink.Controller.SendCommand(enuUnitCommand.Fan, BitConverter.GetBytes(unit.value)[0], unit.id);
|
||||
}
|
||||
|
||||
public void SetThermostatHold(CommandContract unit)
|
||||
{
|
||||
Event.WriteVerbose("WebService", "SetThermostatHold: " + unit.id + " to " + unit.value);
|
||||
WebService.HAC.SendCommand(enuUnitCommand.Hold, BitConverter.GetBytes(unit.value)[0], unit.id);
|
||||
log.Debug("SetThermostatHold: " + unit.id + " to " + unit.value);
|
||||
WebServiceModule.OmniLink.Controller.SendCommand(enuUnitCommand.Hold, BitConverter.GetBytes(unit.value)[0], unit.id);
|
||||
}
|
||||
|
||||
public List<NameContract> ListButtons()
|
||||
{
|
||||
Event.WriteVerbose("WebService", "ListButtons");
|
||||
log.Debug("ListButtons");
|
||||
|
||||
List<NameContract> names = new List<NameContract>();
|
||||
for (ushort i = 1; i < WebService.HAC.Buttons.Count; i++)
|
||||
for (ushort i = 1; i < WebServiceModule.OmniLink.Controller.Buttons.Count; i++)
|
||||
{
|
||||
clsButton unit = WebService.HAC.Buttons[i];
|
||||
clsButton unit = WebServiceModule.OmniLink.Controller.Buttons[i];
|
||||
|
||||
if (unit.DefaultProperties == false)
|
||||
names.Add(new NameContract() { id = i, name = unit.Name });
|
||||
|
@ -302,8 +302,8 @@ namespace HAILogger
|
|||
|
||||
public void PushButton(CommandContract unit)
|
||||
{
|
||||
Event.WriteVerbose("WebService", "PushButton: " + unit.id);
|
||||
WebService.HAC.SendCommand(enuUnitCommand.Button, 0, unit.id);
|
||||
log.Debug("PushButton: " + unit.id);
|
||||
WebServiceModule.OmniLink.Controller.SendCommand(enuUnitCommand.Button, 0, unit.id);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,6 +1,6 @@
|
|||
using System.Runtime.Serialization;
|
||||
|
||||
namespace HAILogger
|
||||
namespace OmniLinkBridge.WebAPI
|
||||
{
|
||||
[DataContract]
|
||||
public class SubscribeContract
|
|
@ -1,7 +1,7 @@
|
|||
using HAI_Shared;
|
||||
using System.Runtime.Serialization;
|
||||
|
||||
namespace HAILogger
|
||||
namespace OmniLinkBridge.WebAPI
|
||||
{
|
||||
[DataContract]
|
||||
public class ThermostatContract
|
|
@ -1,6 +1,6 @@
|
|||
using System.Runtime.Serialization;
|
||||
|
||||
namespace HAILogger
|
||||
namespace OmniLinkBridge.WebAPI
|
||||
{
|
||||
[DataContract]
|
||||
public class UnitContract
|
107
OmniLinkBridge/WebService/WebNotification.cs
Normal file
107
OmniLinkBridge/WebService/WebNotification.cs
Normal file
|
@ -0,0 +1,107 @@
|
|||
using log4net;
|
||||
using Newtonsoft.Json;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Net;
|
||||
using System.Reflection;
|
||||
|
||||
namespace OmniLinkBridge.WebAPI
|
||||
{
|
||||
static class WebNotification
|
||||
{
|
||||
private static ILog log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);
|
||||
|
||||
private static List<string> subscriptions = new List<string>();
|
||||
private static object subscriptions_lock = new object();
|
||||
|
||||
public static void AddSubscription(string callback)
|
||||
{
|
||||
lock (subscriptions_lock)
|
||||
{
|
||||
if (!subscriptions.Contains(callback))
|
||||
{
|
||||
log.Debug("Adding subscription to " + callback);
|
||||
subscriptions.Add(callback);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static void Send(string type, string body)
|
||||
{
|
||||
string[] send;
|
||||
lock (subscriptions_lock)
|
||||
send = subscriptions.ToArray();
|
||||
|
||||
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);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
log.Error("An error occurred sending notification to " + subscription, ex);
|
||||
subscriptions.Remove(subscription);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void client_UploadStringCompleted(object sender, UploadStringCompletedEventArgs e)
|
||||
{
|
||||
if (e.Error != null)
|
||||
{
|
||||
log.Error("An error occurred sending notification to " + e.UserState.ToString(), e.Error);
|
||||
|
||||
lock (subscriptions_lock)
|
||||
subscriptions.Remove(e.UserState.ToString());
|
||||
}
|
||||
}
|
||||
|
||||
public static void RestoreSubscriptions()
|
||||
{
|
||||
string json;
|
||||
|
||||
try
|
||||
{
|
||||
if (File.Exists(Global.webapi_subscriptions_file))
|
||||
json = File.ReadAllText(Global.webapi_subscriptions_file);
|
||||
else
|
||||
return;
|
||||
|
||||
lock (subscriptions_lock)
|
||||
subscriptions = JsonConvert.DeserializeObject<List<string>>(json);
|
||||
|
||||
log.Debug("Restored subscriptions from file");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
log.Error("An error occurred restoring subscriptions", ex);
|
||||
}
|
||||
}
|
||||
|
||||
public static void SaveSubscriptions()
|
||||
{
|
||||
string json;
|
||||
|
||||
lock (subscriptions_lock)
|
||||
json = JsonConvert.SerializeObject(subscriptions);
|
||||
|
||||
try
|
||||
{
|
||||
File.WriteAllText(Global.webapi_subscriptions_file, json);
|
||||
|
||||
log.Debug("Saved subscriptions to file");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
log.Error("An error occurred saving subscriptions", ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,7 +1,7 @@
|
|||
using HAI_Shared;
|
||||
using System.Runtime.Serialization;
|
||||
|
||||
namespace HAILogger
|
||||
namespace OmniLinkBridge.WebAPI
|
||||
{
|
||||
[DataContract]
|
||||
public class ZoneContract
|
19
OmniLinkBridgeTest/AssemblyTestHarness.cs
Normal file
19
OmniLinkBridgeTest/AssemblyTestHarness.cs
Normal file
|
@ -0,0 +1,19 @@
|
|||
using System;
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
using OmniLinkBridge;
|
||||
using System.IO;
|
||||
using System.Reflection;
|
||||
|
||||
namespace OmniLinkBridgeTest
|
||||
{
|
||||
[TestClass]
|
||||
public class AssemblyTestHarness
|
||||
{
|
||||
[AssemblyInitialize]
|
||||
public static void InitializeAssembly(TestContext context)
|
||||
{
|
||||
Global.config_file = "OmniLinkBridge.ini";
|
||||
Settings.LoadSettings();
|
||||
}
|
||||
}
|
||||
}
|
22
OmniLinkBridgeTest/ExtensionTest.cs
Normal file
22
OmniLinkBridgeTest/ExtensionTest.cs
Normal file
|
@ -0,0 +1,22 @@
|
|||
using System;
|
||||
using System.Text;
|
||||
using System.Collections.Generic;
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
using OmniLinkBridge;
|
||||
|
||||
namespace OmniLinkBridgeTest
|
||||
{
|
||||
[TestClass]
|
||||
public class ExtensionTest
|
||||
{
|
||||
[TestMethod]
|
||||
public void TestParseRange()
|
||||
{
|
||||
List<int> blank = "".ParseRanges();
|
||||
Assert.AreEqual(0, blank.Count);
|
||||
|
||||
List<int> range = "1-3,5,6".ParseRanges();
|
||||
CollectionAssert.AreEqual(new List<int>(new int[] { 1, 2, 3, 5, 6 }), range);
|
||||
}
|
||||
}
|
||||
}
|
18
OmniLinkBridgeTest/NotificationTest.cs
Normal file
18
OmniLinkBridgeTest/NotificationTest.cs
Normal file
|
@ -0,0 +1,18 @@
|
|||
using System;
|
||||
using System.Text;
|
||||
using System.Collections.Generic;
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
using OmniLinkBridge.Notifications;
|
||||
|
||||
namespace OmniLinkBridgeTest
|
||||
{
|
||||
[TestClass]
|
||||
public class NotificationTest
|
||||
{
|
||||
[TestMethod]
|
||||
public void SendNotification()
|
||||
{
|
||||
Notification.Notify("Title", "Description");
|
||||
}
|
||||
}
|
||||
}
|
67
OmniLinkBridgeTest/OmniLinkBridgeTest.csproj
Normal file
67
OmniLinkBridgeTest/OmniLinkBridgeTest.csproj
Normal file
|
@ -0,0 +1,67 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project ToolsVersion="15.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
|
||||
<PropertyGroup>
|
||||
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
|
||||
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
|
||||
<ProjectGuid>{6E6950E4-35F9-4D99-8ADA-B7E2F29D4172}</ProjectGuid>
|
||||
<OutputType>Library</OutputType>
|
||||
<AppDesignerFolder>Properties</AppDesignerFolder>
|
||||
<RootNamespace>OmniLinkBridgeTest</RootNamespace>
|
||||
<AssemblyName>OmniLinkBridgeTest</AssemblyName>
|
||||
<TargetFrameworkVersion>v4.5.2</TargetFrameworkVersion>
|
||||
<FileAlignment>512</FileAlignment>
|
||||
<ProjectTypeGuids>{3AC096D0-A1C2-E12C-1390-A8335801FDAB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}</ProjectTypeGuids>
|
||||
<VisualStudioVersion Condition="'$(VisualStudioVersion)' == ''">15.0</VisualStudioVersion>
|
||||
<VSToolsPath Condition="'$(VSToolsPath)' == ''">$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)</VSToolsPath>
|
||||
<ReferencePath>$(ProgramFiles)\Common Files\microsoft shared\VSTT\$(VisualStudioVersion)\UITestExtensionPackages</ReferencePath>
|
||||
<IsCodedUITest>False</IsCodedUITest>
|
||||
<TestProjectType>UnitTest</TestProjectType>
|
||||
<NuGetPackageImportStamp>
|
||||
</NuGetPackageImportStamp>
|
||||
<TargetFrameworkProfile />
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
|
||||
<DebugSymbols>true</DebugSymbols>
|
||||
<DebugType>full</DebugType>
|
||||
<Optimize>false</Optimize>
|
||||
<OutputPath>bin\Debug\</OutputPath>
|
||||
<DefineConstants>DEBUG;TRACE</DefineConstants>
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
<WarningLevel>4</WarningLevel>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
|
||||
<DebugType>pdbonly</DebugType>
|
||||
<Optimize>true</Optimize>
|
||||
<OutputPath>bin\Release\</OutputPath>
|
||||
<DefineConstants>TRACE</DefineConstants>
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
<WarningLevel>4</WarningLevel>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<Reference Include="System" />
|
||||
<Reference Include="System.Core" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Compile Include="AssemblyTestHarness.cs" />
|
||||
<Compile Include="ExtensionTest.cs" />
|
||||
<Compile Include="NotificationTest.cs" />
|
||||
<Compile Include="Properties\AssemblyInfo.cs" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="MSTest.TestAdapter">
|
||||
<Version>1.3.2</Version>
|
||||
</PackageReference>
|
||||
<PackageReference Include="MSTest.TestFramework">
|
||||
<Version>1.3.2</Version>
|
||||
</PackageReference>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\OmniLinkBridge\OmniLinkBridge.csproj">
|
||||
<Project>{0a636707-98ba-45ab-9843-aed430933cee}</Project>
|
||||
<Name>OmniLinkBridge</Name>
|
||||
</ProjectReference>
|
||||
</ItemGroup>
|
||||
<Import Project="$(VSToolsPath)\TeamTest\Microsoft.TestTools.targets" Condition="Exists('$(VSToolsPath)\TeamTest\Microsoft.TestTools.targets')" />
|
||||
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
|
||||
</Project>
|
20
OmniLinkBridgeTest/Properties/AssemblyInfo.cs
Normal file
20
OmniLinkBridgeTest/Properties/AssemblyInfo.cs
Normal file
|
@ -0,0 +1,20 @@
|
|||
using System.Reflection;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
[assembly: AssemblyTitle("OmniLinkBridgeTest")]
|
||||
[assembly: AssemblyDescription("")]
|
||||
[assembly: AssemblyConfiguration("")]
|
||||
[assembly: AssemblyCompany("Excalibur Partners, LLC")]
|
||||
[assembly: AssemblyProduct("OmniLinkBridgeTest")]
|
||||
[assembly: AssemblyCopyright("Copyright © Excalibur Partners, LLC 2018")]
|
||||
[assembly: AssemblyTrademark("")]
|
||||
[assembly: AssemblyCulture("")]
|
||||
|
||||
[assembly: ComVisible(false)]
|
||||
|
||||
[assembly: Guid("6e6950e4-35f9-4d99-8ada-b7e2f29d4172")]
|
||||
|
||||
// [assembly: AssemblyVersion("1.0.*")]
|
||||
[assembly: AssemblyVersion("1.0.0.0")]
|
||||
[assembly: AssemblyFileVersion("1.0.0.0")]
|
144
README.md
144
README.md
|
@ -1,14 +1,13 @@
|
|||
# HAILogger
|
||||
Provides logging and web service API for HAI/Leviton OmniPro II controllers
|
||||
# OmniLink Bridge
|
||||
Provides time sync, logging, web service API, and MQTT bridge for HAI/Leviton OmniPro II controllers
|
||||
|
||||
##Download
|
||||
You can download the [binary here](http://www.excalibur-partners.com/downloads/HAILogger_1_0_8.zip)
|
||||
## Download
|
||||
You can download the [binary here](http://www.excalibur-partners.com/downloads/OmniLinkBridge_1_1_0.zip)
|
||||
|
||||
##Requirements
|
||||
- .NET Framework 4.0
|
||||
- mySQL 5.1 ODBC Connector
|
||||
## Requirements
|
||||
- .NET Framework 4.5.2
|
||||
|
||||
##Operation
|
||||
## Operation
|
||||
- Area, Messages, Units, and Zones are logged to mySQL when status changes
|
||||
- Thermostats are logged to mySQL once per minute
|
||||
- If no notifications are received within 4 minutes a request is issued
|
||||
|
@ -16,63 +15,124 @@ You can download the [binary here](http://www.excalibur-partners.com/downloads/H
|
|||
- If the temp is 0 a warning will be logged and mySQL will not be updated
|
||||
- Controller time is checked and compared to the local computer time disregarding time zones
|
||||
|
||||
##Notifications
|
||||
- Emails are sent to mail_alarm_to when an area status changes
|
||||
- Prowl notifications are sent when an areas status changes
|
||||
## Notifications
|
||||
- Supports email, prowl, and pushover
|
||||
- Always sent for area alarms and critical system events
|
||||
- Optionally enable for area status changes and console messages
|
||||
|
||||
##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 Windows
|
||||
1. Copy files to your desired location like C:\OmniLinkBridge
|
||||
2. Edit OmniLinkBridge.ini and define at a minimum the controller IP and encryptions keys
|
||||
3. Run OmniLinkBridge.exe to verify connectivity
|
||||
4. Add Windows service
|
||||
- sc create OmniLinkBridge binpath=C:\OmniLinkBridge\OmniLinkBridge.exe
|
||||
5. Start service
|
||||
- net start OmniLinkBridge
|
||||
|
||||
##Installation Linux
|
||||
1. Copy files to your desired location like /opt/HAILogger
|
||||
## Installation Linux
|
||||
1. Copy files to your desired location like /opt/OmniLinkBridge
|
||||
2. Configure at a minimum the controller IP and encryptions keys
|
||||
- vim HAILogger.ini
|
||||
- vim OmniLinkBridge.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
|
||||
- mono OmniLinkBridge.exe -i
|
||||
4. Add systemd file and configure paths
|
||||
- cp omnilinkbridge.service /etc/systemd/system/
|
||||
- vim /etc/systemd/system/omnilinkbridge.service
|
||||
- systemctl daemon-reload
|
||||
5. Enable at boot and start service
|
||||
- systemctl enable hailogger.service
|
||||
- systemctl start hailogger.service
|
||||
- systemctl enable omnilinkbridge.service
|
||||
- systemctl start omnilinkbridge.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.
|
||||
## 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 OmniLink Bridge uses ODBC to communicate with the database. The MySQL ODBC Connector library is needed for Windows ODBC to communicate with MySQL.
|
||||
|
||||
http://dev.mysql.com/downloads/mysql/
|
||||
http://dev.mysql.com/downloads/tools/workbench/
|
||||
http://dev.mysql.com/downloads/connector/odbc/5.1.html
|
||||
http://dev.mysql.com/downloads/connector/odbc/
|
||||
|
||||
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.
|
||||
At this point we need to open MySQL Workbench to create the database (called a schema in the Workbench GUI) for OmniLinkBridge to use.
|
||||
|
||||
1. After opening the program double-click on "Local instance MySQL" and enter the password you set in the wizard.
|
||||
2. On the toolbar click the "Create a new schema" button, provide a name, and click apply.
|
||||
3. On the left side right-click on the schema you created and click "Set as default schema".
|
||||
4. In the middle section under Query1 click the open file icon and select the HAILogger.sql file.
|
||||
4. In the middle section under Query1 click the open file icon and select the OmniLinkBridge.sql file.
|
||||
5. Click the Execute lighting bolt to run the query, which will create the tables.
|
||||
|
||||
Lastly in HAILogger.ini set mysql_database to the name of the schema you created. This should get you up and running. The MySQL Workbench can also be used to view the data that HAILogger inserts into the tables.
|
||||
Lastly in OmniLinkBridge.ini set mysql_connection. This should get you up and running. The MySQL Workbench can also be used to view the data that OmniLink Bridge inserts into the tables.
|
||||
|
||||
##Web Service API
|
||||
mysql_connection = DRIVER={MySQL ODBC 8.0 Driver};SERVER=localhost;DATABASE=OmniLinkBridge;USER=root;PASSWORD=myPassword;OPTION=3;
|
||||
|
||||
## Web Service API
|
||||
To test the API you can use your browser to view a page or PowerShell (see below) to change a value.
|
||||
|
||||
- http://localhost:8000/ListUnits
|
||||
- http://localhost:8000/GetUnit?id=1
|
||||
- Invoke-WebRequest -Uri "http://localhost:8000/SetUnit" -Method POST -ContentType "application/json" -Body (convertto-json -InputObject @{"id"=1;"value"=100}) -UseBasicParsing
|
||||
|
||||
##Change Log
|
||||
## MQTT
|
||||
This module will also publish discovery topics for Home Assistant to auto configure devices.
|
||||
|
||||
SUB omnilink/areaX/state
|
||||
string triggered, pending, armed_night, armed_home, armed_away, disarmed
|
||||
|
||||
PUB omnilink/areaX/command
|
||||
string ARM_HOME, ARM_AWAY, ARM_NIGHT, DISARM, ARM_HOME_INSTANT, ARM_NIGHT_DELAY, ARM_VACATION
|
||||
|
||||
SUB omnilink/unitX/state
|
||||
PUB omnilink/unitX/command
|
||||
string OFF, ON
|
||||
|
||||
SUB omnilink/unitX/brightness_state
|
||||
PUB omnilink/unitX/brightness_command
|
||||
int Level from 0 to 100 percent
|
||||
|
||||
SUB omnilink/thermostatX/current_operation
|
||||
string idle, cool, heat
|
||||
|
||||
SUB omnilink/thermostatX/current_temperature
|
||||
int Current temperature in degrees fahrenheit
|
||||
|
||||
SUB omnilink/thermostatX/current_humidity
|
||||
int Current relative humidity
|
||||
|
||||
SUB omnilink/thermostatX/temperature_heat_state
|
||||
SUB omnilink/thermostatX/temperature_cool_state
|
||||
PUB omnilink/thermostatX/temperature_heat_command
|
||||
PUB omnilink/thermostatX/temperature_cool_command
|
||||
int Setpoint in degrees fahrenheit
|
||||
|
||||
SUB omnilink/thermostatX/humidify_state
|
||||
SUB omnilink/thermostatX/dehumidify_state
|
||||
PUB omnilink/thermostatX/humidify_command
|
||||
PUB omnilink/thermostatX/dehumidify_command
|
||||
int Setpoint in relative humidity
|
||||
|
||||
SUB omnilink/thermostatX/mode_state
|
||||
PUB omnilink/thermostatX/mode_command
|
||||
string auto, off, cool, heat
|
||||
|
||||
SUB omnilink/thermostatX/fan_mode_state
|
||||
PUB omnilink/thermostatX/fan_mode_command
|
||||
string auto, on, cycle
|
||||
|
||||
SUB omnilink/thermostatX/hold_state
|
||||
PUB omnilink/thermostatX/hold_command
|
||||
string off, hold
|
||||
|
||||
SUB omnilink/buttonX/state
|
||||
string OFF
|
||||
|
||||
PUB omnilink/buttonX/command
|
||||
string ON
|
||||
|
||||
## Change Log
|
||||
Version 1.1.0 - 2018-10-13
|
||||
- Renamed to OmniLinkBridge
|
||||
- Restructured code to be event based with modules
|
||||
- Added MQTT module for Home Assistant
|
||||
- Added pushover notifications
|
||||
- Added web service API subscriptions file to persist subscriptions
|
||||
|
||||
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
|
||||
|
|
Loading…
Reference in a new issue