/// /// Copyright (c) 2022 Sensus Slovensko a.s. /// using System; using System.Collections.Generic; using System.Drawing; using log4net; using Common; using Config.Entities; using SchematicDrawing; using TBF.Boxes; using TBF.Rig.GenericDevices; namespace TBF.Rig.Modbus.TempControl.Novus { public class TControl : ComponentBase, Generic.IDevice, ITempControl, ISequenceCondition, IDrawingItCmpntWithSetpoint, IDrawingItCmpntWithMeasuredVal, IDrawingItCmpntWithCustomBmp { private static readonly ILog log = LogManager.GetLogger(typeof(TControl)); public override string ToString() { return string.Format("{0}({1})", ClassName, Cfg.ToString(-1)); } const UInt16 SetpointAddress = 0; /// Temperature setpoint address const UInt16 ProcessValueAddress = 1; /// Actual temperature address const UInt16 N1200_FWVerAddr = 7; /// Firmware ver. address (N1200/N1200HC) const UInt16 N1200_CtrlIdAddr = 8; /// Controller ID address (N1200/N1200HC) const UInt16 N1200_Status1Addr = 6; /// Status word 1 address (N1200/N1200HC) const UInt16 N1200_Status2Addr = 9; /// Status word 2 address (N1200/N1200HC) const int N1200_WordsCount = 10; const UInt16 N1040_FWVerAddr = 8; /// Firmware ver. address (N1040) const UInt16 N1040_CtrlIdAddr = 9; /// Controller ID address (N1040) const UInt16 N1040_Status1Addr = 12; /// Status word 1 address (N1040) const UInt16 N1040_Status2Addr = 13; /// Status word 2 address (N1040) const int N1040_WordsCount = 14; readonly TControlCfg novusCfg; public IDrawingItem DrawingItem { get { return novusCfg as IDrawingItem; } } public double ProcParamA { get { return novusCfg.ProcParams != null ? novusCfg.ProcParams.Temp_A : 0; } } public double ProcParamB { get { return novusCfg.ProcParams != null ? novusCfg.ProcParams.Temp_B : 0; } } public double ProcParamC { get { return novusCfg.ProcParams != null ? novusCfg.ProcParams.Temp_C : 0; } } public double ProcParamD { get { return novusCfg.ProcParams != null ? novusCfg.ProcParams.Temp_D : 0; } } public double ProcParamE { get { return novusCfg.ProcParams != null ? novusCfg.ProcParams.Temp_E : 0; } } Common.Modbus modbus; int ticketNumber; /// 0 .. number of devices registered for regular polling - 1 public bool MsrmntAvailable { get { return true; } } public double MeasuredVal { get { return ReadTemperature(); } } public double MsrdValLimLo { get { return Units.ConvertFrom(MsrdUnit, novusCfg.MsrdValLimLo); } } public double MsrdValLimHi { get { return Units.ConvertFrom(MsrdUnit, novusCfg.MsrdValLimHi); } } public Unit MsrdUnit { get { return novusCfg.MsrdUnit; } } public string MsrdFormat { get { return novusCfg.MsrdFormat; } } public string AltString { get { return string.Empty; } } public double SetpointVal { get { return temperatureSetpoint; } set { SetTemperatureSetpoint(value); } } /// public void IncreaseSetpoint() { double tempStepC = isFahrenheit ? Units.ConvertFrom(Unit.F, novusCfg.SetpStep) : novusCfg.SetpStep; double newSetpoint; if (novusCfg.RoundToSetpStep) { double roundedSetpoint = tempStepC * Math.Round(temperatureSetpoint / tempStepC); newSetpoint = (roundedSetpoint <= temperatureSetpoint) ? (roundedSetpoint + tempStepC) : roundedSetpoint; } else { newSetpoint = temperatureSetpoint + tempStepC; } SetTemperatureSetpoint(Math.Min(newSetpoint, 95.0)); } /// public void DecreaseSetpoint() { double tempStepC = isFahrenheit ? Units.ConvertFrom(Unit.F, novusCfg.SetpStep) : novusCfg.SetpStep; double newSetpoint; if (novusCfg.RoundToSetpStep) { double roundedSetpoint = tempStepC * Math.Round(temperatureSetpoint / tempStepC); newSetpoint = (roundedSetpoint >= temperatureSetpoint) ? (roundedSetpoint - tempStepC) : roundedSetpoint; } else { newSetpoint = temperatureSetpoint - tempStepC; } SetTemperatureSetpoint(Math.Max(newSetpoint, 5.0)); } public DrawingShape GetCustomBitmap() { switch (novusCfg.Sz) { case Sz.S: return isHeating ? (isCooling ? TControlCfg.DSBothS : TControlCfg.DSHeatingS) : (isCooling ? TControlCfg.DSCoolingS : TControlCfg.DSIdleS); case Sz.M: return isHeating ? (isCooling ? TControlCfg.DSBothM : TControlCfg.DSHeatingM) : (isCooling ? TControlCfg.DSCoolingM : TControlCfg.DSIdleM); default: case Sz.L: return isHeating ? (isCooling ? TControlCfg.DSBothL : TControlCfg.DSHeatingL) : (isCooling ? TControlCfg.DSCoolingL : TControlCfg.DSIdleL); case Sz.XL: return isHeating ? (isCooling ? TControlCfg.DSBothXL : TControlCfg.DSHeatingXL) : (isCooling ? TControlCfg.DSCoolingXL : TControlCfg.DSIdleXL); } } bool novusDataValid; bool temperatureSetPointValid { get { return novusDataValid; } } Int16 status1; Int16 status2; Int16 fwVersion; Int16 controllerId; double temperatureSetpoint; double actualTemperature; bool isHeating { get { switch (novusCfg.Type) { case Type.N1200HC: case Type.N1200: return (status2 & (1 << 11)) != 0; /// Output 1 set case Type.N1040: default: return false; /// isHeating info unknown } } } bool isCooling { get { switch (novusCfg.Type) { case Type.N1200HC: case Type.N1200: return (status2 & (1 << 12)) != 0; /// Output 2 set case Type.N1040: default: return false; /// isCooling info unknown } } } bool isFahrenheit { get { return (status2 & (1 << 9)) != 0; } } public TControl() { } public TControl(Generic.IComponentCfg cfg, IList components) : base(cfg) { novusCfg = cfg as TControlCfg; CreateConditions(); } /// Initialize this device public override void Initialize() { novusDataValid = false; if (novusCfg.DebugLevel == DebugMode.Simulate) { actualTemperature = 22; temperatureSetpoint = 18; log.FatalFormat("{0} simulated: {1}", Name, this); } else { modbus = TbfComponents.FindComponent(novusCfg.ParentName) as Common.Modbus; if (modbus == null) throw new Exception("Cannot find " + Name + " parent"); modbus.ComponentNames[novusCfg.ModbusAddress] = Name; ticketNumber = modbus.RegisterForPolling(); TBF.UiBridge.Bridge.SetpointChangeHandler += delegate(object sndr, TBF.UiBridge.SetpointChangeArgs args) { if (args.Name == Name) { if (args.Increase) IncreaseSetpoint(); else DecreaseSetpoint(); } }; log.FatalFormat("{0} initialized: {1}", Name, this); } } /// Run this device public void RunDeviceBefore() { if (DebugLevel != DebugMode.Simulate && DebugLevel != DebugMode.FailureDuringOperation) { while (modbus.ReceivedTelegrams[novusCfg.ModbusAddress].Count > 0) { byte[] telegram = modbus.ReceivedTelegrams[novusCfg.ModbusAddress].Dequeue(); if (telegram.Length == 5) { log.InfoFormat("[{0}] received 5 bytes", Name); } else if ((novusCfg.Type == Type.N1200HC || novusCfg.Type == Type.N1200) && telegram.Length == 2 * N1200_WordsCount + 5 && telegram[1] == (byte)Function.ReadHoldingRegisters && telegram[2] == 2 * N1200_WordsCount) { Int16[] values = new Int16[N1200_WordsCount]; for (int i = 0; i < values.Length; i++) { values[i] = (Int16)(256 * telegram[2 * i + 3] + telegram[2 * i + 4] - ((telegram[2 * i + 3] & 0x80) != 0 ? 65536 : 0)); } status1 = values[N1200_Status1Addr]; status2 = values[N1200_Status2Addr]; fwVersion = values[N1200_FWVerAddr]; controllerId = values[N1200_CtrlIdAddr]; double setpointCorF = (double)values[SetpointAddress] * 0.1; temperatureSetpoint = isFahrenheit ? Units.ConvertFrom(Unit.F, setpointCorF) : setpointCorF; double temperatureCorF = (double)values[ProcessValueAddress] * 0.1; actualTemperature = isFahrenheit ? Units.ConvertFrom(Unit.F, temperatureCorF) : temperatureCorF; novusDataValid = true; log.InfoFormat("[{0}] setpoint = {1} temperature = {2}", Name, temperatureSetpoint, actualTemperature); log.InfoFormat("[{0}] s1=0x{1} s2=0x{2} fw={3} id={4}", Name, status1.ToString("X4"), status2.ToString("X4"), fwVersion, controllerId == 18 ? "N1200HC" : controllerId == 48 ? "N1200" : "unknown"); } else if (novusCfg.Type == Type.N1040 && telegram.Length == 2 * N1040_WordsCount + 5 && telegram[1] == (byte)Function.ReadHoldingRegisters && telegram[2] == 2 * N1040_WordsCount) { Int16[] values = new Int16[N1040_WordsCount]; for (int i = 0; i < values.Length; i++) { values[i] = (Int16)(256 * telegram[2 * i + 3] + telegram[2 * i + 4] - ((telegram[2 * i + 3] & 0x80) != 0 ? 65536 : 0)); } status1 = values[N1040_Status1Addr]; status2 = values[N1040_Status2Addr]; fwVersion = values[N1040_FWVerAddr]; controllerId = values[N1040_CtrlIdAddr]; double setpointCorF = (double)values[SetpointAddress] * 0.1; temperatureSetpoint = isFahrenheit ? Units.ConvertFrom(Unit.F, setpointCorF) : setpointCorF; double temperatureCorF = (double)values[ProcessValueAddress] * 0.1; actualTemperature = isFahrenheit ? Units.ConvertFrom(Unit.F, temperatureCorF) : temperatureCorF; novusDataValid = true; log.InfoFormat("[{0}] setpoint = {1} temperature = {2}", Name, temperatureSetpoint, actualTemperature); log.InfoFormat("[{0}] s1=0x{1} s2=0x{2} fw={3} id={4}", Name, status1.ToString("X4"), status2.ToString("X4"), fwVersion, controllerId == 66 ? "N1040" : "unknown"); } else { log.InfoFormat("[{0}] received soemthing else", Name); } } } else if (novusCfg.DebugLevel == DebugMode.Simulate) { switch ((StateMachine.Time / 3) % 3) { case 0: status2 = 0; break; case 1: status2 = 1 << 11; break; case 2: status2 = 1 << 12; break; } } } /// Run this device public void RunDeviceAfter() { if (DebugLevel != DebugMode.Simulate && DebugLevel != DebugMode.FailureDuringOperation && modbus.IsMyTurn(ticketNumber)) { if (novusCfg.Type == Type.N1200HC || novusCfg.Type == Type.N1200) { /// Read SP (setpoint), PV (actual temperature), status1, status2, status3, FW version etc. SendValueToNovus(Function.ReadHoldingRegisters, 0, N1200_WordsCount); } else if (novusCfg.Type == Type.N1040) { /// Read SP (setpoint) and PV (actual temperature) SendValueToNovus(Function.ReadHoldingRegisters, 0, N1040_WordsCount); } } } public void StopDevice() { } public void StopDevice2() { } /// /// Set temperature controller setpoint to specified value /// /// Temperature in °C /// true = OK, false = any error public bool SetTemperatureSetpoint(double temperatureC) { if (temperatureC != 0 && (temperatureC < 5.0 || temperatureC > 95.0)) { return false; /// required temperature out of range } if (novusDataValid && (temperatureC == temperatureSetpoint)) { return true; /// already set to the required temperature } if (temperatureC != 0) /// temperature==0 disables sending the setpoint { double reqTemp = isFahrenheit ? Units.ConvertTo(Unit.F, temperatureC) : temperatureC; Int16 tempSetpointInt16 = Convert.ToInt16(Math.Round(10 * reqTemp)); /// setpoint in 0.1 °C or °F ushort destAddress = SetpointAddress; /// SendValueToNovus(Function.PresetSingleRegister, destAddress, tempSetpointInt16); } temperatureSetpoint = temperatureC; return true; } void SendValueToNovus(Function function, UInt16 address, Int16 value) { byte[] msg = new byte[8]; msg[0] = (byte)novusCfg.ModbusAddress; msg[1] = (byte)function; msg[2] = (byte)(address >> 8); /// Relative address Hi msg[3] = (byte)(address & 0xFF); /// Relative address Lo msg[4] = (byte)(value >> 8); /// Data Hi msg[5] = (byte)(value & 0xFF); /// Data Lo modbus.SendMessage(msg, Name); } /// /// Operations, etc. /// public double ReadTemperature() { return actualTemperature; } /// /// Events: SetOpututsDone, Error /// /// SetOutputsOp instance reference casted to IOperaton public IOperation SetTemperatureSetpointOp(double temperature) { return new SetTemperatureSetpointOp(this, SetpSource.TempArgument, temperature); } public IOperation ReadTempOp(ref DoubleBox temp) { return new ReadTempOp(this, ref temp); } public IOperation ReadTempOp(ref DoubleBox temp, Event eventDone) { return new ReadTempOp(this, ref temp, eventDone); } /// /// 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, IOperation conditionOperation) { sequenceConditionNames.Add(conditionName); sequenceConditions.Add(conditionOperation); } /// /// Create a list of conditions /// /// true = create names and condition operations, false = create names only void CreateConditions() { ClearConditions(); AddCondition(string.Format("Set {0} setpoint A", Name), new SetTemperatureSetpointOp(this, SetpSource.ProcParamA, 0)); AddCondition(string.Format("Set {0} setpoint B", Name), new SetTemperatureSetpointOp(this, SetpSource.ProcParamB, 0)); AddCondition(string.Format("Set {0} setpoint C", Name), new SetTemperatureSetpointOp(this, SetpSource.ProcParamC, 0)); AddCondition(string.Format("Set {0} setpoint D", Name), new SetTemperatureSetpointOp(this, SetpSource.ProcParamD, 0)); AddCondition(string.Format("Set {0} setpoint E", Name), new SetTemperatureSetpointOp(this, SetpSource.ProcParamE, 0)); } } }