/// /// Copyright (c) 2013-2015 Sensus Metering Systems /// using System; using System.Collections.Generic; using System.Globalization; using System.IO.Ports; using System.Text; using log4net; using TBF.BenchControl.Generic; using TBF.Boxes; namespace TBF.BenchControl.Danfoss.VLT2800 { /// /// 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 { private static readonly ILog log = LogManager.GetLogger(typeof(Pump)); public override string ToString() { return string.Format("Pump-Danfoss-VLT2800({0})", Cfg.ToString(1)); } private readonly PumpCfg pumpCfg; 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.0f /// private float power; public float Power { get { return power; } } readonly Elde.ControlBoardDev controlBoard; readonly int bitPosition; /// 0 .. 63 public readonly ulong Mask; /// derived from bitPosition in the constructor public bool State { get { return (controlBoard.Route & Mask) != 0; } } /// Private fields SerialPort serialPort; bool dataToSendReady; byte[] dataToSend; StringBuilder responseBuilder; /// /// 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(PumpCfg cfg, IList components) : base(cfg) { pumpCfg = cfg; controlBoard = (Elde.ControlBoardDev)TbfComponents.FindComponent("CB", components); /// TODO: replace "CB" if (controlBoard == null) throw new Exception("Cannot find " + Name + " parent"); bitPosition = pumpCfg.BitPosition; Mask = (((ulong)1) << bitPosition); Mask |= (((ulong)1) << (bitPosition + 1)); log.Debug(this.ToString()); } public void Initialize() { if (pumpCfg.DebugLevel == Entities.DebugMode.Simulate) { return; } string comPortName = "COM" + pumpCfg.ComPortNr.ToString(); serialPort = new SerialPort(comPortName, pumpCfg.BaudRate, pumpCfg.Parity, pumpCfg.DataBits, pumpCfg.StopBits); serialPort.Handshake = Handshake.None; serialPort.Open(); responseBuilder = new StringBuilder(40); log.FatalFormat("Successfully initialized device {0}", ToString()); } /// Run this device public void RunDeviceBefore() { if (pumpCfg.DebugLevel == Entities.DebugMode.Simulate) return; responseBuilder.Append(serialPort.ReadExisting()); /// Check if the response is complete if (responseBuilder.Length >= 8) { string response = responseBuilder.ToString(); responseBuilder.Clear(); log.Info(Telegram.LogTelegram("Received ", response)); } } /// /// Prepares data to be sent in RunDeviceAfter() function /// bool SendData(byte[] data) { if (dataToSendReady) return false; dataToSend = data; dataToSendReady = true; log.Info(Telegram.LogTelegram("Sending ", data)); return true; } /// Run this device public void RunDeviceAfter() { if (dataToSendReady) { if (pumpCfg.DebugLevel != Entities.DebugMode.Simulate) { serialPort.Write(dataToSend, 0, dataToSend.Length); } dataToSendReady = false; } } /// Stop this device public void StopDevice() { if (serialPort != null) serialPort.Close(); } /// /// Turn the pmp on using Modbus commad, use the variable frequency /// /// Frequency in percent (-200.0 .. 200.0) public void TurnOn(float powerArg) { log.DebugFormat("{0}.TurnOn({1})", Name, powerArg); this.power = powerArg; //UInt16 controlWord = 0x047C; UInt16 controlWord = (UInt16)(CW.Not_DcBraking | CW.Not_CoastingStop | CW.Not_QuickStop | CW.Not_FreezeFreq | CW.Start_NotRampStop | CW.DataValid); UInt16 serialComReference = (UInt16)(Int16)((float)0x4000 * powerArg / 100.0f + 0.5f); byte[] msg; if (pumpCfg.Protocol == PumpProtocol.Modbus) { msg = new byte[13]; msg[0] = (byte)pumpCfg.BusAddress; 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)(serialComReference & 0xFF); /// Serial Com Reference Lo msg[10] = (byte)(serialComReference >> 8); /// Serial Com Reference Hi Telegram.UpdateTelegramCRC(msg); } else /// if (pumpCfg.Protocol == PumpProtocol.FC) { msg = new byte[8]; msg[0] = 0x02; /// STX msg[1] = 6; /// lenght (6 bytes following this one) msg[2] = (byte)pumpCfg.BusAddress; msg[3] = (byte)(controlWord >> 8); /// Control Word Hi msg[4] = (byte)(controlWord & 0xFF); /// Control Word Lo msg[5] = (byte)(serialComReference >> 8); /// Serial Com Reference Hi msg[6] = (byte)(serialComReference & 0xFF); /// Serial Com Reference Lo Telegram.UpdateTelegramChecksum(msg); } SendData(msg); } /// /// Turn the pmp off using Modbus command /// public void TurnOff() { log.DebugFormat("{0}.TurnOff()", Name); this.power = 0; UInt16 controlWord = (UInt16)(CW.Not_DcBraking | CW.Not_CoastingStop | CW.Not_QuickStop | CW.Not_FreezeFreq | //CW.Start_NotRampStop | CW.DataValid); UInt16 serialComReference = 0; byte[] msg; if (pumpCfg.Protocol == PumpProtocol.Modbus) { msg = new byte[13]; msg[0] = (byte)pumpCfg.BusAddress; 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)(serialComReference & 0xFF); /// Serial Com Reference Lo msg[10] = (byte)(serialComReference >> 8); /// Serial Com Reference Hi Telegram.UpdateTelegramCRC(msg); } else /// if (pumpCfg.Protocol == PumpProtocol.FC) { msg = new byte[8]; msg[0] = 0x02; /// STX msg[1] = 6; /// lenght (6 bytes following this one) msg[2] = (byte)pumpCfg.BusAddress; msg[3] = (byte)(controlWord >> 8); /// Control Word Hi msg[4] = (byte)(controlWord & 0xFF); /// Control Word Lo msg[5] = (byte)(serialComReference >> 8); /// Serial Com Reference Hi msg[6] = (byte)(serialComReference & 0xFF); /// Serial Com Reference Lo Telegram.UpdateTelegramChecksum(msg); } SendData(msg); } /// /// The currently running operation. /// public enum CurrentOp { None, TurnOn, TurnOff, } /// CurrentOp currentOp; float powerForOp; /// /// Turn the pump frequency inverter on. /// public IOperation TurnOnOp(float 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; } } }