using HAI_Shared; using System; using System.Collections.Generic; using System.Data; using System.Data.Odbc; using System.IO; using System.Reflection; using System.Text; using System.Threading; namespace HAILogger { class CoreServer { private bool quitting = false; private bool terminate = false; // HAI Controller private clsHAC HAC = null; private DateTime retry = DateTime.MinValue; private List alarms = new List(); // Thermostats private Dictionary tstats = new Dictionary(); private System.Timers.Timer tstat_timer = new System.Timers.Timer(); private object tstat_lock = new object(); // Time Sync private System.Timers.Timer tsync_timer = new System.Timers.Timer(); private DateTime tsync_check = DateTime.MinValue; // mySQL Database private OdbcConnection mysql_conn = null; private DateTime mysql_retry = DateTime.MinValue; private OdbcCommand mysql_command = null; private Queue mysql_queue = new Queue(); private object mysql_lock = new object(); public CoreServer() { Thread handler = new Thread(Server); handler.Start(); } private void Server() { Global.event_log = "EventLog.txt"; Global.event_source = "HAI Logger"; if (string.IsNullOrEmpty(Global.dir_config)) Global.dir_config = Path.GetDirectoryName(Assembly.GetEntryAssembly().Location); Settings.LoadSettings(); Event.WriteInfo("CoreServer", "Starting up server " + Assembly.GetExecutingAssembly().GetName().Version.ToString()); tstat_timer.Elapsed += tstat_timer_Elapsed; tstat_timer.AutoReset = false; tsync_timer.Elapsed += tsync_timer_Elapsed; tsync_timer.AutoReset = false; if (Global.mysql_logging) { Event.WriteInfo("DatabaseLogger", "Connecting to database"); mysql_conn = new OdbcConnection(Global.mysql_connection); // Must make an initial connection if (!DBOpen()) Environment.Exit(1); } HAC = new clsHAC(); WebService web = new WebService(HAC); if (Global.webapi_enabled) web.Start(); Connect(); while (true) { // End gracefully when not logging or database queue empty if (quitting && (!Global.mysql_logging || DBQueueCount() == 0)) break; // Make sure controller connection is active if (HAC.Connection.ConnectionState == enuOmniLinkConnectionState.Offline && retry < DateTime.Now) { Connect(); } // Make sure database connection is active if (Global.mysql_logging && mysql_conn.State != ConnectionState.Open) { // Nothing we can do if shutting down if (quitting) break; if (mysql_retry < DateTime.Now) DBOpen(); if (mysql_conn.State != ConnectionState.Open) { // Loop to prevent database queries from executing Thread.Sleep(1000); continue; } } // Sleep when not logging or database queue empty if (!Global.mysql_logging || DBQueueCount() == 0) { Thread.Sleep(1000); 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) { Event.WriteWarn("DatabaseLogger", "Lost connection to database"); } else { Event.WriteError("DatabaseLogger", "Error executing query\r\n" + ex.Message + "\r\n" + query); // Prevent an endless loop from failed query lock (mysql_lock) mysql_queue.Dequeue(); } } } Event.WriteInfo("CoreServer", "Shutting down server"); if (Global.webapi_enabled) web.Stop(); Disconnect(); HAC = null; if (Global.mysql_logging) DBClose(); terminate = true; } private void Connected() { retry = DateTime.MinValue; GetNamedProperties(); UnsolicitedNotifications(true); tstat_timer.Interval = ThermostatTimerInterval(); tstat_timer.Start(); if (Global.hai_time_sync) { tsync_check = DateTime.MinValue; tsync_timer.Interval = TimeTimerInterval(); tsync_timer.Start(); } } public void Shutdown() { quitting = true; while (!terminate) Thread.Sleep(100); Event.WriteInfo("CoreServer", "Shutdown completed"); } #region Connection private void Connect() { if (HAC.Connection.ConnectionState == enuOmniLinkConnectionState.Offline) { retry = DateTime.Now.AddMinutes(1); HAC.Connection.NetworkAddress = Global.hai_address; HAC.Connection.NetworkPort = (ushort)Global.hai_port; HAC.Connection.ControllerKey = clsUtil.HexString2ByteArray( String.Concat(Global.hai_key1, Global.hai_key2)); HAC.PreferredNetworkProtocol = clsHAC.enuPreferredNetworkProtocol.TCP; HAC.Connection.ConnectionType = enuOmniLinkConnectionType.Network_TCP; HAC.Connection.Connect(HandleConnectStatus, HandleUnsolicitedPackets); } } private void Disconnect() { if (HAC.Connection.ConnectionState != enuOmniLinkConnectionState.Offline) HAC.Connection.Disconnect(); } private void HandleConnectStatus(enuOmniLinkCommStatus CS) { switch (CS) { case enuOmniLinkCommStatus.NoReply: Event.WriteError("CoreServer", "CONNECTION STATUS: No Reply"); break; case enuOmniLinkCommStatus.UnrecognizedReply: Event.WriteError("CoreServer", "CONNECTION STATUS: Unrecognized Reply"); break; case enuOmniLinkCommStatus.UnsupportedProtocol: Event.WriteError("CoreServer", "CONNECTION STATUS: Unsupported Protocol"); break; case enuOmniLinkCommStatus.ClientSessionTerminated: Event.WriteError("CoreServer", "CONNECTION STATUS: Client Session Terminated"); break; case enuOmniLinkCommStatus.ControllerSessionTerminated: Event.WriteError("CoreServer", "CONNECTION STATUS: Controller Session Terminated"); break; case enuOmniLinkCommStatus.CannotStartNewSession: Event.WriteError("CoreServer", "CONNECTION STATUS: Cannot Start New Session"); break; case enuOmniLinkCommStatus.LoginFailed: Event.WriteError("CoreServer", "CONNECTION STATUS: Login Failed"); break; case enuOmniLinkCommStatus.UnableToOpenSocket: Event.WriteError("CoreServer", "CONNECTION STATUS: Unable To Open Socket"); break; case enuOmniLinkCommStatus.UnableToConnect: Event.WriteError("CoreServer", "CONNECTION STATUS: Unable To Connect"); break; case enuOmniLinkCommStatus.SocketClosed: Event.WriteError("CoreServer", "CONNECTION STATUS: Socket Closed"); break; case enuOmniLinkCommStatus.UnexpectedError: Event.WriteError("CoreServer", "CONNECTION STATUS: Unexpected Error"); break; case enuOmniLinkCommStatus.UnableToCreateSocket: Event.WriteError("CoreServer", "CONNECTION STATUS: Unable To Create Socket"); break; case enuOmniLinkCommStatus.Retrying: Event.WriteWarn("CoreServer", "CONNECTION STATUS: Retrying"); break; case enuOmniLinkCommStatus.Connected: IdentifyController(); break; case enuOmniLinkCommStatus.Connecting: Event.WriteInfo("CoreServer", "CONNECTION STATUS: Connecting"); break; case enuOmniLinkCommStatus.Disconnected: Event.WriteInfo("CoreServer", "CONNECTION STATUS: Disconnected"); break; case enuOmniLinkCommStatus.InterruptedFunctionCall: if (!quitting) Event.WriteError("CoreServer", "CONNECTION STATUS: Interrupted Function Call"); break; case enuOmniLinkCommStatus.PermissionDenied: Event.WriteError("CoreServer", "CONNECTION STATUS: Permission Denied"); break; case enuOmniLinkCommStatus.BadAddress: Event.WriteError("CoreServer", "CONNECTION STATUS: Bad Address"); break; case enuOmniLinkCommStatus.InvalidArgument: Event.WriteError("CoreServer", "CONNECTION STATUS: Invalid Argument"); break; case enuOmniLinkCommStatus.TooManyOpenFiles: Event.WriteError("CoreServer", "CONNECTION STATUS: Too Many Open Files"); break; case enuOmniLinkCommStatus.ResourceTemporarilyUnavailable: Event.WriteError("CoreServer", "CONNECTION STATUS: Resource Temporarily Unavailable"); break; case enuOmniLinkCommStatus.OperationNowInProgress: Event.WriteWarn("CoreServer", "CONNECTION STATUS: Operation Now In Progress"); break; case enuOmniLinkCommStatus.OperationAlreadyInProgress: Event.WriteWarn("CoreServer", "CONNECTION STATUS: Operation Already In Progress"); break; case enuOmniLinkCommStatus.SocketOperationOnNonSocket: Event.WriteError("CoreServer", "CONNECTION STATUS: Socket Operation On Non Socket"); break; case enuOmniLinkCommStatus.DestinationAddressRequired: Event.WriteError("CoreServer", "CONNECTION STATUS: Destination Address Required"); break; case enuOmniLinkCommStatus.MessgeTooLong: Event.WriteError("CoreServer", "CONNECTION STATUS: Message Too Long"); break; case enuOmniLinkCommStatus.WrongProtocolType: Event.WriteError("CoreServer", "CONNECTION STATUS: Wrong Protocol Type"); break; case enuOmniLinkCommStatus.BadProtocolOption: Event.WriteError("CoreServer", "CONNECTION STATUS: Bad Protocol Option"); break; case enuOmniLinkCommStatus.ProtocolNotSupported: Event.WriteError("CoreServer", "CONNECTION STATUS: Protocol Not Supported"); break; case enuOmniLinkCommStatus.SocketTypeNotSupported: Event.WriteError("CoreServer", "CONNECTION STATUS: Socket Type Not Supported"); break; case enuOmniLinkCommStatus.OperationNotSupported: Event.WriteError("CoreServer", "CONNECTION STATUS: Operation Not Supported"); break; case enuOmniLinkCommStatus.ProtocolFamilyNotSupported: Event.WriteError("CoreServer", "CONNECTION STATUS: Protocol Family Not Supported"); break; case enuOmniLinkCommStatus.AddressFamilyNotSupported: Event.WriteError("CoreServer", "CONNECTION STATUS: Address Family Not Supported"); break; case enuOmniLinkCommStatus.AddressInUse: Event.WriteError("CoreServer", "CONNECTION STATUS: Address In Use"); break; case enuOmniLinkCommStatus.AddressNotAvailable: Event.WriteError("CoreServer", "CONNECTION STATUS: Address Not Available"); break; case enuOmniLinkCommStatus.NetworkIsDown: Event.WriteError("CoreServer", "CONNECTION STATUS: Network Is Down"); break; case enuOmniLinkCommStatus.NetworkIsUnreachable: Event.WriteError("CoreServer", "CONNECTION STATUS: Network Is Unreachable"); break; case enuOmniLinkCommStatus.NetworkReset: Event.WriteError("CoreServer", "CONNECTION STATUS: Network Reset"); break; case enuOmniLinkCommStatus.ConnectionAborted: Event.WriteError("CoreServer", "CONNECTION STATUS: Connection Aborted"); break; case enuOmniLinkCommStatus.ConnectionResetByPeer: Event.WriteError("CoreServer", "CONNECTION STATUS: Connection Reset By Peer"); break; case enuOmniLinkCommStatus.NoBufferSpaceAvailable: Event.WriteError("CoreServer", "CONNECTION STATUS: No Buffer Space Available"); break; case enuOmniLinkCommStatus.AlreadyConnected: Event.WriteWarn("CoreServer", "CONNECTION STATUS: Already Connected"); break; case enuOmniLinkCommStatus.NotConnected: Event.WriteError("CoreServer", "CONNECTION STATUS: Not Connected"); break; case enuOmniLinkCommStatus.CannotSendAfterShutdown: Event.WriteError("CoreServer", "CONNECTION STATUS: Cannot Send After Shutdown"); break; case enuOmniLinkCommStatus.ConnectionTimedOut: Event.WriteError("CoreServer", "CONNECTION STATUS: Connection Timed Out"); break; case enuOmniLinkCommStatus.ConnectionRefused: Event.WriteError("CoreServer", "CONNECTION STATUS: Connection Refused"); break; case enuOmniLinkCommStatus.HostIsDown: Event.WriteError("CoreServer", "CONNECTION STATUS: Host Is Down"); break; case enuOmniLinkCommStatus.HostUnreachable: Event.WriteError("CoreServer", "CONNECTION STATUS: Host Unreachable"); break; case enuOmniLinkCommStatus.TooManyProcesses: Event.WriteError("CoreServer", "CONNECTION STATUS: Too Many Processes"); break; case enuOmniLinkCommStatus.NetworkSubsystemIsUnavailable: Event.WriteError("CoreServer", "CONNECTION STATUS: Network Subsystem Is Unavailable"); break; case enuOmniLinkCommStatus.UnsupportedVersion: Event.WriteError("CoreServer", "CONNECTION STATUS: Unsupported Version"); break; case enuOmniLinkCommStatus.NotInitialized: Event.WriteError("CoreServer", "CONNECTION STATUS: Not Initialized"); break; case enuOmniLinkCommStatus.ShutdownInProgress: Event.WriteError("CoreServer", "CONNECTION STATUS: Shutdown In Progress"); break; case enuOmniLinkCommStatus.ClassTypeNotFound: Event.WriteError("CoreServer", "CONNECTION STATUS: Class Type Not Found"); break; case enuOmniLinkCommStatus.HostNotFound: Event.WriteError("CoreServer", "CONNECTION STATUS: Host Not Found"); break; case enuOmniLinkCommStatus.HostNotFoundTryAgain: Event.WriteError("CoreServer", "CONNECTION STATUS: Host Not Found Try Again"); break; case enuOmniLinkCommStatus.NonRecoverableError: Event.WriteError("CoreServer", "CONNECTION STATUS: Non Recoverable Error"); break; case enuOmniLinkCommStatus.NoDataOfRequestedType: Event.WriteError("CoreServer", "CONNECTION STATUS: No Data Of Requested Type"); break; default: break; } } private void IdentifyController() { if (HAC.Connection.ConnectionState == enuOmniLinkConnectionState.Online || HAC.Connection.ConnectionState == enuOmniLinkConnectionState.OnlineSecure) { HAC.Connection.Send(new clsOL2MsgRequestSystemInformation(HAC.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(HAC.Connection, B); if (HAC.Model == MSG.ModelNumber) { HAC.CopySystemInformation(MSG); Event.WriteInfo("CoreServer", "CONTROLLER IS: " + HAC.GetModelText() + " (" + HAC.GetVersionText() + ")"); Connected(); return; } Event.WriteError("CoreServer", "Model does not match file"); HAC.Connection.Disconnect(); } } #endregion #region Names private void GetNamedProperties() { Event.WriteInfo("CoreServer", "Retrieving named units"); GetNextNamed(enuObjectType.Area, 0); Thread.Sleep(100); GetNextNamed(enuObjectType.Zone, 0); Thread.Sleep(100); GetNextNamed(enuObjectType.Thermostat, 0); Thread.Sleep(100); GetNextNamed(enuObjectType.Unit, 0); Thread.Sleep(100); GetNextNamed(enuObjectType.Message, 0); Thread.Sleep(100); GetNextNamed(enuObjectType.Button, 0); Thread.Sleep(100); } private void GetNextNamed(enuObjectType type, int ix) { clsOL2MsgRequestProperties MSG = new clsOL2MsgRequestProperties(HAC.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 HAC.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: break; case enuOmniLink2MessageType.Properties: clsOL2MsgProperties MSG = new clsOL2MsgProperties(HAC.Connection, B); switch (MSG.ObjectType) { case enuObjectType.Area: HAC.Areas.CopyProperties(MSG); break; case enuObjectType.Zone: HAC.Zones.CopyProperties(MSG); if (HAC.Zones[MSG.ObjectNumber].IsTemperatureZone()) HAC.Connection.Send(new clsOL2MsgRequestExtendedStatus(HAC.Connection, enuObjectType.Auxillary, MSG.ObjectNumber, MSG.ObjectNumber), HandleRequestAuxillaryStatus); break; case enuObjectType.Thermostat: HAC.Thermostats.CopyProperties(MSG); if (!tstats.ContainsKey(MSG.ObjectNumber)) tstats.Add(MSG.ObjectNumber, DateTime.MinValue); else tstats[MSG.ObjectNumber] = DateTime.MinValue; HAC.Connection.Send(new clsOL2MsgRequestExtendedStatus(HAC.Connection, enuObjectType.Thermostat, MSG.ObjectNumber, MSG.ObjectNumber), HandleRequestThermostatStatus); Event.WriteVerbose("ThermostatTimer", "Added to watch list " + HAC.Thermostats[MSG.ObjectNumber].Name); break; case enuObjectType.Unit: HAC.Units.CopyProperties(MSG); break; case enuObjectType.Message: HAC.Messages.CopyProperties(MSG); break; case enuObjectType.Button: HAC.Buttons.CopyProperties(MSG); break; default: break; } GetNextNamed(MSG.ObjectType, MSG.ObjectNumber); break; default: break; } } } #endregion #region Notifications private void UnsolicitedNotifications(bool enable) { Event.WriteInfo("CoreServer", "Unsolicited notifications " + (enable ? "enabled" : "disabled")); HAC.Connection.Send(new clsOL2EnableNotifications(HAC.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) Event.WriteVerbose("CoreServer", "Unhandled notification: " + ((enuOmniLink2MessageType)B[2]).ToString()); } return true; } private void HandleUnsolicitedSystemEvent(byte[] B) { clsOL2SystemEvent MSG = new clsOL2SystemEvent(HAC.Connection, B); enuEventType type; string value = string.Empty; bool alert = false; if (MSG.SystemEvent >= 1 && MSG.SystemEvent <= 255) { type = enuEventType.USER_MACRO_BUTTON; value = ((int)MSG.SystemEvent).ToString() + " " + HAC.Buttons[MSG.SystemEvent].Name; LogEventStatus(type, value, alert); } else if (MSG.SystemEvent >= 768 && MSG.SystemEvent <= 771) { type = enuEventType.PHONE_; if (MSG.SystemEvent == 768) { value = "DEAD"; alert = true; } else if (MSG.SystemEvent == 769) value = "RING"; else if (MSG.SystemEvent == 770) value = "OFF HOOK"; else if (MSG.SystemEvent == 771) value = "ON HOOK"; LogEventStatus(type, value, alert); } else if (MSG.SystemEvent >= 772 && MSG.SystemEvent <= 773) { type = enuEventType.AC_POWER_; alert = true; if (MSG.SystemEvent == 772) value = "OFF"; else if (MSG.SystemEvent == 773) value = "RESTORED"; LogEventStatus(type, value, alert); } else if (MSG.SystemEvent >= 774 && MSG.SystemEvent <= 775) { type = enuEventType.BATTERY_; alert = true; if (MSG.SystemEvent == 774) value = "LOW"; else if (MSG.SystemEvent == 775) value = "OK"; LogEventStatus(type, value, alert); } else if (MSG.SystemEvent >= 776 && MSG.SystemEvent <= 777) { type = enuEventType.DCM_; alert = true; if (MSG.SystemEvent == 776) value = "TROUBLE"; else if (MSG.SystemEvent == 777) value = "OK"; LogEventStatus(type, value, alert); } else if (MSG.SystemEvent >= 778 && MSG.SystemEvent <= 781) { type = enuEventType.ENERGY_COST_; if (MSG.SystemEvent == 778) value = "LOW"; else if (MSG.SystemEvent == 779) value = "MID"; else if (MSG.SystemEvent == 780) value = "HIGH"; else if (MSG.SystemEvent == 781) value = "CRITICAL"; LogEventStatus(type, value, alert); } else if (MSG.SystemEvent >= 782 && MSG.SystemEvent <= 787) { type = enuEventType.CAMERA; value = (MSG.SystemEvent - 781).ToString(); LogEventStatus(type, value, alert); } else if (MSG.SystemEvent >= 61440 && MSG.SystemEvent <= 64511) { type = enuEventType.SWITCH_PRESS; int state = (int)MSG.Data[1] - 240; int id = (int)MSG.Data[2]; LogEventStatus(type, "Unit: " + id + ", State: " + state, alert); } else if (MSG.SystemEvent >= 64512 && MSG.SystemEvent <= 65535) { type = enuEventType.UPB_LINK; int state = (int)MSG.Data[1] - 252; int id = (int)MSG.Data[2]; LogEventStatus(type, "Link: " + id + ", State: " + state, alert); } else if (Global.verbose_unhandled) { StringBuilder sb = new StringBuilder(); for (int i = 0; i < MSG.MessageLength; i++) sb.Append(MSG.Data[i].ToString() + " "); Event.WriteVerbose("SystemEvent", "Unhandled Raw: " + sb.ToString() + "Num: " + MSG.SystemEvent); int num = ((int)MSG.MessageLength - 1) / 2; for (int i = 0; i < num; i++) { Event.WriteVerbose("SystemEvent", "Unhandled: " + (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(HAC.Connection, B); switch (MSG.ObjectType) { case enuObjectType.Area: for (byte i = 0; i < MSG.AreaCount(); i++) { HAC.Areas[MSG.ObjectNumber(i)].CopyExtendedStatus(MSG, i); LogAreaStatus(MSG.ObjectNumber(i)); WebNotification.Send("area", Helper.Serialize(Helper.ConvertArea( MSG.ObjectNumber(i), HAC.Areas[MSG.ObjectNumber(i)]))); } break; case enuObjectType.Auxillary: for (byte i = 0; i < MSG.AuxStatusCount(); i++) { HAC.Zones[MSG.ObjectNumber(i)].CopyAuxExtendedStatus(MSG, i); LogZoneStatus(MSG.ObjectNumber(i)); if (HAC.Zones[MSG.ObjectNumber(i)].IsTemperatureZone()) { WebNotification.Send("temp", Helper.Serialize(Helper.ConvertZone( MSG.ObjectNumber(i), HAC.Zones[MSG.ObjectNumber(i)]))); } } break; case enuObjectType.Zone: for (byte i = 0; i < MSG.ZoneStatusCount(); i++) { HAC.Zones[MSG.ObjectNumber(i)].CopyExtendedStatus(MSG, i); LogZoneStatus(MSG.ObjectNumber(i)); switch (HAC.Zones[MSG.ObjectNumber(i)].ZoneType) { case enuZoneType.EntryExit: case enuZoneType.X2EntryDelay: case enuZoneType.X4EntryDelay: case enuZoneType.Perimeter: WebNotification.Send("contact", Helper.Serialize(Helper.ConvertZone( MSG.ObjectNumber(i), HAC.Zones[MSG.ObjectNumber(i)]))); break; case enuZoneType.AwayInt: WebNotification.Send("motion", Helper.Serialize(Helper.ConvertZone( MSG.ObjectNumber(i), HAC.Zones[MSG.ObjectNumber(i)]))); break; case enuZoneType.Water: WebNotification.Send("water", Helper.Serialize(Helper.ConvertZone( MSG.ObjectNumber(i), HAC.Zones[MSG.ObjectNumber(i)]))); break; case enuZoneType.Fire: WebNotification.Send("smoke", Helper.Serialize(Helper.ConvertZone( MSG.ObjectNumber(i), HAC.Zones[MSG.ObjectNumber(i)]))); break; case enuZoneType.Gas: WebNotification.Send("co", Helper.Serialize(Helper.ConvertZone( MSG.ObjectNumber(i), HAC.Zones[MSG.ObjectNumber(i)]))); break; } } break; case enuObjectType.Thermostat: for (byte i = 0; i < MSG.ThermostatStatusCount(); i++) { lock (tstat_lock) { HAC.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) Event.WriteVerbose("ThermostatTimer", "Unsolicited status received for " + HAC.Thermostats[MSG.ObjectNumber(i)].Name); WebNotification.Send("thermostat", Helper.Serialize(Helper.ConvertThermostat( MSG.ObjectNumber(i), HAC.Thermostats[MSG.ObjectNumber(i)]))); } } break; case enuObjectType.Unit: for (byte i = 0; i < MSG.UnitStatusCount(); i++) { HAC.Units[MSG.ObjectNumber(i)].CopyExtendedStatus(MSG, i); LogUnitStatus(MSG.ObjectNumber(i)); WebNotification.Send("unit", Helper.Serialize(Helper.ConvertUnit( MSG.ObjectNumber(i), HAC.Units[MSG.ObjectNumber(i)]))); } break; case enuObjectType.Message: for (byte i = 0; i < MSG.MessageCount(); i++) { HAC.Messages[MSG.ObjectNumber(i)].CopyExtendedStatus(MSG, i); LogMessageStatus(MSG.ObjectNumber(i)); } break; default: if (Global.verbose_unhandled) { StringBuilder sb = new StringBuilder(); foreach (byte b in MSG.ToByteArray()) sb.Append(b.ToString() + " "); Event.WriteVerbose("ExtendedStatus", "Unhandled " + MSG.ObjectType.ToString() + " " + sb.ToString()); } break; } } #endregion private void HandleRequestAuxillaryStatus(clsOmniLinkMessageQueueItem M, byte[] B, bool Timeout) { if (Timeout) return; clsOL2MsgExtendedStatus MSG = new clsOL2MsgExtendedStatus(HAC.Connection, B); for (byte i = 0; i < MSG.AuxStatusCount(); i++) { HAC.Zones[MSG.ObjectNumber(i)].CopyAuxExtendedStatus(MSG, i); } } #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 tstat in tstats) { // Poll every 4 minutes if no prior update if (RoundToMinute(tstat.Value).AddMinutes(4) <= RoundToMinute(DateTime.Now)) { HAC.Connection.Send(new clsOL2MsgRequestExtendedStatus(HAC.Connection, enuObjectType.Thermostat, tstat.Key, tstat.Key), HandleRequestThermostatStatus); if (Global.verbose_thermostat_timer) Event.WriteVerbose("ThermostatTimer", "Polling status for " + HAC.Thermostats[tstat.Key].Name); } // Log every minute if update within 5 minutes and connected if (RoundToMinute(tstat.Value).AddMinutes(5) > RoundToMinute(DateTime.Now) && (HAC.Connection.ConnectionState == enuOmniLinkConnectionState.Online || HAC.Connection.ConnectionState == enuOmniLinkConnectionState.OnlineSecure)) { if (HAC.Thermostats[tstat.Key].Temp > 0) LogThermostatStatus(tstat.Key); else if (Global.verbose_thermostat_timer) Event.WriteWarn("ThermostatTimer", "Not logging unknown temp for " + HAC.Thermostats[tstat.Key].Name); } else if (Global.verbose_thermostat_timer) Event.WriteWarn("ThermostatTimer", "Not logging out of date status for " + HAC.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(HAC.Connection, B); for (byte i = 0; i < MSG.ThermostatStatusCount(); i++) { lock (tstat_lock) { HAC.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) Event.WriteVerbose("ThermostatTimer", "Polling status received for " + HAC.Thermostats[MSG.ObjectNumber(i)].Name); } } } #endregion #region Time Sync 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.hai_time_interval) < DateTime.Now) HAC.Connection.Send(new clsOL2MsgRequestSystemStatus(HAC.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(HAC.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 { Event.WriteWarn("TimeSyncTimer", "Controller time could not be parsed", true); DateTime now = DateTime.Now; HAC.Connection.Send(new clsOL2MsgSetTime(HAC.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.hai_time_drift) { Event.WriteWarn("TimeSyncTimer", "Controller time " + time.ToString("MM/dd/yyyy HH:mm:ss") + " out of sync by " + adj + " seconds", true); DateTime now = DateTime.Now; HAC.Connection.Send(new clsOL2MsgSetTime(HAC.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; Event.WriteVerbose("TimeSyncTimer", "Controller time has been successfully set"); } #endregion #region Logging private void LogEventStatus(enuEventType type, string value, bool alert) { DBQueue(@" INSERT INTO log_events (timestamp, name, status) VALUES ('" + DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss") + "','" + type.ToString() + "','" + value + "')"); if (alert) { Event.WriteWarn("SystemEvent", type.ToString() + " " + value); Prowl.Notify("SystemEvent", type.ToString() + " " + value); } if (Global.verbose_event) Event.WriteVerbose("SystemEvent", type.ToString() + " " + value); } private void LogAreaStatus(ushort id) { clsArea unit = HAC.Areas[id]; // Alarm notifcation if (unit.AreaFireAlarmText != "OK") { Event.WriteAlarm("AreaStatus", "FIRE " + unit.Name + " " + unit.AreaFireAlarmText); Prowl.Notify("ALARM", "FIRE " + unit.Name + " " + unit.AreaFireAlarmText, ProwlPriority.Emergency); if (!alarms.Contains("FIRE" + id)) alarms.Add("FIRE" + id); } else if (alarms.Contains("FIRE" + id)) { Event.WriteAlarm("AreaStatus", "CLEARED - FIRE " + unit.Name + " " + unit.AreaFireAlarmText); Prowl.Notify("ALARM CLEARED", "FIRE " + unit.Name + " " + unit.AreaFireAlarmText, ProwlPriority.High); alarms.Remove("FIRE" + id); } if (unit.AreaBurglaryAlarmText != "OK") { Event.WriteAlarm("AreaStatus", "BURGLARY " + unit.Name + " " + unit.AreaBurglaryAlarmText); Prowl.Notify("ALARM", "BURGLARY " + unit.Name + " " + unit.AreaBurglaryAlarmText, ProwlPriority.Emergency); if (!alarms.Contains("BURGLARY" + id)) alarms.Add("BURGLARY" + id); } else if (alarms.Contains("BURGLARY" + id)) { Event.WriteAlarm("AreaStatus", "CLEARED - BURGLARY " + unit.Name + " " + unit.AreaBurglaryAlarmText); Prowl.Notify("ALARM CLEARED", "BURGLARY " + unit.Name + " " + unit.AreaBurglaryAlarmText, ProwlPriority.High); alarms.Remove("BURGLARY" + id); } if (unit.AreaAuxAlarmText != "OK") { Event.WriteAlarm("AreaStatus", "AUX " + unit.Name + " " + unit.AreaAuxAlarmText); Prowl.Notify("ALARM", "AUX " + unit.Name + " " + unit.AreaAuxAlarmText, ProwlPriority.Emergency); if (!alarms.Contains("AUX" + id)) alarms.Add("AUX" + id); } else if (alarms.Contains("AUX" + id)) { Event.WriteAlarm("AreaStatus", "CLEARED - AUX " + unit.Name + " " + unit.AreaAuxAlarmText); Prowl.Notify("ALARM CLEARED", "AUX " + unit.Name + " " + unit.AreaAuxAlarmText, ProwlPriority.High); alarms.Remove("AUX" + id); } if (unit.AreaDuressAlarmText != "OK") { Event.WriteAlarm("AreaStatus", "DURESS " + unit.Name + " " + unit.AreaDuressAlarmText); Prowl.Notify("ALARM", "DURESS " + unit.Name + " " + unit.AreaDuressAlarmText, ProwlPriority.Emergency); if (!alarms.Contains("DURESS" + id)) alarms.Add("DURESS" + id); } else if (alarms.Contains("DURESS" + id)) { Event.WriteAlarm("AreaStatus", "CLEARED - DURESS " + unit.Name + " " + unit.AreaDuressAlarmText); Prowl.Notify("ALARM CLEARED", "DURESS " + unit.Name + " " + unit.AreaDuressAlarmText, ProwlPriority.High); alarms.Remove("DURESS" + id); } string status = unit.ModeText(); if (unit.ExitTimer > 0) status = "ARMING " + status; if (unit.EntryTimer > 0) status = "TRIPPED " + status; DBQueue(@" INSERT INTO log_areas (timestamp, id, name, fire, police, auxiliary, duress, security) VALUES ('" + DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss") + "','" + id.ToString() + "','" + unit.Name + "','" + unit.AreaFireAlarmText + "','" + unit.AreaBurglaryAlarmText + "','" + unit.AreaAuxAlarmText + "','" + unit.AreaDuressAlarmText + "','" + status + "')"); if (Global.verbose_area) Event.WriteVerbose("AreaStatus", id + " " + unit.Name + ", Status: " + status); if (unit.LastMode != unit.AreaMode) Prowl.Notify("Security", unit.Name + " " + unit.ModeText()); } private void LogZoneStatus(ushort id) { clsZone unit = HAC.Zones[id]; DBQueue(@" INSERT INTO log_zones (timestamp, id, name, status) VALUES ('" + DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss") + "','" + id.ToString() + "','" + unit.Name + "','" + unit.StatusText() + "')"); if (Global.verbose_zone) { if (unit.IsTemperatureZone()) Event.WriteVerbose("ZoneStatus", id + " " + unit.Name + ", Temp: " + unit.TempText()); else Event.WriteVerbose("ZoneStatus", id + " " + unit.Name + ", Status: " + unit.StatusText()); } } private void LogThermostatStatus(ushort id) { clsThermostat unit = HAC.Thermostats[id]; int temp, heat, cool, humidity, humidify, dehumidify; Int32.TryParse(unit.TempText(), out temp); Int32.TryParse(unit.HeatSetpointText(), out heat); Int32.TryParse(unit.CoolSetpointText(), out cool); Int32.TryParse(unit.HumidityText(), out humidity); Int32.TryParse(unit.HumidifySetpointText(), out humidify); Int32.TryParse(unit.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") + "','" + id.ToString() + "','" + unit.Name + "','" + unit.HorC_StatusText() + "','" + temp.ToString() + "','" + heat + "','" + cool + "','" + humidity + "','" + humidify + "','" + dehumidify + "','" + unit.ModeText() + "','" + unit.FanModeText() + "','" + unit.HoldStatusText() + "')"); if (Global.verbose_thermostat) Event.WriteVerbose("ThermostatStatus", id + " " + unit.Name + ", Status: " + unit.TempText() + " " + unit.HorC_StatusText() + ", Heat: " + unit.HeatSetpointText() + ", Cool: " + unit.CoolSetpointText() + ", Mode: " + unit.ModeText() + ", Fan: " + unit.FanModeText() + ", Hold: " + unit.HoldStatusText()); } private void LogUnitStatus(ushort id) { clsUnit unit = HAC.Units[id]; string status = unit.StatusText; if (unit.Status == 100 && unit.StatusTime == 0) status = "OFF"; else if (unit.Status == 200 && unit.StatusTime == 0) status = "ON"; DBQueue(@" INSERT INTO log_units (timestamp, id, name, status, statusvalue, statustime) VALUES ('" + DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss") + "','" + id.ToString() + "','" + unit.Name + "','" + status + "','" + unit.Status + "','" + unit.StatusTime + "')"); if (Global.verbose_unit) Event.WriteVerbose("UnitStatus", id + " " + unit.Name + ", Status: " + status); } private void LogMessageStatus(ushort id) { clsMessage unit = HAC.Messages[id]; DBQueue(@" INSERT INTO log_messages (timestamp, id, name, status) VALUES ('" + DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss") + "','" + id.ToString() + "','" + unit.Name + "','" + unit.StatusText() + "')"); if (Global.verbose_message) Event.WriteVerbose("MessageStatus", unit.Name + ", " + unit.StatusText()); if (Global.prowl_messages) Prowl.Notify("Message", id + " " + unit.Name + ", " + unit.StatusText()); } #endregion #region Database public bool DBOpen() { try { if (mysql_conn.State != ConnectionState.Open) mysql_conn.Open(); mysql_retry = DateTime.MinValue; } catch (Exception ex) { Event.WriteError("DatabaseLogger", "Failed to connect to database\r\n" + ex.Message); 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; } #endregion } }