using Common; using Dirichlet.Numerics; using log4net; using SchematicDrawing; /// /// Copyright (c) 2020 Sensus Slovensko a.s. /// using System; using System.Collections.Generic; using System.Diagnostics; using System.Security.Cryptography.Xml; using TBF.Rig.ControlBoard; using TBF.Rig.Generic; namespace TBF.Rig.Modbus.PumpFM.DanfossVLT { /// /// See page 20 of MG10S202 Operational Instructions Modbus RTU /// public enum CW : ushort { PresetRefLsb = (1 << 0), PresetRefMsb = (1 << 1), Not_DcBraking = (1 << 2), Not_CoastingStop = (1 << 3), Not_QuickStop = (1 << 4), Not_FreezeFreq = (1 << 5), Start_NotRampStop = (1 << 6), Reset = (1 << 7), Jog = (1 << 8), Ramp2_NotRamp1 = (1 << 9), DataValid = (1 << 10), Relay01Activ = (1 << 11), DOut46Activ = (1 << 12), SelectSetupLsb = (1 << 13), SelectSetupMsb = (1 << 14), Reversing = (1 << 15), } /// /// See page 22 of MG10S202 Operational Instructions Modbus RTU /// public enum SW : ushort { ControlReady = (1 << 0), DriveReady = (1 << 1), CoastingStop = (1 << 2), Trip = (1 << 3), TripLock = (1 << 6), Warning = (1 << 7), SpeedEqRef = (1 << 8), RemoteCtrl = (1 << 9), FreqLimitOK = (1 << 10), MotorRunning = (1 << 11), VoltageWarn = (1 << 13), CurrentLimit = (1 << 14), ThermalWarn = (1 << 15), } public enum Function : byte { ReadCoilStatus = 0x01, ForceSingleCoil = 0x05, ForceMultipleCoils = 0x0F, ReadHoldingRegisters = 0x03, PresetSingleRegister = 0x06, PresetMultipleRegisters = 0x10, } public class Pump : ComponentBase, IDevice, IOperation, GenericDevices.IPumpFM, GenericDevices.IValve, IDrawingItCmpntWithSetpoint { private static readonly ILog log = LogManager.GetLogger(typeof(Pump)); public override string ToString() { return string.Format("{0}({1})", ClassName, Cfg.ToString(-1)); } private readonly PumpCfg pumpCfg; public IDrawingItem DrawingItem { get { return pumpCfg as IDrawingItem; } } public int Delay { get { return pumpCfg.Delay; } } public ValveCategory Category { get { return ValveCategory.Feeding; } } /// Pump category is Feeding public GenericDevices.IValve CoupledTo { get { return null; } } public bool InvertCouple { get { return false; } } public int LagOpening { get { return 0; } } public int LagClosing { get { return 0; } } /// /// Pump power in [%] in range 0 .. 100.0 /// double power; public double Power { get { return power; } } /// public double SetpointVal { get { return power; } set { power = (float)value; if (power != 0) TurnOn(power); } } /// public void IncreaseSetpoint() { power += 5.0; if (power > 100.0) power = 100.0; TurnOn(power); } /// public void DecreaseSetpoint() { power -= 5.0; if (power <= 0) { power = 0; TurnOff(); } else { TurnOn(power); } } Common.Modbus modbus; IControlBoard controlBoard; int bitNr; /// 0 .. 63 public UInt128 Mask; /// derived from bitPosition in the constructor public bool State { get { return (controlBoard.Route & Mask) != 0; } } public Pump() { } /// /// Ambient temperature / humidity / pressure meter 'Greco' connected via serial interface (RS232) /// Connection settings: 19200 Bd 8-bits No-parity 1-stop-bit Flow control: none or hardware. /// public Pump(Generic.IComponentCfg cfg, IList components) : base(cfg) { pumpCfg = cfg as PumpCfg; } public override void Initialize() { if (DebugLevel == DebugMode.Normal || DebugLevel == DebugMode.DetectedOn) { modbus = TbfComponents.FindComponent(pumpCfg.ParentName) as Common.Modbus; if (modbus == null) throw new Exception("Cannot find " + Name + " parent"); controlBoard = null; foreach (var name in new string[] { "CB", "UniCB" }) { if ((controlBoard = TbfComponents.FindComponent(name) as IControlBoard) != null) break; } if (controlBoard == null) throw new Exception("Cannot find a control board"); bitNr = pumpCfg.BitNr; Mask = (((UInt128)1) << bitNr); modbus.ComponentNames[pumpCfg.ModbusAddress] = Name; 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); } else { log.FatalFormat("{0} simulated: {1}", Name, this); } } /// /// IDevice interface /// public void RunDeviceBefore() { if (DebugLevel == DebugMode.Normal || DebugLevel == DebugMode.DetectedOn) { if (modbus.ReceivedTelegrams[pumpCfg.ModbusAddress].Count > 0) { byte[] telegram = modbus.ReceivedTelegrams[pumpCfg.ModbusAddress].Dequeue(); string s = Telegram.LogTelegram(string.Format("Telegram received from {0}: ", Name), telegram); log.Warn(s); } } } public void RunDeviceAfter() { } public void StopDevice() { } public void StopDevice2() { } /// /// Turn the pmp on using Modbus commad, use the variable frequency /// /// Frequency in percent (-200.0 .. 200.0) public void TurnOn(double powerArg) { log.DebugFormat("{0}.TurnOn({1})", Name, powerArg); UInt16 controlWord = (UInt16)(CW.Not_DcBraking | CW.Not_CoastingStop | CW.Not_QuickStop | CW.Not_FreezeFreq | CW.Start_NotRampStop | CW.DataValid); this.power = powerArg; UInt16 reference = (UInt16)Math.Round(0x4000 * powerArg / 100.0); if (DebugLevel == DebugMode.Normal || DebugLevel == DebugMode.DetectedOn) { byte[] msg = new byte[13]; msg[0] = (byte)pumpCfg.ModbusAddress; msg[1] = (byte)Function.ForceMultipleCoils; msg[2] = 0; /// Data Hi msg[3] = 0; /// Data Lo msg[4] = 0; /// Nr. of coils Hi msg[5] = 0x20; /// Nr. of coils Lo msg[6] = 4; /// Byte count msg[7] = (byte)(controlWord & 0xFF); /// Control Word Lo msg[8] = (byte)(controlWord >> 8); /// Control Word Hi msg[9] = (byte)(reference & 0xFF); /// Serial Com Reference Lo msg[10] = (byte)(reference >> 8); /// Serial Com Reference Hi /// modbus.SendMessage(msg, Name); /// --- DIAGNOSTIKA --- LiveLogDiag.Log1("--------------------------------------------"); LiveLogDiag.Log1($"TurnOff controlWord=0x{controlWord:X4}"); LiveLogDiag.Log1($"TurnOff reference={reference}"); LiveLogDiag.Log1("TurnOff msg = " + BitConverter.ToString(msg)); } } /// /// Turn the pmp off using Modbus command /// public void TurnOff() { log.DebugFormat("{0}.TurnOff()", Name); UInt16 controlWord = (UInt16)(CW.Not_DcBraking | CW.Not_CoastingStop | CW.Not_QuickStop | CW.Not_FreezeFreq | //CW.Start_NotRampStop | CW.DataValid); this.power = 0; UInt16 reference = 0; if (DebugLevel == DebugMode.Normal || DebugLevel == DebugMode.DetectedOn) { byte[] msg = new byte[13]; msg[0] = (byte)pumpCfg.ModbusAddress; msg[1] = (byte)Function.ForceMultipleCoils; msg[2] = 0; /// Data Hi msg[3] = 0; /// Data Lo msg[4] = 0; /// Nr. of coils Hi msg[5] = 0x20; /// Nr. of coils Lo msg[6] = 4; /// Byte count msg[7] = (byte)(controlWord & 0xFF); /// Control Word Lo msg[8] = (byte)(controlWord >> 8); /// Control Word Hi msg[9] = (byte)(reference & 0xFF); /// Serial Com Reference Lo msg[10] = (byte)(reference >> 8); /// Serial Com Reference Hi //Telegram.UpdateTelegramCRC(msg); modbus.SendMessage(msg, Name); /// --- DIAGNOSTIKA --- LiveLogDiag.Log1("--------------------------------------------"); LiveLogDiag.Log1($"TurnOff controlWord=0x{controlWord:X4}"); LiveLogDiag.Log1($"TurnOff reference={reference}"); LiveLogDiag.Log1("TurnOff msg = " + BitConverter.ToString(msg)); } } /// /// The currently running operation. /// public enum CurrentOp { None, TurnOn, TurnOff, } /// CurrentOp currentOp; double powerForOp; /// /// Turn the pump frequency inverter on. /// public IOperation TurnOnOp(double powerArg) { if (currentOp != CurrentOp.None) throw new Exception("Sequence error"); currentOp = CurrentOp.TurnOn; powerForOp = powerArg; return this; } /// /// Turn the pump frequency inverter on. /// public IOperation TurnOffOp() { if (currentOp != CurrentOp.None) throw new Exception("Sequence error"); currentOp = CurrentOp.TurnOff; return this; } /// Start the operation public void Start() { switch (currentOp) { case CurrentOp.TurnOn: TurnOn(powerForOp); return; case CurrentOp.TurnOff: default: TurnOff(); return; } } /// Run the operation public Event Run() { return Event.TurnPumpOnOffDone; } /// Stop the operation public void Stop() { currentOp = CurrentOp.None; } } /// /// Diagnostic logging /// Activated by compilation condition: DIAG_ENDURANCE_LOG /// static class LiveLogDiag { [Conditional("DIAG_LOG_PUMP")] public static void Log1(string format, params object[] args) { //LiveLogCache.Instance.AddLog(string.Format(format, args)); } } }