427 lines
18 KiB
C#
427 lines
18 KiB
C#
///
|
|
/// Copyright (c) 2022 Sensus Slovensko a.s.
|
|
///
|
|
using System;
|
|
using System.Collections.Generic;
|
|
using System.Drawing;
|
|
using log4net;
|
|
using Common;
|
|
using Config.Entities;
|
|
using TBF.Boxes;
|
|
using TBF.Rig.GenericDevices;
|
|
|
|
namespace TBF.Rig.Modbus.TempControl.Novus
|
|
{
|
|
public class TControl : ComponentBase, Generic.IDevice, ITempControl, ISequenceCondition
|
|
{
|
|
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 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));
|
|
}
|
|
|
|
|
|
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<Generic.IComponent> components)
|
|
: base(cfg)
|
|
{
|
|
novusCfg = cfg as TControlCfg;
|
|
|
|
CreateConditions(true);
|
|
}
|
|
|
|
/// <summary>Initialize this device</summary>
|
|
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();
|
|
|
|
log.FatalFormat("{0} initialized: {1}", Name, this);
|
|
}
|
|
}
|
|
|
|
|
|
/// <summary>Run this device</summary>
|
|
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;
|
|
string setpointStr = string.Format(novusCfg.SetpFormat, Units.ConvertTo(novusCfg.SetpUnit, temperatureSetpoint));
|
|
|
|
double temperatureCorF = (double)values[ProcessValueAddress] * 0.1;
|
|
actualTemperature = isFahrenheit ? Units.ConvertFrom(Unit.F, temperatureCorF) : temperatureCorF;
|
|
string temperatureStr = string.Format(novusCfg.MsrdFormat, Units.ConvertTo(novusCfg.MsrdUnit, actualTemperature));
|
|
|
|
novusDataValid = true;
|
|
|
|
StateMachine.ControlBoard.SetReservoirTemp(2 * (novusCfg.ReservoirNr - 1), temperatureStr, Convert.ToSingle(actualTemperature));
|
|
StateMachine.ControlBoard.SetReservoirTemp(2 * (novusCfg.ReservoirNr - 1) + 1, setpointStr, Convert.ToSingle(temperatureSetpoint));
|
|
|
|
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;
|
|
string setpointStr = string.Format(novusCfg.SetpFormat, Units.ConvertTo(novusCfg.SetpUnit, temperatureSetpoint));
|
|
|
|
double temperatureCorF = (double)values[ProcessValueAddress] * 0.1;
|
|
actualTemperature = isFahrenheit ? Units.ConvertFrom(Unit.F, temperatureCorF) : temperatureCorF;
|
|
string temperatureStr = string.Format(novusCfg.MsrdFormat, Units.ConvertTo(novusCfg.MsrdUnit, actualTemperature));
|
|
|
|
novusDataValid = true;
|
|
|
|
StateMachine.ControlBoard.SetReservoirTemp(2 * (novusCfg.ReservoirNr - 1), temperatureStr, Convert.ToSingle(actualTemperature));
|
|
StateMachine.ControlBoard.SetReservoirTemp(2 * (novusCfg.ReservoirNr - 1) + 1, setpointStr, Convert.ToSingle(temperatureSetpoint));
|
|
|
|
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;
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>Run this device</summary>
|
|
public void RunDeviceAfter()
|
|
{
|
|
if (DebugLevel != DebugMode.Simulate && DebugLevel != DebugMode.FailureDuringOperation
|
|
&& (StateMachine.Time % 3 == novusCfg.ReservoirNr - 1))
|
|
{
|
|
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() { }
|
|
|
|
|
|
/// <summary>
|
|
/// Set temperature controller setpoint to specified value
|
|
/// </summary>
|
|
/// <param name="temperatureC">Temperature in °C</param>
|
|
/// <returns>true = OK, false = any error</returns>
|
|
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);
|
|
}
|
|
|
|
|
|
///
|
|
/// Operations, etc.
|
|
///
|
|
public double ReadTemperature()
|
|
{
|
|
return actualTemperature;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Events: SetOpututsDone, Error
|
|
/// </summary>
|
|
/// <returns>SetOutputsOp instance reference casted to IOperaton</returns>
|
|
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<string> sequenceConditionNames;
|
|
IList<IOperation> 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<string>();
|
|
sequenceConditions = new List<IOperation>();
|
|
}
|
|
|
|
void AddCondition(string conditionName, bool createOperation, IOperation conditionOperation)
|
|
{
|
|
sequenceConditionNames.Add(conditionName);
|
|
if (createOperation) sequenceConditions.Add(conditionOperation);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Create a list of conditions
|
|
/// </summary>
|
|
/// <param name="createOps">true = create names and condition operations, false = create names only</param>
|
|
void CreateConditions(bool createOps)
|
|
{
|
|
ClearConditions();
|
|
AddCondition(string.Format("Set {0} setpoint A", Name), createOps, new SetTemperatureSetpointOp(this, SetpSource.ProcParamA, 0));
|
|
AddCondition(string.Format("Set {0} setpoint B", Name), createOps, new SetTemperatureSetpointOp(this, SetpSource.ProcParamB, 0));
|
|
AddCondition(string.Format("Set {0} setpoint C", Name), createOps, new SetTemperatureSetpointOp(this, SetpSource.ProcParamC, 0));
|
|
AddCondition(string.Format("Set {0} setpoint D", Name), createOps, new SetTemperatureSetpointOp(this, SetpSource.ProcParamD, 0));
|
|
AddCondition(string.Format("Set {0} setpoint E", Name), createOps, new SetTemperatureSetpointOp(this, SetpSource.ProcParamE, 0));
|
|
}
|
|
}
|
|
}
|