/// /// Copyright (c) 2017 Sensus Metering Systems /// using System; using System.Collections.Generic; using log4net; using TBF.BenchControl.Generic; using TBF.BenchControl.GenericDevices; using TBF.Boxes; using TBF.Resources; namespace TBF.BenchControl.Modbus.TankSelector { /// Enumeration of water tanks public enum TankId { None, Cold, /// Cold water tank Warm, /// Warm water tank Hot, /// Hot water tank } /// Enumeration of water tank activities public enum Activity { Unknown, Idle, /// Main tank is not interconnected with any other water tank Selected_Waiting1min, /// Selected water tank and main tank are interconnected, water is flowing to the main tank Selected, /// Selected water tank and main tank are interconnected Unselected_WaitingLevel, /// Water is being pumped from the main tank to the (un)selected water tank until main tank is empty } /// TankSelector state public enum State { Unknown, /// Initial state after the program startup NoTankSelected, ColdTankSelected_Waiting1min, ColdTankSelected, ColdTankUnselected_WaitingLevel, WarmTankSelected_Waiting1min, WarmTankSelected, WarmTankUnselected_WaitingLevel, HotTankSelected_Waiting1min, HotTankSelected, HotTankUnselected_WaitingLevel, } /// Quido digital out board outputs assignment public enum QuidoOutputs : ushort { None = 0, SelectColdTank = 0x0001, /// Quido output 1 SelectWarmTank = 0x0002, /// Quido output 2 SelectHotTank = 0x0004, /// Quido output 3 PumpToColdTank = 0x0008, /// Quido output 4 PumpToWarmTank = 0x0010, /// Quido output 5 PumpToHotTank = 0x0020, /// Quido output 6 Mixing = 0x0040, /// Quido output 7 HeatingHotTank = 0x0080, /// Quido output 8 HeatingWarmTank = 0x0100, /// Quido output 9 CoolingHotTank = 0x0200, /// Quido output 10 CoolingWarmTank = 0x0400, /// Quido output 11 CoolingColdTank = 0x0800, /// Quido output 12 } /// Related control board digital inputs public enum DigitalInputs : ulong { HotTankSelected = (ulong)(1 << 18), WarmTankSelected = (ulong)(1 << 19), ColdTankSelected = (ulong)(1 << 20), Pumping_WaitingLevel = (ulong)(1 << 21), } public class TankSelector : ComponentBase, ISequenceCondition, IDevice { private static readonly ILog log = LogManager.GetLogger(typeof(TankSelector)); public override string ToString() { return string.Format("TankSelector({0})", Cfg.ToString(1)); } readonly TankSelectorCfg tankSelectorCfg; readonly TBF.BenchControl.Modbus.QuidoRS.QuidoRS quidoRS; readonly TBF.BenchControl.Modbus.Easytherm.Easytherm hotHeating; readonly TBF.BenchControl.Modbus.Easytherm.Easytherm hotCooling; readonly TBF.BenchControl.Modbus.Easytherm.Easytherm warmHeating; readonly TBF.BenchControl.Modbus.Easytherm.Easytherm warmCooling; readonly TBF.BenchControl.Modbus.Easytherm.Easytherm coldCooling; public State State; public TankId CurrentTank { get { return GetTankIdFromState(State); } } public Activity CurrentActivity { get { return GetActivityFromState(State); } } ulong latchedDigitalInputs; ulong lastDigitalInputs; ulong lastValueRepetitions; public TankId GetTankIdFromState(State state) { switch (state) { case State.ColdTankSelected_Waiting1min: case State.ColdTankSelected: case State.ColdTankUnselected_WaitingLevel: return TankId.Cold; case State.WarmTankSelected_Waiting1min: case State.WarmTankSelected: case State.WarmTankUnselected_WaitingLevel: return TankId.Warm; case State.HotTankSelected_Waiting1min: case State.HotTankSelected: case State.HotTankUnselected_WaitingLevel: return TankId.Hot; default: return TankId.None; } } public Activity GetActivityFromState(State state) { switch (state) { case State.NoTankSelected: return Activity.Idle; case State.ColdTankSelected_Waiting1min: case State.WarmTankSelected_Waiting1min: case State.HotTankSelected_Waiting1min: return Activity.Selected_Waiting1min; case State.ColdTankSelected: case State.WarmTankSelected: case State.HotTankSelected: return Activity.Selected; case State.ColdTankUnselected_WaitingLevel: case State.WarmTankUnselected_WaitingLevel: case State.HotTankUnselected_WaitingLevel: return Activity.Unselected_WaitingLevel; default: return Activity.Unknown; } } public TankSelector() { CreateConditions(false); } public TankSelector(Generic.IComponentCfg cfg, IList components) : base(cfg) { tankSelectorCfg = cfg as TankSelectorCfg; quidoRS = (TBF.BenchControl.Modbus.QuidoRS.QuidoRS)TbfComponents.FindComponent(cfg.ParentName, components); if (quidoRS == null) throw new Exception(string.Format("Cannot find parent '{0}' of the component {1}", cfg.ParentName, Name)); hotHeating = (TBF.BenchControl.Modbus.Easytherm.Easytherm)TbfComponents.FindComponent(tankSelectorCfg.HotWaterHeating, components); hotCooling = (TBF.BenchControl.Modbus.Easytherm.Easytherm)TbfComponents.FindComponent(tankSelectorCfg.HotWaterCooling, components); warmHeating = (TBF.BenchControl.Modbus.Easytherm.Easytherm)TbfComponents.FindComponent(tankSelectorCfg.WarmWaterHeating, components); warmCooling = (TBF.BenchControl.Modbus.Easytherm.Easytherm)TbfComponents.FindComponent(tankSelectorCfg.WarmWaterCooling, components); coldCooling = (TBF.BenchControl.Modbus.Easytherm.Easytherm)TbfComponents.FindComponent(tankSelectorCfg.ColdWaterCooling, components); if (hotHeating == null) throw new Exception(string.Format("Component '{0}' cannot find temperature controller '{1}'", Name, tankSelectorCfg.HotWaterHeating)); if (hotCooling == null) throw new Exception(string.Format("Component '{0}' cannot find temperature controller '{1}'", Name, tankSelectorCfg.HotWaterCooling)); if (warmHeating == null) throw new Exception(string.Format("Component '{0}' cannot find temperature controller '{1}'", Name, tankSelectorCfg.WarmWaterHeating)); if (warmCooling == null) throw new Exception(string.Format("Component '{0}' cannot find temperature controller '{1}'", Name, tankSelectorCfg.WarmWaterCooling)); if (coldCooling == null) throw new Exception(string.Format("Component '{0}' cannot find temperature controller '{1}'", Name, tankSelectorCfg.ColdWaterCooling)); CreateConditions(true); State = State.Unknown; log.Fatal(this.ToString()); } /// /// Recover the TankSelector state from ContolBoard.DigitalInputs /// void RecoverState() { latchedDigitalInputs = StateMachine.ControlBoard.DigitalInputs; if ((latchedDigitalInputs & (ulong)DigitalInputs.ColdTankSelected) != 0) { if ((latchedDigitalInputs & (ulong)DigitalInputs.Pumping_WaitingLevel) == 0) State = State.ColdTankSelected; else State = State.ColdTankUnselected_WaitingLevel; } else if ((latchedDigitalInputs & (ulong)DigitalInputs.WarmTankSelected) != 0) { if ((latchedDigitalInputs & (ulong)DigitalInputs.Pumping_WaitingLevel) == 0) State = State.WarmTankSelected; else State = State.WarmTankUnselected_WaitingLevel; } else if ((latchedDigitalInputs & (ulong)DigitalInputs.HotTankSelected) != 0) { if ((latchedDigitalInputs & (ulong)DigitalInputs.Pumping_WaitingLevel) == 0) State = State.HotTankSelected; else State = State.HotTankUnselected_WaitingLevel; } else { State = State.NoTankSelected; } log.FatalFormat("{0} successfully initialized, DigiInputs={1}, initial state={2}", Name, latchedDigitalInputs.ToString("X16"), State); } /// /// Detect 'State.NoTankSelected' from ContolBoard.DigitalInputs /// void DetectNoTankSelected() { ulong digiIn = StateMachine.ControlBoard.DigitalInputs; if (digiIn == latchedDigitalInputs) { return; } else if (digiIn == lastDigitalInputs) { if (++lastValueRepetitions == 3) { latchedDigitalInputs = lastDigitalInputs; if ((latchedDigitalInputs & (ulong)DigitalInputs.ColdTankSelected) == 0 && (latchedDigitalInputs & (ulong)DigitalInputs.WarmTankSelected) == 0 && (latchedDigitalInputs & (ulong)DigitalInputs.HotTankSelected) == 0 && (latchedDigitalInputs & (ulong)DigitalInputs.Pumping_WaitingLevel) == 0) { State = State.NoTankSelected; } } } else { lastDigitalInputs = digiIn; /// new value stored lastValueRepetitions = 1; /// one repetition so far } } /// /// IDevice interface implementation /// public void Initialize() { SetOutputs(0); /// Reset all Quido outputs } public void RunDeviceBefore() { if (StateMachine.Time == 3) { RecoverState(); } else if (StateMachine.Time > 3) { DetectNoTankSelected(); } } public void RunDeviceAfter() {} public void StopDevice() {} /// /// Quido wrappers /// public void SetOutputs(ushort outputs) { quidoRS.SetOutputs(outputs); } public void SetBit(ushort bitMask) { quidoRS.SetBit(bitMask); } public void ResetBit(ushort bitMask) { quidoRS.ResetBit(bitMask); } /// /// ISequenceCondition interface implementation (conditions in transition sequences) /// IList sequenceConditionNames; IList sequenceConditions; public int ConditionsCount { get { return sequenceConditions != null ? sequenceConditions.Count : 0; } } /// Strings are added to the combo-box for transition sequence condition selection public string ConditionName(int i) { if (sequenceConditionNames != null && i < sequenceConditionNames.Count && i >= 0) return sequenceConditionNames[i]; else return string.Empty; } /// Operations are executed as a part of a transition sequence public IOperation ConditionOp(int i) { if (sequenceConditions != null && i < sequenceConditions.Count && i >= 0) return sequenceConditions[i]; else return null; } void ClearConditions() { sequenceConditionNames = new List(); sequenceConditions = new List(); } void AddCondition(string conditionName, bool createOperation, IOperation conditionOperation) { sequenceConditionNames.Add(conditionName); if (createOperation) sequenceConditions.Add(conditionOperation); } /// /// Create a list of conditions /// /// true = create names and condition operations, false = create names only void CreateConditions(bool createOps) { ClearConditions(); AddCondition(Strings.Select_COLD_tank, createOps, new SelectTankOp(this, TankId.Cold)); AddCondition(Strings.Select_WARM_tank, createOps, new SelectTankOp(this, TankId.Warm)); AddCondition(Strings.Select_HOT_tank, createOps, new SelectTankOp(this, TankId.Hot)); AddCondition(Strings.Unselect_current_tank, createOps, new UnselectTankOp(this)); AddCondition(Strings.Set_hot_water_heating_temperature, createOps, hotHeating.SetRequiredTemperatureOp()); AddCondition(Strings.Set_hot_water_cooling_temperature, createOps, hotCooling.SetRequiredTemperatureOp()); AddCondition(Strings.Set_warm_water_heating_temperature, createOps, warmHeating.SetRequiredTemperatureOp()); AddCondition(Strings.Set_warm_water_cooling_temperature, createOps, warmCooling.SetRequiredTemperatureOp()); AddCondition(Strings.Set_cold_water_cooling_temperature, createOps, coldCooling.SetRequiredTemperatureOp()); AddCondition("Start heating hot water", createOps, new SetQuidoBitOp(quidoRS, QuidoOutputs.HeatingHotTank, true)); AddCondition("Stop heating hot water", createOps, new SetQuidoBitOp(quidoRS, QuidoOutputs.HeatingHotTank, false)); AddCondition("Start cooling hot water", createOps, new SetQuidoBitOp(quidoRS, QuidoOutputs.CoolingHotTank, true)); AddCondition("Stop cooling hot water", createOps, new SetQuidoBitOp(quidoRS, QuidoOutputs.CoolingHotTank, false)); AddCondition("Start heating warm water", createOps, new SetQuidoBitOp(quidoRS, QuidoOutputs.HeatingWarmTank, true)); AddCondition("Stop heating warm water", createOps, new SetQuidoBitOp(quidoRS, QuidoOutputs.HeatingWarmTank, false)); AddCondition("Start cooling warm water", createOps, new SetQuidoBitOp(quidoRS, QuidoOutputs.CoolingWarmTank, true)); AddCondition("Stop cooling warm water", createOps, new SetQuidoBitOp(quidoRS, QuidoOutputs.CoolingWarmTank, false)); AddCondition("Start cooling cold water", createOps, new SetQuidoBitOp(quidoRS, QuidoOutputs.CoolingColdTank, true)); AddCondition("Stop cooling cold water", createOps, new SetQuidoBitOp(quidoRS, QuidoOutputs.CoolingColdTank, false)); AddCondition("Start mixing water", createOps, new SetQuidoBitOp(quidoRS, QuidoOutputs.Mixing, true)); AddCondition("Stop mixing water", createOps, new SetQuidoBitOp(quidoRS, QuidoOutputs.Mixing, false)); } } }