tbf/TBF/Rig/Modbus/PumpFM/Grundfoss/Pump.cs

498 lines
16 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

using Common;
using Dirichlet.Numerics;
using log4net;
using SchematicDrawing;
using SharedComponents;
///
/// Copyright (c) 2020 Sensus Slovensko a.s.
///
using System;
using System.Collections.Generic;
using System.Diagnostics;
using TBF.Rig.ControlBoard;
using TBF.Rig.Generic;
namespace TBF.Rig.Modbus.PumpFM.Grundfoss
{
/// <summary>
/// See page 20 of MG10S202 Operational Instructions Modbus RTU
/// </summary>
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),
}
/// <summary>
/// See page 22 of MG10S202 Operational Instructions Modbus RTU
/// </summary>
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; } }
/// <summary>
/// Pump power in [%] in range 0 .. 100.0
/// </summary>
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 += 3.0;
if (power > 100.0) power = 100.0;
TurnOn(power);
}
///
public void DecreaseSetpoint()
{
power -= 3.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() { }
/// <summary>
/// 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.
/// </summary>
public Pump(Generic.IComponentCfg cfg, IList<Generic.IComponent> 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);
}
}*/
if (DebugLevel == DebugMode.Normal || DebugLevel == DebugMode.DetectedOn)
{
if (modbus.ReceivedTelegrams[pumpCfg.ModbusAddress].Count > 0)
{
try
{
byte[] telegram = modbus.ReceivedTelegrams[pumpCfg.ModbusAddress].Dequeue();
string hex = BitConverter.ToString(telegram).Replace("-", " ");
string msg = $"Telegram received from {Name}: {hex}";
log.Warn(msg);
LiveLogDiag.Log1(msg);
}
catch (Exception ex)
{
log.Error($"RunDeviceBefore() error processing telegram: {ex}");
LiveLogDiag.Log1($"RunDeviceBefore() error processing telegram: {ex.Message}");
}
}
}
}
public void RunDeviceAfter() { }
public void StopDevice() { }
public void StopDevice2() { }
/*/// <summary>
/// Turn the pmp on using Modbus commad, use the variable frequency
/// </summary>
/// <param name="powerArg">Frequency in percent (-200.0 .. 200.0)</param>
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));
}
}*/
public void TurnOn(double powerArg)
{
log.DebugFormat("{0}.TurnOn({1})", Name, powerArg);
LiveLogDiag.Log1("--------------------------------------------");
LiveLogDiag.Log1("{0}.TurnOn({1})", Name, powerArg);
LiveLogDiag.Log1($"DebugLevel={DebugLevel}");
// clamp 0100 %
double pct = Math.Max(0, Math.Min(100.0, powerArg));
UInt16 setpoint = (UInt16)Math.Round(pct * 100); // fH,fL = %*100
this.power = pct;
if (DebugLevel == DebugMode.Normal || DebugLevel == DebugMode.DetectedOn)
{
byte[] msg = new byte[17];
msg[0] = (byte)pumpCfg.ModbusAddress; // adr
msg[1] = 0x10; // function = write multiple registers
msg[2] = 0x00; // start address hi
msg[3] = 0x64; // start address lo = 100
msg[4] = 0x00; // register count hi
msg[5] = 0x05; // register count lo = 5
msg[6] = 0x0A; // byte count = 10
// ----- registers -----
// reg100 = command = 0x0007 (RUN)
msg[7] = 0x00;
msg[8] = 0x07;
// reg101 = 0x0001
msg[9] = 0x00;
msg[10] = 0x01;
// reg102 = 0x0000
msg[11] = 0x00;
msg[12] = 0x00;
// reg103 = setpoint = fH,fL
msg[13] = (byte)(setpoint >> 8);
msg[14] = (byte)(setpoint & 0xFF);
// reg104 = 0x0001 (RUN)
msg[15] = 0x00;
msg[16] = 0x01;
//Telegram.UpdateTelegramCRC(msg);
modbus.SendMessage(msg, Name);
modbus.SendMessage(msg, Name);
// --- DIAGNOSTIKA ---
LiveLogDiag.Log1($"GF TurnOn Setpoint={pct}");
LiveLogDiag.Log1($"GF TurnOn raw reference={setpoint}");
LiveLogDiag.Log1("GF TurnOn msg = " + BitConverter.ToString(msg));
}
}
/*/// <summary>
/// Turn the pmp off using Modbus command
/// </summary>
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));
}
}*/
/// <summary>
/// Turn the pump off using Modbus command (Grundfos version)
/// </summary>
public void TurnOff()
{
log.DebugFormat("{0}.TurnOff()", Name);
LiveLogDiag.Log1("--------------------------------------------");
LiveLogDiag.Log1("{0}.TurnOff()", Name);
LiveLogDiag.Log1($"DebugLevel={DebugLevel}");
this.power = 0;
UInt16 setpoint = 0; // desired = 0%
if (DebugLevel == DebugMode.Normal || DebugLevel == DebugMode.DetectedOn)
{
byte[] msg = new byte[17];
msg[0] = (byte)pumpCfg.ModbusAddress; // adr
msg[1] = 0x10; // function
msg[2] = 0x00;
msg[3] = 0x64; // reg100
msg[4] = 0x00;
msg[5] = 0x05; // 5 registers
msg[6] = 0x0A; // 10 bytes
// reg100 = 0x0005 (STOP)
msg[7] = 0x00;
msg[8] = 0x05;
// reg101 = 0x0001
msg[9] = 0x00;
msg[10] = 0x01;
// reg102 = 0x0000
msg[11] = 0x00;
msg[12] = 0x00;
// reg103 = setpoint = 0
msg[13] = 0x00;
msg[14] = 0x00;
// reg104 = 0x0000 (STOP)
msg[15] = 0x00;
msg[16] = 0x00;
//Telegram.UpdateTelegramCRC(msg);
modbus.SendMessage(msg, Name);
modbus.SendMessage(msg, Name);
// --- DIAGNOSTIKA ---
LiveLogDiag.Log1("GF TurnOff msg = " + BitConverter.ToString(msg));
}
}
/// <summary>
/// The currently running operation.
/// </summary>
public enum CurrentOp
{
None,
TurnOn,
TurnOff,
}
///
CurrentOp currentOp;
double powerForOp;
/// <summary>
/// Turn the pump frequency inverter on.
/// </summary>
public IOperation TurnOnOp(double powerArg)
{
if (currentOp != CurrentOp.None) throw new Exception("Sequence error");
currentOp = CurrentOp.TurnOn;
powerForOp = powerArg;
return this;
}
/// <summary>
/// Turn the pump frequency inverter on.
/// </summary>
public IOperation TurnOffOp()
{
if (currentOp != CurrentOp.None) throw new Exception("Sequence error");
currentOp = CurrentOp.TurnOff;
return this;
}
/// <summary>Start the operation</summary>
public void Start()
{
switch (currentOp)
{
case CurrentOp.TurnOn:
TurnOn(powerForOp);
return;
case CurrentOp.TurnOff:
default:
TurnOff();
return;
}
}
/// <summary>Run the operation</summary>
public Event Run()
{
return Event.TurnPumpOnOffDone;
}
/// <summary>Stop the operation</summary>
public void Stop()
{
currentOp = CurrentOp.None;
}
}
/// <summary>
/// Diagnostic logging
/// Activated by compilation condition: DIAG_ENDURANCE_LOG
/// </summary>
static class LiveLogDiag
{
[Conditional("DIAG_LOG_PUMP")]
public static void Log1(string format, params object[] args)
{
LiveLogCache.Instance.AddLog(string.Format(format, args));
}
}
}