public void Initialise(EngineRoomServiceDB erdb)
        {
            DBRow row = erdb.GetLatestEvent(EngineRoomServiceDB.LogEventType.ON, ID);

            if (row != null)
            {
                LastOn = row.GetDateTime("created");
            }
            row = erdb.GetLatestEvent(EngineRoomServiceDB.LogEventType.OFF, ID);
            if (row != null)
            {
                LastOff = row.GetDateTime("created");
            }

            DBRow enabled  = erdb.GetLatestEvent(EngineRoomServiceDB.LogEventType.ENABLE, ID);
            DBRow disabled = erdb.GetLatestEvent(EngineRoomServiceDB.LogEventType.DISABLE, ID);

            bool enable = true;

            if (disabled != null)
            {
                enable = enabled == null ? false : enabled.GetDateTime("created").Ticks > disabled.GetDateTime("created").Ticks;
            }
            else if (enabled != null)
            {
                enable = true;
            }
            Enable(enable);

            String desc = String.Format("Initialised engine {0} ... engine is {1}", ID, Enabled ? "enabled" : "disabled");

            erdb.LogEvent(EngineRoomServiceDB.LogEventType.INITIALISE, ID, desc);
        }
        protected override void OnStart(string[] args)
        {
            try
            {
                Tracing?.TraceEvent(TraceEventType.Information, 0, "Connecting to Engine Room database...");
                _erdb = EngineRoomServiceDB.Create(Properties.Settings.Default, "EngineRoomDBName");
                Tracing?.TraceEvent(TraceEventType.Information, 0, "Connected to Engine Room database");

                _erdb.GetLatestEvent(EngineRoomServiceDB.LogEventType.CONNECT, EngineRoomServiceDB.LogEventType.DISCONNECT, "BBEngineRoom");

                Tracing?.TraceEvent(TraceEventType.Information, 0, "Creating state log timer at {0} intervals", TIMER_STATE_LOG_INTERVAL);
                _timerStateLog          = new System.Timers.Timer();
                _timerStateLog.Interval = TIMER_STATE_LOG_INTERVAL;
                _timerStateLog.Elapsed += OnStateLogTimer;

                Tracing?.TraceEvent(TraceEventType.Information, 0, "Creating montior engine room timer at {0} intervals", TIMER_MONITOR_ENGINE_ROOM_INTERVAL);
                _timerMonitorEngineRoom          = new System.Timers.Timer();
                _timerMonitorEngineRoom.Interval = TIMER_MONITOR_ENGINE_ROOM_INTERVAL;
                _timerMonitorEngineRoom.Elapsed += OnMonitorEngineRoomTimer;
            }
            catch (Exception e)
            {
                Tracing?.TraceEvent(TraceEventType.Error, 0, e.Message);
                throw e;
            }

            base.OnStart(args);
        }
        public void LogState(EngineRoomServiceDB erdb)
        {
            if (!Enabled)
            {
                return;
            }

            String desc = IsOn ? String.Format("On @ {0} for {1} secs", LastOn, DateTime.Now.Subtract(LastOn).TotalSeconds) : String.Empty;

            erdb.LogState(ID, "Pumping", State, desc);
        }
        public void Monitor(EngineRoomServiceDB erdb, List <Message> messages, bool returnEventsOnly)
        {
            //if not enabled OR it has only just been initialised then don't monitor ... the distance sensor device needs time to build an accurate reading
            if (!Enabled || DateTime.Now.Subtract(_initialisedAt).TotalSeconds < 45)
            {
                return;
            }

            Message msg  = null;
            String  desc = null;

            EngineRoomServiceDB.LogEventType let;

            FluidLevel waterLevel = Level;                //old level

            Level = FluidTank.GetFluidLevel(PercentFull); //current level
            bool fillingUp = waterLevel < Level;

            switch (Level)
            {
            case FluidLevel.VERY_LOW:
                let  = fillingUp ? EngineRoomServiceDB.LogEventType.INFO : EngineRoomServiceDB.LogEventType.WARNING;
                desc = String.Format("Water Level: {0} @ {1}% and {2}L remaining", Level, PercentFull, Remaining);
                BBAlarmsService.AlarmState alarmState = fillingUp ? BBAlarmsService.AlarmState.OFF : BBAlarmsService.AlarmState.MODERATE;
                msg = BBAlarmsService.AlarmsMessageSchema.AlertAlarmStateChange(ID, alarmState, desc);
                break;

            case FluidLevel.EMPTY:
                let  = EngineRoomServiceDB.LogEventType.WARNING;
                desc = String.Format("Water Level: {0} @ {1}% and {2}L remaining", Level, PercentFull, Remaining);
                msg  = BBAlarmsService.AlarmsMessageSchema.AlertAlarmStateChange(ID, BBAlarmsService.AlarmState.SEVERE, desc);
                break;

            default:
                let  = EngineRoomServiceDB.LogEventType.INFO;
                desc = String.Format("Water Level: {0} @ {1}% and {2}L remaining", Level, PercentFull, Remaining);
                msg  = BBAlarmsService.AlarmsMessageSchema.AlertAlarmStateChange(ID, BBAlarmsService.AlarmState.OFF, desc);
                break;
            }

            if (msg != null && (waterLevel != Level || !returnEventsOnly))
            {
                messages.Add(msg);
                if (returnEventsOnly)
                {
                    erdb.LogEvent(let, ID, desc);
                }
            }
        }
        public void Monitor(EngineRoomServiceDB erdb, List <Message> messages, bool returnEventsOnly)
        {
            if (!Enabled)
            {
                return;
            }

            EngineRoomServiceDB.LogEventType let = EngineRoomServiceDB.LogEventType.INFO;
            String  desc = null;
            Message msg  = null;

            PumpState pumpState = StateOfPump;

            if (IsOn && DateTime.Now.Subtract(LastOn).TotalSeconds > MaxOnDuration)
            {
                StateOfPump = PumpState.ON_TOO_LONG;
            }
            else
            {
                StateOfPump = IsOn ? PumpState.ON : PumpState.OFF;
            }

            switch (StateOfPump)
            {
            case PumpState.ON_TOO_LONG:
                let  = EngineRoomServiceDB.LogEventType.WARNING;
                desc = String.Format("Pump is: {0}", StateOfPump);
                msg  = BBAlarmsService.AlarmsMessageSchema.AlertAlarmStateChange(ID, BBAlarmsService.AlarmState.CRITICAL, desc);
                break;

            case PumpState.ON:
            case PumpState.OFF:
                let  = StateOfPump == PumpState.ON ? EngineRoomServiceDB.LogEventType.ON: EngineRoomServiceDB.LogEventType.OFF;
                desc = String.Format("Pump is: {0}", StateOfPump);
                msg  = BBAlarmsService.AlarmsMessageSchema.AlertAlarmStateChange(ID, BBAlarmsService.AlarmState.OFF, desc);
                break;
            }
            if (msg != null && (pumpState != StateOfPump || !returnEventsOnly))
            {
                messages.Add(msg);
                if (returnEventsOnly)
                {
                    erdb.LogEvent(let, ID, desc);
                }
            }
        }
        public void LogState(EngineRoomServiceDB erdb)
        {
            if (Tanks.Count == 0 || !Enabled || DateTime.Now.Subtract(_initialisedAt).TotalSeconds < 45)
            {
                return;
            }

            String desc;

            foreach (FluidTank ft in Tanks)
            {
                desc = String.Format("Water tank {0} is {1}% full (distance = {2}) and has {3}L remaining ... level is {4}", ft.ID, ft.PercentFull, ft.AverageDistance, ft.Remaining, ft.Level);
                erdb.LogState(ft.ID, "Percent Full", ft.PercentFull, desc);
            }

            desc = String.Format("Remaining water @ {0}%, {1}L ... level is {2}", PercentFull, Remaining, Level);
            erdb.LogState(ID, "Percent Full", PercentFull, desc);
        }
        public void LogState(EngineRoomServiceDB erdb)
        {
            if (!Enabled)
            {
                return;
            }

            erdb.LogState(ID, "Running", Running);
            if (RPM != null)
            {
                erdb.LogState(ID, "RPM", RPM.RPM);
                erdb.LogState(ID, "RPM Average", RPM.AverageRPM);
            }
            if (OilSensor != null)
            {
                erdb.LogState(ID, "OilSensor", OilSensor.State);
            }
            if (TempSensor != null)
            {
                erdb.LogState(ID, "Temperature", TempSensor.Temperature);
                erdb.LogState(ID, "Temperature Average", TempSensor.AverageTemperature);
            }
        }
        public void Initialise(EngineRoomServiceDB erdb)
        {
            DBRow enabled  = erdb.GetLatestEvent(EngineRoomServiceDB.LogEventType.ENABLE, ID);
            DBRow disabled = erdb.GetLatestEvent(EngineRoomServiceDB.LogEventType.DISABLE, ID);

            bool enable = true;

            if (disabled != null)
            {
                enable = enabled == null ? false : enabled.GetDateTime("created").Ticks > disabled.GetDateTime("created").Ticks;
            }
            else if (enabled != null)
            {
                enable = true;
            }
            Enable(enable);

            _initialisedAt = DateTime.Now;

            String desc = String.Format("Initialised {0} water tanks ... tanks are {1}", Tanks.Count, Enabled ? "enabled" : "disabled");

            erdb.LogEvent(EngineRoomServiceDB.LogEventType.INITIALISE, ID, desc);
        }
        public void Monitor(EngineRoomServiceDB erdb, List <Message> messages, bool returnEventsOnly)
        {
            if (!Enabled)
            {
                return;
            }

            EngineRoomServiceDB.LogEventType let = EngineRoomServiceDB.LogEventType.INFO;
            String  desc = null;
            Message msg  = null;

            //see if engine is Running (and log)
            bool running = Running; // record to test change of state

            Running = RPM.AverageRPM > IS_RUNNING_RPM_THRESHOLD;
            bool isEvent = running != Running;

            //Running or not
            if (isEvent) //log
            {
                if (Running)
                {
                    erdb.LogEvent(EngineRoomServiceDB.LogEventType.ON, ID, String.Format("Current RPM = {0}", RPM.RPM));
                    LastOn = erdb.GetLatestEvent(EngineRoomServiceDB.LogEventType.ON, ID).GetDateTime("created");
                }
                else
                {
                    desc = LastOn == default(DateTime) ? "N/A" : String.Format("Running for {0}", (DateTime.Now - LastOn).ToString("c"));
                    erdb.LogEvent(EngineRoomServiceDB.LogEventType.OFF, ID, desc);
                    LastOff = erdb.GetLatestEvent(EngineRoomServiceDB.LogEventType.OFF, ID).GetDateTime("created");
                }
            }

            if (isEvent || !returnEventsOnly)
            {
                EngineRoomMessageSchema schema = new EngineRoomMessageSchema(new Message(MessageType.DATA));
                schema.AddEngine(this);
                messages.Add(schema.Message);
            }


            //some useful durations...
            long secsSinceLastOn  = LastOn == default(DateTime) ? -1 : (long)DateTime.Now.Subtract(LastOn).TotalSeconds;
            long secsSinceLastOff = LastOff == default(DateTime) ? -1 : (long)DateTime.Now.Subtract(LastOff).TotalSeconds;

            //Oil State
            OilState oilState = StateOfOil;

            if (Running && RPM.RPM > 100 && secsSinceLastOn > 30)
            {
                oilState = OilSensor.IsOn ? OilState.NO_PRESSURE : OilState.OK_ENGINE_ON;
            }
            else if (!Running && RPM.RPM == 0 && secsSinceLastOff > 30)
            {
                oilState = OilSensor.IsOff ? OilState.SENSOR_FAULT : OilState.OK_ENGINE_OFF;
            }

            msg        = null;
            isEvent    = oilState != StateOfOil;
            StateOfOil = oilState;
            String rpmDesc = String.Format("RPM (instant/average) = {0}/{1}", RPM.RPM, RPM.AverageRPM);

            switch (StateOfOil)
            {
            case OilState.NO_PRESSURE:
                let  = EngineRoomServiceDB.LogEventType.WARNING;
                desc = String.Format("Engine {0} Oil sensor {1} gives {2} ... {3}", Running ? "running for " + secsSinceLastOn : "off for " + secsSinceLastOff, OilSensor.State, StateOfOil, rpmDesc);
                msg  = BBAlarmsService.AlarmsMessageSchema.AlertAlarmStateChange(OilSensor.ID, BBAlarmsService.AlarmState.SEVERE, desc);
                break;

            case OilState.SENSOR_FAULT:
                let  = EngineRoomServiceDB.LogEventType.WARNING;
                desc = String.Format("Engine {0} Oil sensor {1} gives {2} ... {3}", Running ? "running for " + secsSinceLastOn : "off for " + secsSinceLastOff, OilSensor.State, StateOfOil, rpmDesc);
                msg  = BBAlarmsService.AlarmsMessageSchema.AlertAlarmStateChange(OilSensor.ID, BBAlarmsService.AlarmState.MODERATE, desc);
                break;

            case OilState.OK_ENGINE_OFF:
            case OilState.OK_ENGINE_ON:
                let  = EngineRoomServiceDB.LogEventType.INFO;
                desc = String.Format("Engine {0} Oil sensor {1} gives {2}... {3}", Running ? "running for " + secsSinceLastOn : "off for " + secsSinceLastOff, OilSensor.State, StateOfOil, rpmDesc);
                msg  = BBAlarmsService.AlarmsMessageSchema.AlertAlarmStateChange(OilSensor.ID, BBAlarmsService.AlarmState.OFF, desc);
                break;
            }

            if (msg != null && (isEvent || !returnEventsOnly))
            {
                messages.Add(msg);
                if (isEvent)
                {
                    erdb.LogEvent(let, OilSensor.ID, desc);
                }
            }


            //Temp state
            TemperatureState tempState = StateOfTemperature; //keep a record

            if (!Running || (TempSensor.AverageTemperature <= TemperatureThresholds[TemperatureState.OK]))
            {
                StateOfTemperature = TemperatureState.OK;
            }
            else if (TempSensor.AverageTemperature <= TemperatureThresholds[TemperatureState.HOT])
            {
                StateOfTemperature = TemperatureState.HOT;
            }
            else
            {
                StateOfTemperature = TemperatureState.TOO_HOT;
            }

            msg     = null;
            isEvent = tempState != StateOfTemperature;
            switch (StateOfTemperature)
            {
            case TemperatureState.TOO_HOT:
                let  = EngineRoomServiceDB.LogEventType.WARNING;
                desc = String.Format("Temp sensor: {0} {1}", TempSensor.AverageTemperature, StateOfTemperature);
                msg  = BBAlarmsService.AlarmsMessageSchema.AlertAlarmStateChange(TempSensor.ID, BBAlarmsService.AlarmState.CRITICAL, desc);
                break;

            case TemperatureState.HOT:
                let  = EngineRoomServiceDB.LogEventType.WARNING;
                desc = String.Format("Temp sensor: {0} {1}", TempSensor.AverageTemperature, StateOfTemperature);
                msg  = BBAlarmsService.AlarmsMessageSchema.AlertAlarmStateChange(TempSensor.ID, BBAlarmsService.AlarmState.SEVERE, desc);
                break;

            case TemperatureState.OK:
                let  = EngineRoomServiceDB.LogEventType.INFO;
                desc = String.Format("Temp sensor: {0} {1}", TempSensor.AverageTemperature, StateOfTemperature);
                msg  = BBAlarmsService.AlarmsMessageSchema.AlertAlarmStateChange(TempSensor.ID, BBAlarmsService.AlarmState.OFF, desc);
                break;
            }
            if (msg != null && (isEvent || !returnEventsOnly))
            {
                messages.Add(msg);
                if (isEvent)
                {
                    erdb.LogEvent(let, TempSensor.ID, desc);
                }
            }

            //RPM state
            RPMState rpmState = StateOfRPM;

            if (!Running)
            {
                StateOfRPM = RPMState.OFF;
            }
            else if (secsSinceLastOn > 10)
            {
                if (RPM.AverageRPM < RPMThresholds[RPMState.SLOW])
                {
                    StateOfRPM = RPMState.SLOW;
                }
                else if (RPM.AverageRPM < RPMThresholds[RPMState.NORMAL])
                {
                    StateOfRPM = RPMState.NORMAL;
                }
                else if (RPM.AverageRPM < RPMThresholds[RPMState.FAST])
                {
                    StateOfRPM = RPMState.FAST;
                }
                else
                {
                    StateOfRPM = RPMState.TOO_FAST;
                }
            }

            msg     = null;
            isEvent = rpmState != StateOfRPM;
            switch (StateOfRPM)
            {
            case RPMState.OFF:
            case RPMState.SLOW:
            case RPMState.NORMAL:
                let  = EngineRoomServiceDB.LogEventType.INFO;
                desc = String.Format("RPM (Instant/Average): {0}/{1} gives state {2}", RPM.RPM, RPM.AverageRPM, StateOfRPM);
                msg  = BBAlarmsService.AlarmsMessageSchema.AlertAlarmStateChange(RPM.ID, BBAlarmsService.AlarmState.OFF, desc);
                break;

            case RPMState.FAST:
                let  = EngineRoomServiceDB.LogEventType.WARNING;
                desc = String.Format("RPM (Instant/Average): {0}/{1} gives state {2}", RPM.RPM, RPM.AverageRPM, StateOfRPM);
                msg  = BBAlarmsService.AlarmsMessageSchema.AlertAlarmStateChange(RPM.ID, BBAlarmsService.AlarmState.MODERATE, desc);
                break;

            case RPMState.TOO_FAST:
                let  = EngineRoomServiceDB.LogEventType.WARNING;
                desc = String.Format("RPM (Instant/Average): {0}/{1} gives state {2}", RPM.RPM, RPM.AverageRPM, StateOfRPM);
                msg  = BBAlarmsService.AlarmsMessageSchema.AlertAlarmStateChange(RPM.ID, BBAlarmsService.AlarmState.SEVERE, desc);
                break;
            }
            if (msg != null && (isEvent || !returnEventsOnly))
            {
                messages.Add(msg);
                if (isEvent)
                {
                    erdb.LogEvent(let, RPM.ID, desc);
                }
            }
        }
        static public EngineRoomServiceDB Create(System.Configuration.ApplicationSettingsBase settings, String dbnameKey = null)
        {
            EngineRoomServiceDB db = dbnameKey != null?DB.Create <EngineRoomServiceDB>(settings, dbnameKey) : DB.Create <EngineRoomServiceDB>(settings);

            return(db);
        }