From ebcb560ef4eb3b71522d07573785735a82715e7a Mon Sep 17 00:00:00 2001 From: mharb Date: Sun, 29 Jun 2025 18:55:22 -0400 Subject: [PATCH] Implement and test various functionalities of the F4/S --- watlow-f4sd/ModbusWatlowRamp/Program.cs | 292 +++++++++++++++++------- 1 file changed, 205 insertions(+), 87 deletions(-) diff --git a/watlow-f4sd/ModbusWatlowRamp/Program.cs b/watlow-f4sd/ModbusWatlowRamp/Program.cs index 7548cc5..599f769 100644 --- a/watlow-f4sd/ModbusWatlowRamp/Program.cs +++ b/watlow-f4sd/ModbusWatlowRamp/Program.cs @@ -1,88 +1,206 @@ -using Modbus.Device; // NModbus namespace -using System.Net.Sockets; - -internal class Program -{ - // Configuration - private const string hostname = "127.0.0.1"; // Replace with your Watlow F4 IP - - private const int port = 502; - private const byte slaveId = 0; - - // Register 100 should be the first read-only input on the F4 - // Value 99 or 100 should work depending on zero indexing - private const ushort loadRegister = 99; - - private static void Main(string[] args) - { - TcpClient tcpClient = null; - ModbusIpMaster master = null; - - try - { - // Initialize connection - tcpClient = new TcpClient(hostname, port); - master = ModbusIpMaster.CreateIp(tcpClient); - - // Monitor continuously - while (true) - { - // Check for Escape key press - if (Console.KeyAvailable) - { - ConsoleKeyInfo key = Console.ReadKey(true); - if (key.Key == ConsoleKey.Escape) - { - Console.WriteLine("Escape pressed. Exiting..."); - break; - } - } - - try - { - // Read load value (holding register) - ushort[] registers = master.ReadHoldingRegisters(slaveId, loadRegister, 1); - int load = registers[0]; - - // Display load info - DisplayLoadInfo(load); - - // Pause before next read - Thread.Sleep(1000); - } - catch (Exception ex) - { - Console.WriteLine($"Error: {ex.Message}"); - break; - } - } - } - catch (Exception ex) - { - Console.WriteLine($"Initialization error: {ex.Message}"); - } - finally - { - // Clean up - master?.Dispose(); - tcpClient?.Close(); - } - } - - private static void DisplayLoadInfo(int load) - { - Console.Clear(); // Clears the console - - // Assume load is 0-65535 for 0.00-100.00% (16-bit range) - int percent = (int)load / 65535 * 100; - int barLength = (int)(percent / 2); // Scale to 50 chars for console - - Console.WriteLine($"Load Value: {load}"); - Console.WriteLine($"Load Percentage: {percent:F2}%"); - Console.Write("Bar Graph: ["); - Console.Write(new string('=', barLength)); - Console.Write(new string(' ', 50 - barLength)); - Console.WriteLine("]"); - Console.WriteLine("\nHold ESC to exit."); - } +using Modbus.Device; // NModbus namespace +using System.IO.Ports; +using static System.Console; + +internal class Program +{ + // Registers + private const ushort READINPUT1 = 100; + private const ushort INPUT1ERROR = 101; + private const ushort ALARM1STATUS = 102; + private const ushort ALARM2STATUS = 106; + private const ushort ANALOGINPUT1DECIMAL = 606; + private const ushort POWEROUTPUT1A = 103; + private const ushort SETPOINT1 = 300; + private const ushort CURRENTDAY = 1920; + private const ushort CURRENTMONTH = 1919; + private const ushort CURRENTYEAR = 1921; + private const ushort CURRENTHOUR = 1916; + private const ushort CURRENTMINUTE = 1917; + private const ushort CURRENTSECOND = 1918; + private const ushort OUTPUT1AFUNCTION = 700; + private const ushort OUTPUT1ACYCLETIMETYPE = 509; + private const ushort OUTPUT1ACYCLETIME = 506; + private const ushort DIGITALINPUT1FUNCTION = 1060; + private const ushort DIGITALINPUT1CONDITION = 1061; + private const ushort DIGITALINPUT1STATUS = 201; + private const ushort POWEROUTACTION = 1206; + private const ushort POWEROUTTIME = 1213; + private const ushort ANALOGINPUTUNITS = 608; + private const ushort CONTROLOUTPUTTYPE = 701; + private const ushort ANALOGINPUT1SENSORTYPE = 601; + private const ushort ANALOGINPUT1SENSOR = 600; + private const ushort ANALOGINPUT1SETPOINTHIGHLIMIT = 603; + private const ushort ANALOGINPUT1SETPOINTLOWLIMIT = 602; + private const ushort TEMPSCALEDISPLAY = 1923; + private const ushort TEMPSCALE = 901; + private const ushort ANALOGOUTPUT1ATYPE = 701; + private const ushort PROPORTIONALBAND = 500; + + // Configuration + private const byte slaveID = 1; + + private static void Main(string[] args) + { + ushort[] registers; + + // Initialize the F4 + SerialPort serialPort = (SerialPort)InitializeModbusSerialPort(); + ModbusSerialMaster master = (ModbusSerialMaster)InitializeModbusMaster(serialPort); + + // Then, run it + try + { + // Monitor continuously + while (true) + { + // Check for escape key press + if (KeyAvailable) + { + ConsoleKeyInfo key = ReadKey(true); + if (key.Key == ConsoleKey.Escape) + { + WriteLine(" pressed. Exiting..."); + break; + } + } + + // Display current process values + try + { + registers = master.ReadInputRegisters(slaveID, INPUT1ERROR, 1); + if (registers[0] != 0) + { + WriteLine("Failure in the temperature input channel; program ends here...\n"); + return; + }; + registers = master.ReadInputRegisters(slaveID, READINPUT1, 1); + Write("temperature is {0:F1}; ", registers[0] / 10.0); + registers = master.ReadInputRegisters(slaveID, POWEROUTPUT1A, 1); + Write("power output is {0:F2}%\n", registers[0] / 100.0); + // Pause before next read + Thread.Sleep(750); + } + catch (Exception ex) + { + WriteLine($"Error: {ex.Message}"); + break; + } + } + } + catch (Exception ex) + { + WriteLine($"Initialization error: {ex.Message}"); + } + finally + { + // Shut off the output + master?.WriteSingleRegister(slaveID, SETPOINT1, 0); + + // Clean up + master?.Dispose(); + serialPort?.Close(); + } + + static object InitializeModbusSerialPort() + { + SerialPort serialPort; + + serialPort = new SerialPort + { + PortName = "COM1", + BaudRate = 19200, + DataBits = 8, + Parity = Parity.None, + StopBits = StopBits.One, + ReadTimeout = 1000 + }; + serialPort.Open(); + return serialPort; + } + + static object InitializeModbusMaster(SerialPort serialPort) + { + ushort[] registers; + ushort setPoint = 1000; + ModbusSerialMaster master; + DateTime dateTime; + + master = ModbusSerialMaster.CreateRtu(serialPort); + + // Set sensor type + WriteLine("Set sensor type to 100 Ω DIN platinum RTD"); + master.WriteSingleRegister(slaveID, ANALOGINPUT1SENSOR, 1); + master.WriteSingleRegister(slaveID, ANALOGINPUT1SENSORTYPE, 11); + + // Set to one decimal place (this cannot be the first initialization instruction) + WriteLine("Set to one decimal place on display"); + master.WriteSingleRegister(slaveID, ANALOGINPUT1DECIMAL, 1); + + // Display current setpoint high and low limits + registers = master.ReadInputRegisters(slaveID, ANALOGINPUT1SETPOINTLOWLIMIT, 1); + WriteLine("Setpoint low limit is {0}", registers[0]); + registers = master.ReadInputRegisters(slaveID, ANALOGINPUT1SETPOINTHIGHLIMIT, 1); + WriteLine("Setpoint high limit is {0}", registers[0]); + + // Set to proportional mode + WriteLine("Make proportional band 5°F"); + master.WriteSingleRegister(slaveID, PROPORTIONALBAND, 5); + + // Set output function to heating + WriteLine("Output function is heating"); + master.WriteSingleRegister(slaveID, OUTPUT1AFUNCTION, 1); + + // Set analog output1 to 4-20 ma + WriteLine("Set the analog output to 4-20 MA"); + master.WriteSingleRegister(slaveID, ANALOGOUTPUT1ATYPE, 0); + + // Set analog input parameter to temperature + master.WriteSingleRegister(slaveID, ANALOGINPUTUNITS, 0); + + // Set temperature SCALE to ON + master.WriteSingleRegister(slaveID, TEMPSCALEDISPLAY, 1); + + // Set temperature scale type to Fahrenheit + master.WriteSingleRegister(slaveID, TEMPSCALE, 0); + + // Set power failure response + master.WriteSingleRegister(slaveID, POWEROUTACTION, 2); + + // Set output1 cycle time type to variable burst + master.WriteSingleRegister(slaveID, OUTPUT1ACYCLETIMETYPE, 0); + + // Read current output1 cycle time + registers = master.ReadInputRegisters(slaveID, OUTPUT1ACYCLETIME, 1); + WriteLine("Output cycle time is {0}", registers[0]); + + // Set output1 cycle time to new value + master.WriteSingleRegister(slaveID, OUTPUT1ACYCLETIME, 500); + + // Check new value + registers = master.ReadInputRegisters(slaveID, OUTPUT1ACYCLETIME, 1); + WriteLine("Output cycle time is now {0}", registers[0]); + + // Write setpoint to F4 + WriteLine("Setting the process variable setpoint"); + master.WriteSingleRegister(slaveID, SETPOINT1, setPoint); + + // Verify setpoint value + WriteLine("Verifying the setpoint value"); + registers = master.ReadInputRegisters(slaveID, SETPOINT1, 1); + WriteLine("Setpoint is {0:F1}", registers[0] / 10.0); + + // Set F4 clock to current date and time + dateTime = DateTime.Now; + master.WriteSingleRegister(slaveID, CURRENTDAY, (ushort)dateTime.Day); + master.WriteSingleRegister(slaveID, CURRENTMONTH, (ushort)dateTime.Month); + master.WriteSingleRegister(slaveID, CURRENTYEAR, (ushort)dateTime.Year); + master.WriteSingleRegister(slaveID, CURRENTHOUR, (ushort)dateTime.Hour); + master.WriteSingleRegister(slaveID, CURRENTMINUTE, (ushort)dateTime.Minute); + master.WriteSingleRegister(slaveID, CURRENTSECOND, (ushort)dateTime.Second); + WriteLine("Set F4 clock to {0} {1}", dateTime.ToLongTimeString(), dateTime.ToLongDateString()); + + return master; + } + } } \ No newline at end of file