diff --git a/App.config b/App.config
new file mode 100644
index 0000000..816b022
--- /dev/null
+++ b/App.config
@@ -0,0 +1,22 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/App.xaml b/App.xaml
new file mode 100644
index 0000000..40d34dc
--- /dev/null
+++ b/App.xaml
@@ -0,0 +1,9 @@
+
+
+
+
+
diff --git a/App.xaml.cs b/App.xaml.cs
new file mode 100644
index 0000000..3413eb6
--- /dev/null
+++ b/App.xaml.cs
@@ -0,0 +1,17 @@
+using System;
+using System.Collections.Generic;
+using System.Configuration;
+using System.Data;
+using System.Linq;
+using System.Threading.Tasks;
+using System.Windows;
+
+namespace FAtoPA.Net
+{
+ ///
+ /// Interaction logic for App.xaml
+ ///
+ public partial class App : Application
+ {
+ }
+}
diff --git a/Config.cs b/Config.cs
new file mode 100644
index 0000000..594adf5
--- /dev/null
+++ b/Config.cs
@@ -0,0 +1,112 @@
+using System;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.IO;
+using System.Linq;
+using System.Text;
+using System.Text.Json;
+using System.Threading.Tasks;
+
+namespace FAtoPA
+{
+ class Config
+ {
+ // FSM settings
+ public byte FSM_NetGroup { get; set; } = 2;
+ public byte FSM_NetNode { get; set; } = 1;
+ public byte FSM_PNA { get; set; } = 2;
+ public String FSM_LocalIP { get; set; } = "192.168.10.23";
+ public bool FSM_UseMulticast { get; set; } = false;
+ public String FSM_MulticastIP { get; set; } = "239.192.0.1";
+ public UInt16 FSM_MulticastPort { get; set; } = 25000;
+
+ public UInt16 Modbus_ListenPort { get; set; } = 502;
+ public byte Modbus_DeviceID { get; set; } = 1;
+ public UInt16 Modbus_MaxRegister { get; set; } = 2000;
+
+ public String VX_TargetIP { get; set; } = "192.168.14.1";
+ public UInt16 VX_TargetPort { get; set; } = 5000;
+ private String _configpath = "";
+
+ public Config() {
+ _configpath = GetConfigPath();
+ // kalau belum ada, create default
+ if (!File.Exists(_configpath)) Save();
+ }
+
+ public Boolean FileIsExist() { return File.Exists(_configpath); }
+
+ public bool Save()
+ {
+
+ Debug.WriteLine("Will Save Config to [" + _configpath + "]");
+
+ try
+ {
+ String jsonstring = JsonSerializer.Serialize(this);
+ File.WriteAllText(_configpath, jsonstring);
+ Debug.WriteLine("Config saved to [" + _configpath + "]");
+ return true;
+ }
+ catch (Exception ex)
+ {
+ Debug.WriteLine("Config Save to ["+ _configpath + "] Error: " + ex.Message);
+ }
+ return false;
+ }
+
+ public bool Load()
+ {
+
+ if (File.Exists(_configpath))
+ {
+ try
+ {
+ string jsonstring = File.ReadAllText(_configpath);
+ Config _loaded = JsonSerializer.Deserialize(jsonstring);
+ if (_loaded != null)
+ {
+ this.FSM_NetGroup = _loaded.FSM_NetGroup;
+ this.FSM_NetNode = _loaded.FSM_NetNode;
+ this.FSM_PNA = _loaded.FSM_PNA;
+ this.FSM_LocalIP = _loaded.FSM_LocalIP;
+ this.FSM_UseMulticast = _loaded.FSM_UseMulticast;
+ this.FSM_MulticastIP = _loaded.FSM_MulticastIP;
+ this.FSM_MulticastPort = _loaded.FSM_MulticastPort;
+ this.Modbus_ListenPort = _loaded.Modbus_ListenPort;
+ this.Modbus_DeviceID = _loaded.Modbus_DeviceID;
+ this.Modbus_MaxRegister = _loaded.Modbus_MaxRegister;
+ this.VX_TargetIP = _loaded.VX_TargetIP;
+ this.VX_TargetPort = _loaded.VX_TargetPort;
+
+ Debug.WriteLine("Config Loaded from [" + _configpath + "]");
+ return true;
+ } else Debug.WriteLine("Config Load Error: File ["+ _configpath + "] is not valid Config.");
+ } catch(Exception ex)
+ {
+ Debug.WriteLine("Config Load from ["+ _configpath + "] Error: " + ex.Message);
+ }
+ }
+ else Debug.WriteLine("Config Load Error: File ["+ _configpath + "] not found.");
+ return false;
+ }
+
+ private static String GetConfigPath()
+ {
+ String _xx = System.IO.Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "FAtoPA");
+ Debug.WriteLine("Config Path: " + _xx);
+ if (!Directory.Exists(_xx)) {
+ try
+ {
+ Directory.CreateDirectory(_xx);
+ }
+ catch (Exception ex)
+ {
+ Debug.WriteLine("Config Path Error: " + ex.Message);
+ }
+ }
+
+ return System.IO.Path.Combine(_xx, "config.json");
+ }
+ }
+}
diff --git a/Database.cs b/Database.cs
new file mode 100644
index 0000000..7acedb0
--- /dev/null
+++ b/Database.cs
@@ -0,0 +1,475 @@
+using Microsoft.Data.Sqlite;
+using System;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace FAtoPA
+{
+ class Database
+ {
+ String connectionString = "Data Source=database.db";
+
+ ///
+ /// Create Database Object
+ ///
+ public Database()
+ {
+
+ CreateFSMTable();
+ CreateModbusTable();
+ CreateVXTable();
+ }
+
+ private bool CreateFSMTable()
+ {
+ //Debug.WriteLine("About to execute CreateFSMTable");
+ using (var connection = new SqliteConnection(connectionString))
+ {
+ try
+ {
+ connection.Open();
+ var createtablecmd = connection.CreateCommand();
+ createtablecmd.CommandText = "CREATE TABLE IF NOT EXISTS FsmData (SIID TEXT PRIMARY KEY, Enable INTEGER, Description TEXT)";
+ createtablecmd.ExecuteNonQuery();
+ Debug.WriteLine("CreateFSMTable success");
+ return true;
+ }
+ catch (Exception ex)
+ {
+ Debug.WriteLine("Error CreateFSMTable, Exception : " + ex.Message);
+ return false;
+ }
+
+ }
+ }
+
+ public bool ClearFSMTable() {
+ using (var connection = new SqliteConnection(connectionString))
+ {
+ connection.Open();
+ var deleteCmd = connection.CreateCommand();
+ deleteCmd.CommandText = "DELETE FROM FsmData";
+ try
+ {
+ int result = deleteCmd.ExecuteNonQuery();
+ return (result > 0);
+ }
+ catch (SqliteException e)
+ {
+ Debug.WriteLine("Error deleting FSMData: " + e.Message);
+ }
+ }
+ return false;
+ }
+
+ public List GetFSMDatas()
+ {
+ List fsmDatas = new List();
+ using (var connection = new SqliteConnection(connectionString))
+ {
+ connection.Open();
+
+ var selectCmd = connection.CreateCommand();
+ selectCmd.CommandText = "SELECT * FROM FsmData";
+ using (var reader = selectCmd.ExecuteReader())
+ {
+ while (reader.Read())
+ {
+ FSMData fsmData = new FSMData(reader.GetString(0), reader.GetBoolean(1), reader.GetString(2));
+ fsmDatas.Add(fsmData);
+ }
+ }
+ }
+ return fsmDatas;
+ }
+
+ public bool AddFSMData(params FSMData[] data)
+ {
+ using(var connection = new SqliteConnection(connectionString))
+ {
+ connection.Open();
+ using (var transaction = connection.BeginTransaction())
+ {
+ foreach (var item in data)
+ {
+ var insertCmd = connection.CreateCommand();
+ insertCmd.CommandText = "INSERT INTO FsmData (SIID, Enable, Description) VALUES (@SIID, @Enable, @Description)";
+ insertCmd.Parameters.AddWithValue("@SIID", item.SIID);
+ insertCmd.Parameters.AddWithValue("@Enable", item.Enable);
+ insertCmd.Parameters.AddWithValue("@Description", item.Description);
+ try
+ {
+ int result = insertCmd.ExecuteNonQuery();
+ if (result <= 0)
+ {
+ transaction.Rollback();
+ return false;
+ }
+ }
+ catch (SqliteException e)
+ {
+ Debug.WriteLine("Error inserting FSMData: " + e.Message);
+ transaction.Rollback();
+ return false;
+ }
+ }
+ transaction.Commit();
+ }
+ return true;
+ }
+ }
+
+ public bool RemoveFSMDatabySIID(String SIID)
+ {
+ using (var conn = new SqliteConnection(connectionString))
+ {
+ conn.Open();
+ var deleteCmd = conn.CreateCommand();
+ deleteCmd.CommandText = "DELETE FROM FsmData WHERE SIID = @SIID";
+ deleteCmd.Parameters.AddWithValue("@SIID", SIID);
+ try
+ {
+ int result = deleteCmd.ExecuteNonQuery();
+ return (result > 0);
+ }
+ catch (SqliteException e)
+ {
+ Debug.WriteLine("Error deleting FSMData: " + e.Message);
+ return false;
+ }
+ }
+ }
+
+ private bool CreateModbusTable()
+ {
+ //Debug.WriteLine("About to execute CreateModbusTable");
+ using (var connection = new SqliteConnection(connectionString))
+ {
+ try
+ {
+ connection.Open();
+ var modbuscmd = connection.CreateCommand();
+ modbuscmd.CommandText = "CREATE TABLE IF NOT EXISTS ModbusData (SIID TEXT PRIMARY KEY, Register INTEGER, Description TEXT)";
+ modbuscmd.ExecuteNonQuery();
+ Debug.WriteLine("CreateModbusTable success");
+ return true;
+ }
+ catch (Exception ex)
+ {
+ Debug.WriteLine("Error CreateModbusTable, Exception : " + ex.Message);
+ return false;
+ }
+ }
+ }
+
+ public bool ClearModbusTable()
+ {
+ using (var connection = new SqliteConnection(connectionString))
+ {
+ connection.Open();
+ var deleteCmd = connection.CreateCommand();
+ deleteCmd.CommandText = "DELETE FROM ModbusData";
+ try
+ {
+ int result = deleteCmd.ExecuteNonQuery();
+ return (result > 0);
+ }
+ catch (SqliteException e)
+ {
+ Debug.WriteLine("Error deleting ModbusData: " + e.Message);
+ }
+ }
+ return false;
+ }
+
+ public Boolean AddModbusData(params ModbusData[] data)
+ {
+ using (var connection = new SqliteConnection(connectionString))
+ {
+ connection.Open();
+ using (var transaction = connection.BeginTransaction())
+ {
+ foreach (var item in data)
+ {
+ var insertCmd = connection.CreateCommand();
+ insertCmd.CommandText = "INSERT INTO ModbusData (SIID, Register, Description) VALUES (@SIID, @Register, @Description)";
+ insertCmd.Parameters.AddWithValue("@SIID", item.SIID);
+ insertCmd.Parameters.AddWithValue("@Register", item.Register);
+ insertCmd.Parameters.AddWithValue("@Description", item.Description);
+ try
+ {
+ int result = insertCmd.ExecuteNonQuery();
+ if (result <= 0)
+ {
+ transaction.Rollback();
+ return false;
+ }
+ }
+ catch (SqliteException e)
+ {
+ Debug.WriteLine("Error inserting ModbusData: " + e.Message);
+ transaction.Rollback();
+ return false;
+ }
+ }
+ transaction.Commit();
+ }
+ return true;
+ }
+ }
+
+ public List GetModbusDatas()
+ {
+ List modbusDatas = new List();
+ using (var connection = new SqliteConnection(connectionString))
+ {
+ connection.Open();
+
+ var selectCmd = connection.CreateCommand();
+ selectCmd.CommandText = "SELECT * FROM ModbusData";
+ using (var reader = selectCmd.ExecuteReader())
+ {
+ while (reader.Read())
+ {
+ ModbusData modbusdata = new ModbusData(reader.GetString(0), reader.GetFieldValue(1), reader.GetString(2));
+ modbusDatas.Add(modbusdata);
+ }
+ }
+ }
+ return modbusDatas;
+ }
+
+ public bool RemoveModbusDatabySIID(String SIID)
+ {
+ using (var conn = new SqliteConnection(connectionString))
+ {
+ conn.Open();
+ var deleteCmd = conn.CreateCommand();
+ deleteCmd.CommandText = "DELETE FROM ModbusData WHERE SIID = @SIID";
+ deleteCmd.Parameters.AddWithValue("@SIID", SIID);
+ try
+ {
+ int result = deleteCmd.ExecuteNonQuery();
+ return (result > 0);
+ }
+ catch (SqliteException e)
+ {
+ Debug.WriteLine("Error deleting ModbusData: " + e.Message);
+ return false;
+ }
+ }
+ }
+
+ private bool CreateVXTable()
+ {
+ //Debug.WriteLine("About to execute CreateVXTable");
+ using (var connection = new SqliteConnection(connectionString))
+ {
+ try
+ {
+ connection.Open();
+ var vxlancmd = connection.CreateCommand();
+ vxlancmd.CommandText = "CREATE TABLE IF NOT EXISTS VxTable (SIID TEXT PRIMARY KEY, FrameID INTEGER, CIN INTEGER, Description TEXT)";
+ vxlancmd.ExecuteNonQuery();
+ Debug.WriteLine("CreateVXTable success");
+ return true;
+ }
+ catch (Exception ex)
+ {
+ Debug.WriteLine("Error CreateVXTable, Exception : " + ex.Message);
+ return false;
+ }
+ }
+ }
+
+ public bool ClearVXTable()
+ {
+ using (var connection = new SqliteConnection(connectionString))
+ {
+ connection.Open();
+ var deleteCmd = connection.CreateCommand();
+ deleteCmd.CommandText = "DELETE FROM VxTable";
+ try
+ {
+ int result = deleteCmd.ExecuteNonQuery();
+ return (result > 0);
+ }
+ catch (SqliteException e)
+ {
+ Debug.WriteLine("Error deleting VXData: " + e.Message);
+ }
+ }
+ return false;
+ }
+
+ public List GetVXDatas()
+ {
+ List vxDatas = new List();
+ using (var connection = new SqliteConnection(connectionString))
+ {
+ connection.Open();
+
+ var selectCmd = connection.CreateCommand();
+ selectCmd.CommandText = "SELECT * FROM VxTable";
+ using (var reader = selectCmd.ExecuteReader())
+ {
+ while (reader.Read())
+ {
+ VXData vxdata = new VXData(reader.GetString(0), reader.GetByte(1), reader.GetByte(2));
+ vxDatas.Add(vxdata);
+ }
+ }
+ }
+ return vxDatas;
+ }
+
+ public bool RemoveVXDatabySIID(String SIID)
+ {
+ using (var conn = new SqliteConnection(connectionString))
+ {
+ conn.Open();
+ var deleteCmd = conn.CreateCommand();
+ deleteCmd.CommandText = "DELETE FROM VxTable WHERE SIID = @SIID";
+ deleteCmd.Parameters.AddWithValue("@SIID", SIID);
+ try
+ {
+ int result = deleteCmd.ExecuteNonQuery();
+ return (result > 0);
+ }
+ catch (SqliteException e)
+ {
+ Debug.WriteLine("Error deleting VXData: " + e.Message);
+ return false;
+ }
+ }
+ }
+
+ public bool AddVXData(params VXData[] data) {
+ using(var connection = new SqliteConnection(connectionString))
+ {
+ connection.Open();
+ using (var transaction = connection.BeginTransaction())
+ {
+ foreach (var item in data)
+ {
+ var insertCmd = connection.CreateCommand();
+ insertCmd.CommandText = "INSERT INTO VxTable (SIID, FrameID, CIN, Description) VALUES (@SIID, @FrameID, @CIN, @Description)";
+ insertCmd.Parameters.AddWithValue("@SIID", item.SIID);
+ insertCmd.Parameters.AddWithValue("@FrameID", item.FrameID);
+ insertCmd.Parameters.AddWithValue("@CIN", item.CIN);
+ insertCmd.Parameters.AddWithValue("@Description", item.Description);
+ try
+ {
+ int result = insertCmd.ExecuteNonQuery();
+ if (result <= 0)
+ {
+ transaction.Rollback();
+ return false;
+ }
+ }
+ catch (SqliteException e)
+ {
+ Debug.WriteLine("Error inserting VXData: " + e.Message);
+ transaction.Rollback();
+ return false;
+ }
+ }
+ transaction.Commit();
+ }
+ return true;
+ }
+ }
+
+ }
+
+ class FSMData
+ {
+ public String SIID { get; set; }
+ public Boolean Enable { get; set; }
+ public String Description { get; set; }
+ public String Value { get; set; }
+ public String LastUpdate { get; set; }
+ public FSMData(String siid, Boolean enable, String description)
+ {
+ this.SIID = siid;
+ this.Enable = enable;
+ this.Description = description;
+ this.Value = "";
+ this.LastUpdate = "";
+ }
+ public FSMData(String siid)
+ {
+ this.SIID = siid;
+ this.Enable = true;
+ this.Description = "";
+ this.Value = "";
+ this.LastUpdate = "";
+ }
+ public FSMData(String siid, Boolean enable)
+ {
+ this.SIID = siid;
+ this.Enable = enable;
+ this.Description = "";
+ this.LastUpdate = "";
+ this.Value = "";
+
+ }
+ }
+
+ class ModbusData
+ {
+ public String SIID { get; set; }
+ public UInt16 Register { get; set; }
+ public String Description { get; set; }
+
+ public String Value { get; set; }
+ public String LastUpdate { get; set; }
+ public ModbusData(String siid, UInt16 register, String description)
+ {
+ this.SIID = siid;
+ this.Register = register;
+ this.Description = description;
+ this.Value = "";
+ this.LastUpdate = "";
+ }
+ public ModbusData(String siid, UInt16 register)
+ {
+ this.SIID = siid;
+ this.Register = register;
+ this.Description = siid + " To " + register;
+ this.Value = "";
+ this.LastUpdate = "";
+ }
+ }
+
+ class VXData
+ {
+ public String SIID { get; set; }
+ public Byte FrameID { get; set; }
+ public Byte CIN { get; set; }
+ public String Description { get; set; }
+ public String Value { get; set; }
+ public String LastUpdate { get; set; }
+ public VXData(String siid, Byte frameid, Byte cin, String Description)
+ {
+ this.SIID = siid;
+ this.FrameID = frameid;
+ this.CIN = cin;
+ this.Description=Description;
+ this.Value = "";
+ this.LastUpdate = "";
+ }
+ public VXData(String siid, Byte frameid, Byte cin)
+ {
+ this.SIID = siid;
+ this.FrameID = frameid;
+ this.CIN = cin;
+ this.Description = siid+" To "+ frameid + "." + cin;
+ this.Value = "";
+ this.LastUpdate = "";
+ }
+ }
+}
diff --git a/EventInterface.cs b/EventInterface.cs
new file mode 100644
index 0000000..b2ef6b0
--- /dev/null
+++ b/EventInterface.cs
@@ -0,0 +1,15 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace FAtoPA
+{
+ interface EventInterface
+ {
+ void ConnectStatus(bool success, String message);
+ void StatisticUpdate(uint TXOK, uint RXOK, uint TXErr, uint RXerr, uint TXBytes, uint RXBytes);
+ void Log(String msg);
+ }
+}
diff --git a/FAtoPA.Net.csproj b/FAtoPA.Net.csproj
new file mode 100644
index 0000000..cb23295
--- /dev/null
+++ b/FAtoPA.Net.csproj
@@ -0,0 +1,207 @@
+
+
+
+
+ Debug
+ AnyCPU
+ {C158634C-A54E-41CD-A41A-6D0AFF841BE4}
+ WinExe
+ FAtoPA.Net
+ FAtoPA.Net
+ v4.8
+ 512
+ {60dc8134-eba5-43b8-bcc9-bb4bc16c2548};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}
+ 4
+ true
+ true
+
+
+ AnyCPU
+ true
+ full
+ false
+ bin\Debug\
+ DEBUG;TRACE
+ prompt
+ 4
+
+
+ AnyCPU
+ pdbonly
+ true
+ bin\Release\
+ TRACE
+ prompt
+ 4
+
+
+ true
+ bin\x64\Debug\
+ DEBUG;TRACE
+ full
+ x64
+ 7.3
+ prompt
+ true
+
+
+ bin\x64\Release\
+ TRACE
+ true
+ pdbonly
+ x64
+ 7.3
+ prompt
+ true
+
+
+
+ ..\..\..\Bosch FA\FSM5000FSI-2.0.21\Release_x64\ApplicationProtocolCIL.dll
+
+
+ ..\..\..\Bosch FA\FSM5000FSI-2.0.21\Release_x64\Backend.dll
+
+
+ ..\..\..\Bosch FA\FSM5000FSI-2.0.21\Release_x64\DataExchangeInterfaces.dll
+
+
+ ..\..\..\Bosch FA\FSM5000FSI-2.0.21\Release_x64\FSIConfig.dll
+
+
+ ..\..\..\Bosch FA\FSM5000FSI-2.0.21\Release_x64\FSITrace.dll
+
+
+ ..\..\..\Bosch FA\FSM5000FSI-2.0.21\Release_x64\FSM5000FSI.dll
+
+
+ ..\..\..\Bosch FA\FSM5000FSI-2.0.21\Release_x64\FSM5000FSIAPI.dll
+
+
+ ..\..\..\Bosch FA\FSM5000FSI-2.0.21\Release_x64\FSM5000FSILoader.dll
+
+
+ packages\Microsoft.Bcl.AsyncInterfaces.9.0.0\lib\net462\Microsoft.Bcl.AsyncInterfaces.dll
+
+
+ packages\Microsoft.Data.Sqlite.Core.9.0.0\lib\netstandard2.0\Microsoft.Data.Sqlite.dll
+
+
+ ..\..\..\Bosch FA\FSM5000FSI-2.0.21\Release_x64\MPNetCIL.dll
+
+
+ ..\..\..\Bosch FA\FSM5000FSI-2.0.21\Release_x64\MPNetGate.dll
+
+
+ ..\..\..\Bosch FA\FSM5000FSI-2.0.21\Release_x64\Repository.dll
+
+
+ packages\SQLitePCLRaw.bundle_e_sqlite3.2.1.10\lib\net461\SQLitePCLRaw.batteries_v2.dll
+
+
+ packages\SQLitePCLRaw.core.2.1.10\lib\netstandard2.0\SQLitePCLRaw.core.dll
+
+
+ packages\SQLitePCLRaw.provider.dynamic_cdecl.2.1.10\lib\netstandard2.0\SQLitePCLRaw.provider.dynamic_cdecl.dll
+
+
+ ..\..\..\Bosch FA\FSM5000FSI-2.0.21\Release_x64\StandardCIL.dll
+
+
+
+ packages\System.Buffers.4.5.1\lib\net461\System.Buffers.dll
+
+
+
+ packages\System.IO.Pipelines.9.0.0\lib\net462\System.IO.Pipelines.dll
+
+
+ packages\System.Memory.4.5.5\lib\net461\System.Memory.dll
+
+
+
+ packages\System.Numerics.Vectors.4.5.0\lib\net46\System.Numerics.Vectors.dll
+
+
+ packages\System.Runtime.CompilerServices.Unsafe.6.0.0\lib\net461\System.Runtime.CompilerServices.Unsafe.dll
+
+
+ packages\System.Text.Encodings.Web.9.0.0\lib\net462\System.Text.Encodings.Web.dll
+
+
+ packages\System.Text.Json.9.0.0\lib\net462\System.Text.Json.dll
+
+
+ packages\System.Threading.Tasks.Extensions.4.5.4\lib\net461\System.Threading.Tasks.Extensions.dll
+
+
+ packages\System.ValueTuple.4.5.0\lib\net47\System.ValueTuple.dll
+
+
+
+
+
+
+
+
+ 4.0
+
+
+
+
+
+
+
+ MSBuild:Compile
+ Designer
+
+
+
+ MSBuild:Compile
+ Designer
+
+
+ App.xaml
+ Code
+
+
+
+
+
+
+
+ MainWindow.xaml
+ Code
+
+
+
+
+
+
+
+ Code
+
+
+ True
+ True
+ Resources.resx
+
+
+ True
+ Settings.settings
+ True
+
+
+ ResXFileCodeGenerator
+ Resources.Designer.cs
+
+
+
+ SettingsSingleFileGenerator
+ Settings.Designer.cs
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/FAtoPA.Net.sln b/FAtoPA.Net.sln
new file mode 100644
index 0000000..fbfdd7a
--- /dev/null
+++ b/FAtoPA.Net.sln
@@ -0,0 +1,28 @@
+
+Microsoft Visual Studio Solution File, Format Version 12.00
+# Visual Studio Version 17
+VisualStudioVersion = 17.12.35514.174 d17.12
+MinimumVisualStudioVersion = 10.0.40219.1
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FAtoPA.Net", "FAtoPA.Net.csproj", "{C158634C-A54E-41CD-A41A-6D0AFF841BE4}"
+EndProject
+Global
+ GlobalSection(SolutionConfigurationPlatforms) = preSolution
+ Debug|Any CPU = Debug|Any CPU
+ Debug|x64 = Debug|x64
+ Release|Any CPU = Release|Any CPU
+ Release|x64 = Release|x64
+ EndGlobalSection
+ GlobalSection(ProjectConfigurationPlatforms) = postSolution
+ {C158634C-A54E-41CD-A41A-6D0AFF841BE4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {C158634C-A54E-41CD-A41A-6D0AFF841BE4}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {C158634C-A54E-41CD-A41A-6D0AFF841BE4}.Debug|x64.ActiveCfg = Debug|x64
+ {C158634C-A54E-41CD-A41A-6D0AFF841BE4}.Debug|x64.Build.0 = Debug|x64
+ {C158634C-A54E-41CD-A41A-6D0AFF841BE4}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {C158634C-A54E-41CD-A41A-6D0AFF841BE4}.Release|Any CPU.Build.0 = Release|Any CPU
+ {C158634C-A54E-41CD-A41A-6D0AFF841BE4}.Release|x64.ActiveCfg = Release|x64
+ {C158634C-A54E-41CD-A41A-6D0AFF841BE4}.Release|x64.Build.0 = Release|x64
+ EndGlobalSection
+ GlobalSection(SolutionProperties) = preSolution
+ HideSolutionNode = FALSE
+ EndGlobalSection
+EndGlobal
diff --git a/FSM.cs b/FSM.cs
new file mode 100644
index 0000000..fa18635
--- /dev/null
+++ b/FSM.cs
@@ -0,0 +1,288 @@
+using FSM5000FSI;
+using FSM5000FSIAPI.Version3;
+
+using System;
+using System.CodeDom;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace FAtoPA
+{
+ class FSM
+ {
+ public static bool Started = false;
+ private FSIConfig config;
+ private FSIController controller;
+ private List listenerlist;
+ public Dictionary ItemTypeDictionary { get; }
+ public Dictionary AvailableNodes { get; }
+ private EventInterface _event;
+ public FSM(EventInterface callback)
+ {
+ _event = callback;
+ ItemTypeDictionary = new Dictionary();
+ AvailableNodes = new Dictionary();
+ listenerlist = new List();
+ }
+
+ ///
+ /// Initialize Fire Alarm Module
+ ///
+ /// 1 - 255
+ /// 1 - 255
+ /// 1 - 255
+ /// on a system with multiple network adapter, use this to specify which adapter will be used
+ public FSM(EventInterface callback, byte netgroup, byte netnode, byte pna, byte[] localip) : this(callback) {
+ SetConfig(netgroup, netnode, pna, localip);
+ }
+
+ public void SetConfig(byte netgroup, byte netnode, byte pna, byte[] localip)
+ {
+ config = new FSIConfig();
+ config.MPNetGroup = netgroup;
+ config.MPNetNode = netnode;
+ config.PNA = pna;
+ config.LocalIPAddress = localip;
+ config.RepositoryLocation = ".\\repository";
+ config.AdvancedFSIConfig = new AdvancedFSIConfig();
+ }
+
+ ///
+ /// Optional option if using Multicast Communication
+ ///
+ /// true if want to use Multicast
+ /// target Multicast IP, if not available, default to 239.192.0.1
+ /// target Port, if not available, default to 25000
+ public void MulticastConfig(Boolean useMulticast, byte[] MulticastIP, ushort PortNumber)
+ {
+ if (config != null)
+ {
+ if (config.AdvancedFSIConfig == null) config.AdvancedFSIConfig = new AdvancedFSIConfig();
+ config.AdvancedFSIConfig.IsIPMulticast = useMulticast;
+ config.AdvancedFSIConfig.MulticastAddress = MulticastIP;
+ config.AdvancedFSIConfig.PortNumber = PortNumber;
+ config.AdvancedFSIConfig.NetstackTraceLevel = SourceLevels.Error;
+ }
+
+ }
+
+
+ ///
+ /// Start FSM Monitoring
+ ///
+ public void Start()
+ {
+ controller = FSIController.GetInstance();
+ // internal Listeners
+ controller.AddListener(new ConfigListener(ItemTypeDictionary, AvailableNodes, listenerlist));
+ controller.AddListener(new StateListener(AvailableNodes, listenerlist));
+ TraceConsoleWritter outputter = new TraceConsoleWritter();
+ controller.SetTraceLevel(SourceLevels.Warning); //for support issues please set the level to SourceLevels.Verbose
+ Result result = controller.Startup(config);
+ //Debug.WriteLine("[FSM] Start result = "+result.ToString());
+ if (result==Result.SUCCESS)
+ {
+ Started = true;
+ if (_event != null) _event.ConnectStatus(true, "FSM Started");
+ }
+ else
+ {
+ Started = false;
+ if (_event != null) _event.ConnectStatus(false, "FSM Failed to Start");
+ }
+ }
+
+ public void AddListener(FSMResultInterface listener)
+ {
+ if (listenerlist != null) listenerlist.Add(listener);
+ }
+
+ ///
+ /// Stop FSM Monitoring
+ ///
+ public void Stop()
+ {
+ if (controller != null)
+ {
+ try
+ {
+ Result result = controller.Shutdown();
+ //Console.WriteLine("[FSM] Shutdown result = " + result.ToString());
+ if (_event!=null) _event.ConnectStatus(false, "FSM Stopped");
+ } catch (Exception e)
+ {
+ Debug.WriteLine("[FSM] Error on Stop: " + e.Message);
+ }
+
+ }
+
+ }
+
+
+
+ }
+
+
+ ///
+ /// The Config Listener receives all information about the configuration
+ /// received from the panel network. The information must be stored in real application
+ /// to know the devices from the panel network and the possible commands.
+ ///
+ class ConfigListener : IPanelConfigListener
+ {
+ private Dictionary type_dictionary;
+ private Dictionary node_data;
+ private List listeners;
+ public ConfigListener(Dictionary type, Dictionary data, List listener)
+ {
+ this.type_dictionary = type;
+ this.node_data = data;
+ this.listeners = listener;
+ }
+
+ ///
+ /// Akan muncul di pertama kali, untuk informasi bahwa device dengan FunctionalType [X} memiliki deskripsi [Y]
+ ///
+ ///
+ public void SetItemType(ItemType devItemType)
+ {
+ Debug.WriteLine($"Item type {devItemType.FunctionalType} ({devItemType.Description})");
+ if (type_dictionary != null)
+ {
+ int type = int.Parse(devItemType.FunctionalType.ToString());
+ if (!type_dictionary.ContainsKey(type)) type_dictionary.Add(type, devItemType.Description);
+
+ }
+
+ }
+
+ ///
+ /// Akan muncul di urutan kedua, untuk informasi node device apa saja yang terdeteksi di system
+ ///
+ ///
+ public void SetItem(Item devItem)
+ {
+ Debug.WriteLine($"Item ({devItem.SIID}, {devItem.FunctionalType} ({devItem.Label})");
+ if (node_data != null)
+ {
+ String SIID = devItem.SIID.ToString();
+ int type = int.Parse(devItem.FunctionalType.ToString());
+ if (!node_data.ContainsKey(SIID))
+ {
+ NodeData nodeData = new NodeData();
+ nodeData.SIID = devItem.SIID;
+ nodeData.Type = type;
+ nodeData.Label = devItem.Label;
+ nodeData.Description = type_dictionary.ContainsKey(type) ? type_dictionary[type] : "Unknown";
+ node_data.Add(SIID, nodeData);
+ // notify all listeners
+ if (listeners != null)
+ foreach (var item in listeners)
+ item.DiscoveredSIID(SIID, nodeData);
+
+ }
+ }
+
+ }
+
+ ///
+ /// Akan muncul kalau suatu node hilang dari system
+ ///
+ ///
+ public void RemoveItem(SIID address)
+ {
+ Debug.WriteLine("SI " + address + " removed.");
+ if (node_data != null)
+ {
+ String SIID = address.ToString();
+ if (node_data.ContainsKey(SIID)) node_data.Remove(SIID);
+
+ }
+
+ }
+ }
+
+ ///
+ /// The State Listener receive all states for devices send by the panel network.
+ /// Ini yang paling penting, untuk trigger ke Modbus dan VX3000
+ ///
+ class StateListener : IStateListener
+ {
+ private Dictionary node_data;
+ private List listeners;
+ public StateListener(Dictionary data, List listener)
+ {
+ this.node_data= data;
+ this.listeners = listener;
+ }
+ ///
+ /// Akan muncul ketiga dan seterusnya, ketika ada perubahan status dari node device
+ ///
+ ///
+ public void SetItemState(ItemState devItemState)
+ {
+ Debug.WriteLine($"State {devItemState.SIID} = {devItemState.LogicalState}");
+ if (node_data != null)
+ {
+ String SIID = devItemState.SIID.ToString();
+ if (node_data.ContainsKey(SIID))
+ {
+ NodeData nd = node_data[SIID];
+ if (nd != null)
+ {
+ NodeState prev = nd.State;
+ nd.State = new NodeState(devItemState);
+
+ // notify all listeners
+ if (listeners != null)
+ foreach (var item in listeners)
+ item.NewState(SIID, prev, nd.State);
+
+ if (prev != null)
+ {
+ Debug.WriteLine("SIID=" + SIID + " Change LogicalState from " + prev.LogicalState + " to " + nd.State.LogicalState);
+ }
+ else
+ {
+ Debug.WriteLine("New State for SIID=" + SIID + " LogicalState=" + nd.State.LogicalState);
+ }
+ } else Debug.WriteLine("NodeData with SIID " + SIID + " is null");
+ } else Debug.WriteLine("node_data doesn't contain SIID " + SIID);
+
+ } else Debug.WriteLine("node_data is null");
+
+
+ }
+
+ ///
+ /// Akan muncul sekali di awal mula, sebagai informasi waktu di panel system
+ ///
+ ///
+ public void SetMPNetTime(DateTime mpNetTime)
+ {
+ Debug.WriteLine("Time is " + mpNetTime + ".");
+ }
+ }
+
+ ///
+ /// Receive the traces from the FSI and output the traces to console
+ /// In real application it is very useful to write the traces to files
+ /// for suppport issues
+ ///
+ class TraceConsoleWritter : TraceListener
+ {
+ public override void Write(string message)
+ {
+ if (message!=null && message.Length>0) Debug.Write(message);
+ }
+
+ public override void WriteLine(string message)
+ {
+ if (message != null && message.Length > 0) Debug.WriteLine(message);
+ }
+ }
+
+}
diff --git a/FSMResultInterface.cs b/FSMResultInterface.cs
new file mode 100644
index 0000000..9fcee90
--- /dev/null
+++ b/FSMResultInterface.cs
@@ -0,0 +1,14 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace FAtoPA
+{
+ interface FSMResultInterface
+ {
+ void DiscoveredSIID(String SIID, NodeData type);
+ void NewState(String SIID, NodeState previous, NodeState current);
+ }
+}
diff --git a/MainWindow.xaml b/MainWindow.xaml
new file mode 100644
index 0000000..35b78c3
--- /dev/null
+++ b/MainWindow.xaml
@@ -0,0 +1,279 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/MainWindow.xaml.cs b/MainWindow.xaml.cs
new file mode 100644
index 0000000..c6be18b
--- /dev/null
+++ b/MainWindow.xaml.cs
@@ -0,0 +1,1417 @@
+using FSM5000FSIAPI.Version3;
+using System;
+using System.Collections.Generic;
+using System.Collections.ObjectModel;
+using System.Diagnostics;
+using System.Linq;
+using System.Net;
+using System.Text;
+using System.Text.RegularExpressions;
+using System.Threading.Tasks;
+using System.Windows;
+using System.Windows.Controls;
+using System.Windows.Data;
+using System.Windows.Documents;
+using System.Windows.Input;
+using System.Windows.Media;
+using System.Windows.Media.Imaging;
+using System.Windows.Navigation;
+using System.Windows.Shapes;
+using System.Windows.Threading;
+
+namespace FAtoPA.Net
+{
+ ///
+ /// Interaction logic for MainWindow.xaml
+ ///
+ public partial class MainWindow : Window
+ {
+ private DispatcherTimer timer1s;
+ private Config config;
+ private Database database;
+ private FSM fsm;
+ private ModbusSlave modbusSlave;
+ private VX3K vx3k;
+
+ // untuk values di Combobox FSM SIID
+ public ObservableCollection FSMSIID { get; set; }
+ // untuk values di Combobox VX3K ID dan CIN
+ public ObservableCollection VX3KID { get; set; }
+ public ObservableCollection VX3KCIN { get; set; }
+ // untuk values di Combobox Modbus Registers
+ public ObservableCollection ModbusRegisters { get; set; }
+
+ public ObservableCollection NetGroupList { get; set; }
+ public ObservableCollection NetNodeList { get; set; }
+ public ObservableCollection PNAList { get; set; }
+
+ public ObservableCollection SINumberList { get; set; }
+ public ObservableCollection SISubList { get; set; }
+
+ ObservableCollection FsmTableMember { get; set; }
+ ObservableCollection ModbusTableMember { get; set; }
+ ObservableCollection VXTableMember { get; set; }
+
+ FSMEvent fsmEvent;
+ public MainWindow()
+ {
+ InitializeComponent();
+ // Di-isi dengan user input di Tab Fire Alarm
+ FSMSIID = new ObservableCollection();
+
+ // Isinya ada di Window_Loaded
+ ModbusRegisters = new ObservableCollection();
+ FsmTableMember = new ObservableCollection();
+ ModbusTableMember = new ObservableCollection();
+ VXTableMember = new ObservableCollection();
+
+
+
+ // Set default value for VX3KID and VX3KCIN
+ VX3KID = new ObservableCollection();
+ for (int i = 0; i < 40; i++)
+ {
+ VX3KID.Add(i);
+ }
+
+ VX3KCIN = new ObservableCollection();
+ for (int i = 0; i < 16; i++)
+ {
+ VX3KCIN.Add(i);
+ }
+
+ NetGroupList = new ObservableCollection();
+ NetNodeList = new ObservableCollection();
+ PNAList = new ObservableCollection();
+
+ SINumberList = new ObservableCollection();
+ SISubList = new ObservableCollection();
+
+ for (int ii = 1; ii < 256; ii++)
+ {
+ NetGroupList.Add(ii);
+ NetNodeList.Add(ii);
+ PNAList.Add(ii);
+
+ SINumberList.Add(ii);
+ SISubList.Add(ii);
+ }
+
+ siType.ItemsSource = Enum.GetValues(typeof(SIType)).Cast().ToList();
+
+ this.DataContext = this;
+ }
+
+ private void Window_Loaded(object sender, RoutedEventArgs e)
+ {
+ Debug.WriteLine("Window Loaded");
+ // Timer untuk update datetimebar tiap 1 detik
+ timer1s = new DispatcherTimer();
+ timer1s.Interval = new TimeSpan(0, 0, 1);
+ timer1s.Tick += Timer1s_Tick;
+
+ timer1s.Start();
+
+ // Load Config
+ config = new Config();
+ config.Load();
+ FSMConfig_NetGroup.Text = config.FSM_NetGroup.ToString();
+ FSMConfig_NetNode.Text = config.FSM_NetNode.ToString();
+ FSMConfig_PNA.Text = config.FSM_PNA.ToString();
+ FSMConfig_LocalIP.Text = config.FSM_LocalIP;
+ FSM_UseMulticast.IsChecked = config.FSM_UseMulticast;
+ FSMConfig_MulticastAddress.Text = config.FSM_MulticastIP;
+ FSMConfig_MulticastPort.Text = config.FSM_MulticastPort.ToString();
+ Debug.WriteLine("NetGroup : " + config.FSM_NetGroup);
+ Debug.WriteLine("NetNode : " + config.FSM_NetNode);
+ Debug.WriteLine("PNA : " + config.FSM_PNA);
+ Debug.WriteLine("LocalIP : " + config.FSM_LocalIP);
+ Debug.WriteLine("UseMulticast : " + config.FSM_UseMulticast);
+ Debug.WriteLine("MulticastIP : " + config.FSM_MulticastIP);
+ Debug.WriteLine("MulticastPort : " + config.FSM_MulticastPort);
+
+
+ ModbusListenPort.Text = config.Modbus_ListenPort.ToString();
+ ModbusDeviceID.Text = config.Modbus_DeviceID.ToString();
+ ModbusMaxRegister.Text = config.Modbus_MaxRegister.ToString();
+ VX3K_IP.Text = config.VX_TargetIP;
+ VX3K_Port.Text = config.VX_TargetPort.ToString();
+
+
+
+
+ // Load Database
+ database = new Database();
+ database.GetFSMDatas().ForEach(f => FsmTableMember.Add(f));
+ database.GetModbusDatas().ForEach(m => ModbusTableMember.Add(m));
+ database.GetVXDatas().ForEach(v => VXTableMember.Add(v));
+
+ FSMTable.ItemsSource = FsmTableMember;
+ ModbusTable.ItemsSource = ModbusTableMember;
+ VXTable.ItemsSource = VXTableMember;
+
+ // Load FSM
+ fsmEvent = new FSMEvent(this.firealarmstatusbar);
+ fsm = new FSM(fsmEvent, config.FSM_NetGroup, config.FSM_NetNode, config.FSM_PNA, IPAddress.Parse(config.FSM_LocalIP).GetAddressBytes());
+ if (config.FSM_UseMulticast)
+ {
+ fsm.MulticastConfig(config.FSM_UseMulticast, IPAddress.Parse(config.FSM_MulticastIP).GetAddressBytes(), config.FSM_MulticastPort);
+ }
+
+
+ ModbusRegisters.Clear();
+ for (int i = 0; i < config.Modbus_MaxRegister; i++)
+ {
+ ModbusRegisters.Add(i);
+ }
+
+ // Load Modbus Slave
+ modbusSlave = new ModbusSlave(new ModbusEvent(modbusstatusbar, ConnectedModbusClients, ConnectedModbusCount), config.Modbus_MaxRegister);
+ modbusSlave.ID = config.Modbus_DeviceID;
+ //modbusSlave.Start(config.Modbus_ListenPort);
+
+ // Connect with VX3000
+ vx3k = new VX3K(new VX3KEvent(this.vxstatusbar));
+ //vx3k.Connect(config.VX_TargetIP, config.VX_TargetPort);
+
+
+ fsm.AddListener(new FSMTableUpdater(FsmTableMember, DetectedSIID, DetectedSIIDCount));
+ fsm.AddListener(new ModbusTriggerFromFSM(FsmTableMember, ModbusTableMember, modbusSlave));
+ fsm.AddListener(new VXTriggerFromFSM(FsmTableMember, VXTableMember, vx3k));
+ }
+
+ private void Timer1s_Tick(object sender, EventArgs e)
+ {
+ datetimebar.Text = DateTime.Now.ToString();
+ }
+
+ private void Window_Closing(object sender, System.ComponentModel.CancelEventArgs e)
+ {
+ Debug.WriteLine("Window Closing");
+ timer1s?.Stop();
+ fsm?.Stop();
+ modbusSlave?.Stop();
+ vx3k?.Disconnect();
+ }
+
+ private void btnStartStopFSM_Click(object sender, RoutedEventArgs e)
+ {
+ if (FSM.Started)
+ {
+ fsm?.Stop();
+ btnStartStopFSM.Content = "Start FSM Connection";
+ FSM.Started = false;
+ }
+ else
+ {
+ fsm?.Start();
+ btnStartStopFSM.Content = "Stop FSM Connection";
+ FSM.Started = true;
+ }
+ }
+
+ private void btnStartStopModbus_Click(object sender, RoutedEventArgs e)
+ {
+ if (ModbusSlave.Started)
+ {
+ modbusSlave?.Stop();
+ btnStartStopModbus.Content = "Start Modbus Connection";
+ ModbusSlave.Started = false;
+ }
+ else
+ {
+ if (config != null)
+ {
+ modbusSlave?.Start(config.Modbus_ListenPort);
+ btnStartStopModbus.Content = "Stop Modbus Connection";
+ ModbusSlave.Started = true;
+
+ }
+ }
+
+ }
+
+ private void btnStartStopVX_Click(object sender, RoutedEventArgs e)
+ {
+ if (VX3K.Started)
+ {
+ vx3k?.Disconnect();
+ btnStartStopVX.Content = "Start VX Connection";
+ VX3K.Started = false;
+ }
+ else
+ {
+ if (config != null)
+ {
+ vx3k?.Connect(config.VX_TargetIP, config.VX_TargetPort);
+ btnStartStopVX.Content = "Stop VX Connection";
+ VX3K.Started = true;
+ }
+ }
+ }
+
+ private void ApplyFSMConfig(object sender, RoutedEventArgs e)
+ {
+ if (config != null)
+ {
+ Boolean changed = false;
+ int netgroup = 0;
+ if (int.TryParse(FSMConfig_NetGroup.Text, out netgroup))
+ {
+ if (NetGroupList.Contains(netgroup))
+ {
+ if (netgroup != config.FSM_NetGroup)
+ {
+ config.FSM_NetGroup = (byte)netgroup;
+ changed = true;
+ }
+ }
+ else
+ {
+ MessageBox.Show("Invalid Net Group");
+ return;
+ }
+ }
+ else
+ {
+ MessageBox.Show("Invalid Net Group");
+ return;
+ }
+ int netnode = 0;
+ if (int.TryParse(FSMConfig_NetNode.Text, out netnode))
+ {
+ if (NetNodeList.Contains(netnode))
+ {
+ if (netnode != config.FSM_NetNode)
+ {
+ config.FSM_NetNode = (byte)netnode;
+ changed = true;
+ }
+ }
+ else
+ {
+ MessageBox.Show("Invalid Net Node");
+ return;
+ }
+
+ }
+ else
+ {
+ MessageBox.Show("Invalid Net Node");
+ return;
+ }
+ int pna = 0;
+ if (int.TryParse(FSMConfig_PNA.Text, out pna))
+ {
+ if (PNAList.Contains(pna))
+ {
+ if (pna != config.FSM_PNA)
+ {
+ config.FSM_PNA = (byte)pna;
+ changed = true;
+ }
+ }
+ else
+ {
+ MessageBox.Show("Invalid PNA");
+ return;
+ }
+ }
+ else
+ {
+ MessageBox.Show("Invalid PNA");
+ return;
+ }
+ if (TextIsIPAddress(FSMConfig_LocalIP.Text))
+ {
+ if (FSMConfig_LocalIP.Text != config.FSM_LocalIP)
+ {
+ config.FSM_LocalIP = FSMConfig_LocalIP.Text;
+ changed = true;
+ }
+ }
+ else
+ {
+ MessageBox.Show("Invalid Local IP");
+ return;
+ }
+
+ if (FSM_UseMulticast.IsChecked != null)
+ {
+ if (FSM_UseMulticast.IsChecked != config.FSM_UseMulticast)
+ {
+ config.FSM_UseMulticast = (bool)FSM_UseMulticast.IsChecked;
+ changed = true;
+ }
+ }
+ else
+ {
+ MessageBox.Show("Invalid Multicast Use");
+ return;
+ }
+
+ if (TextIsIPAddress(FSMConfig_MulticastAddress.Text))
+ {
+ if (FSMConfig_MulticastAddress.Text != config.FSM_MulticastIP)
+ {
+ config.FSM_MulticastIP = FSMConfig_MulticastAddress.Text;
+ changed = true;
+ }
+ }
+ else
+ {
+ MessageBox.Show("Invalid Multicast Address");
+ return;
+ }
+ int multicastport = 0;
+ if (int.TryParse(FSMConfig_MulticastPort.Text, out multicastport))
+ {
+ if (multicastport >= 0 && multicastport <= 65535)
+ {
+ if (multicastport != config.FSM_MulticastPort)
+ {
+ config.FSM_MulticastPort = (ushort)multicastport;
+ changed = true;
+ }
+ }
+ else
+ {
+ MessageBox.Show("Invalid Multicast Port");
+ return;
+ }
+ }
+ else
+ {
+ MessageBox.Show("Invalid Multicast Port");
+ return;
+ }
+ if (changed)
+ {
+ if (config.Save())
+ {
+ MessageBox.Show("FSM Config Saved");
+ //TODO : Restart FSM Connection
+ }
+ else MessageBox.Show("FSM Config Save Failed");
+ }
+ else MessageBox.Show("No changes in FSM Config");
+ }
+ else MessageBox.Show("Config is null");
+ }
+
+ private void ApplyModbusConfig(object sender, RoutedEventArgs e)
+ {
+ if (config != null)
+ {
+ Boolean changed = false;
+ int listenport = 0;
+ if (int.TryParse(ModbusListenPort.Text, out listenport))
+ {
+ if (listenport >= 0 && listenport <= 65535)
+ {
+ if (listenport != config.Modbus_ListenPort)
+ {
+ config.Modbus_ListenPort = (ushort)listenport;
+ changed = true;
+ }
+
+ }
+ else
+ {
+ MessageBox.Show("Invalid Listen Port");
+ return;
+ }
+ }
+ else
+ {
+ MessageBox.Show("Invalid Listen Port");
+ return;
+ }
+ int devid = 0;
+ if (int.TryParse(ModbusDeviceID.Text, out devid))
+ {
+ if (devid >= 1 && devid <= 247)
+ {
+ if (devid != config.Modbus_DeviceID)
+ {
+ config.Modbus_DeviceID = (byte)devid;
+ changed = true;
+ }
+ }
+ else
+ {
+ MessageBox.Show("Invalid Device ID");
+ return;
+ }
+ }
+ else
+ {
+ MessageBox.Show("Invalid Device ID");
+ return;
+ }
+ int maxreg = 0;
+ if (int.TryParse(ModbusMaxRegister.Text, out maxreg))
+ {
+ if (maxreg >= 1 && maxreg <= 10000)
+ {
+ if (maxreg != config.Modbus_MaxRegister)
+ {
+ config.Modbus_MaxRegister = (ushort)maxreg;
+ changed = true;
+ }
+ }
+ else
+ {
+ MessageBox.Show("Invalid Max Register");
+ return;
+ }
+ }
+ else
+ {
+ MessageBox.Show("Invalid Max Register");
+ }
+ if (changed)
+ {
+ if (config.Save())
+ {
+ MessageBox.Show("Modbus Config Saved");
+ //TODO : Restart Modbus Connection
+ }
+ else MessageBox.Show("Modbus Config Save Failed");
+ }
+ else MessageBox.Show("No changes in Modbus Config");
+ }
+ else MessageBox.Show("Config is null");
+
+ }
+
+ private void ApplyVX3KConfig(object sender, RoutedEventArgs e)
+ {
+ if (config != null)
+ {
+ Boolean changed = false;
+ String ipaddress = "";
+ int port = 0;
+ try
+ {
+ IPAddress ip = IPAddress.Parse(VX3K_IP.Text);
+ ipaddress = ip.ToString();
+ if (ipaddress != config.VX_TargetIP)
+ {
+ config.VX_TargetIP = ipaddress;
+ changed = true;
+ }
+ }
+ catch (Exception)
+ {
+ MessageBox.Show("Invalid IP Address");
+ return;
+ }
+ if (int.TryParse(VX3K_Port.Text, out port))
+ {
+ if (port != config.VX_TargetPort)
+ {
+ config.VX_TargetPort = (ushort)port;
+ changed = true;
+ }
+ }
+ else
+ {
+ MessageBox.Show("Invalid Port");
+ return;
+ }
+
+ if (changed)
+ {
+ if (config.Save())
+ {
+ MessageBox.Show("VX3K Config Saved");
+ //TODO : Restart VX3K Connection
+ }
+ else MessageBox.Show("VX3K Config Save Failed");
+ }
+ else MessageBox.Show("No changes in VX3K Config");
+ }
+ else MessageBox.Show("Config is null");
+
+ }
+
+ // Regex Protection for FSMConfig_NetGroup
+ private void FSMConfig_NetGroup_PreviewTextInput(object sender, TextCompositionEventArgs e)
+ {
+ TextBoxNumberOnly(e);
+ }
+
+ // Regex Protection for FSMConfig_NetNode
+ private void FSMConfig_NetNode_PreviewTextInput(object sender, TextCompositionEventArgs e)
+ {
+ TextBoxNumberOnly(e);
+ }
+
+ // Regex Protection for FSMConfig_PNA
+ private void FSMConfig_PNA_PreviewTextInput(object sender, TextCompositionEventArgs e)
+ {
+ TextBoxNumberOnly(e);
+ }
+
+ // Regex Protection for FSMConfig_LocalIP
+ private void FSMConfig_LocalIP_PreviewTextInput(object sender, TextCompositionEventArgs e)
+ {
+ TextBoxIPAddressOnly(e);
+ }
+
+ // Regex Protection for FSMConfig_MulticastAddress
+ private void FSMConfig_MulticastAddress_PreviewTextInput(object sender, TextCompositionEventArgs e)
+ {
+ TextBoxIPAddressOnly(e);
+ }
+
+ // Regex Protection for FSMConfig_MulticastPort
+ private void FSMConfig_MulticastPort_PreviewTextInput(object sender, TextCompositionEventArgs e)
+ {
+ TextBoxNumberOnly(e);
+ }
+
+ // If NetGroup value is changed, check for valid value. If not valid, change background to red
+ private void FSMConfig_NetGroup_TextChanged(object sender, TextChangedEventArgs e)
+ {
+ TextBox_FSMVerify(FSMConfig_NetGroup);
+ }
+
+ // If NetNode value is changed, check for valid value. If not valid, change background to red
+ private void FSMConfig_NetNode_TextChanged(object sender, TextChangedEventArgs e)
+ {
+ TextBox_FSMVerify(FSMConfig_NetNode);
+ }
+
+ // If PNA value is changed, check for valid value. If not valid, change background to red
+ private void FSMConfig_PNA_TextChanged(object sender, TextChangedEventArgs e)
+ {
+ TextBox_FSMVerify(FSMConfig_PNA);
+ }
+
+ // If LocalIP value is changed, check for valid value. If not valid, change background to red
+ private void FSMConfig_LocalIP_TextChanged(object sender, TextChangedEventArgs e)
+ {
+ TextBox_IPVerify(FSMConfig_LocalIP);
+ }
+
+ // If MulticastAddress value is changed, check for valid value. If not valid, change background to red
+ private void FSMConfig_MulticastAddress_TextChanged(object sender, TextChangedEventArgs e)
+ {
+ TextBox_IPVerify(FSMConfig_MulticastAddress);
+ }
+
+ // If MulticastPort value is changed, check for valid value. If not valid, change background to red
+ private void FSMConfig_MulticastPort_TextChanged(object sender, TextChangedEventArgs e)
+ {
+ TextBox_PortVerify(FSMConfig_MulticastPort);
+ }
+
+ // Only allow number input
+ private void TextBoxNumberOnly(TextCompositionEventArgs e)
+ {
+ Regex regex = new Regex("[^0-9]+");
+ e.Handled = regex.IsMatch(e.Text);
+ }
+
+ private void TextBoxIPAddressOnly(TextCompositionEventArgs e)
+ {
+ //string pattern = @"^((25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$";
+ //Regex regex = new Regex(pattern);
+ //e.Handled = regex.IsMatch(e.Text);
+ e.Handled = TextIsIPAddress(e.Text);
+ }
+ private Boolean TextIsIPAddress(String vv)
+ {
+ if (vv != null)
+ {
+ if (vv.Length > 0)
+ {
+ string pattern = @"^((25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$";
+ Regex regex = new Regex(pattern);
+ return regex.IsMatch(vv);
+ }
+ }
+ return false;
+ }
+
+ // Verify Textbox value for FSM, which mostly only accept number 1 - 255
+ private void TextBox_FSMVerify(TextBox e)
+ {
+ if (int.TryParse(e.Text, out int result))
+ {
+ if (result < 0 || result > 255)
+ {
+ e.Background = Brushes.Red;
+ }
+ else
+ {
+ e.Background = Brushes.Transparent;
+ }
+ }
+ else
+ {
+ e.Background = Brushes.Red;
+ }
+ }
+
+ private void TextBox_IPVerify(TextBox e)
+ {
+ try
+ {
+ IPAddress.Parse(e.Text);
+ e.Background = Brushes.Transparent;
+ }
+ catch (Exception)
+ {
+ e.Background = Brushes.Red;
+ }
+ }
+
+ private void TextBox_PortVerify(TextBox e)
+ {
+ if (int.TryParse(e.Text, out int result))
+ {
+ if (result < 0 || result > 65535)
+ {
+ e.Background = Brushes.Red;
+ }
+ else
+ {
+ e.Background = Brushes.Transparent;
+ }
+ }
+ else
+ {
+ e.Background = Brushes.Red;
+ }
+ }
+
+ private void VX3K_IP_PreviewTextInput(object sender, TextCompositionEventArgs e)
+ {
+ TextBoxIPAddressOnly(e);
+ }
+
+ private void VX3K_Port_PreviewTextInput(object sender, TextCompositionEventArgs e)
+ {
+ TextBoxNumberOnly(e);
+ }
+
+ private void ModbusListenPort_PreviewTextInput(object sender, TextCompositionEventArgs e)
+ {
+ TextBoxNumberOnly(e);
+ }
+
+ private void ModbusDeviceID_PreviewTextInput(object sender, TextCompositionEventArgs e)
+ {
+ TextBoxNumberOnly(e);
+ }
+
+ private void ModbusMaxRegister_PreviewTextInput(object sender, TextCompositionEventArgs e)
+ {
+ TextBoxNumberOnly(e);
+ }
+
+ private void ModbusListenPort_TextChanged(object sender, TextChangedEventArgs e)
+ {
+ TextBox_PortVerify(ModbusListenPort);
+ }
+
+ private void VX3K_Port_TextChanged(object sender, TextChangedEventArgs e)
+ {
+ TextBox_PortVerify(VX3K_Port);
+ }
+
+ private void VX3K_IP_TextChanged(object sender, TextChangedEventArgs e)
+ {
+ TextBox_IPVerify(VX3K_IP);
+ }
+
+ private void ModbusDeviceID_TextChanged(object sender, TextChangedEventArgs e)
+ {
+ int devid = 0;
+ if (int.TryParse(ModbusDeviceID.Text, out devid))
+ {
+ if (devid >= 1 && devid <= 247)
+ {
+ ModbusDeviceID.Background = Brushes.Transparent;
+ }
+ else
+ {
+ ModbusDeviceID.Background = Brushes.Red;
+ }
+ }
+ else
+ {
+ ModbusDeviceID.Background = Brushes.Red;
+ }
+ }
+
+ private void ModbusMaxRegister_TextChanged(object sender, TextChangedEventArgs e)
+ {
+ int maxreg = 0;
+ if (int.TryParse(ModbusMaxRegister.Text, out maxreg))
+ {
+ if (maxreg >= 1 && maxreg <= 10000)
+ {
+ ModbusMaxRegister.Background = Brushes.Transparent;
+ }
+ else
+ {
+ ModbusMaxRegister.Background = Brushes.Red;
+ }
+ }
+ else
+ {
+ ModbusMaxRegister.Background = Brushes.Red;
+ }
+ }
+
+ private void btnAddSIID_Click(object sender, RoutedEventArgs e)
+ {
+ SIID selected = GetSIID();
+ if (selected != null)
+ {
+ MessageBoxResult result = MessageBox.Show("Add SIID " + selected.ToString() + " ?", "Add SIID", MessageBoxButton.YesNo);
+ if (result == MessageBoxResult.Yes)
+ {
+ if (database != null)
+ {
+ FSMData f = new FSMData(selected.ToString());
+ if (database.AddFSMData(f))
+ {
+ FSMTable.ItemsSource = database.GetFSMDatas();
+ FSMSIID.Add(selected.ToString());
+ }
+ else MessageBox.Show("Failed to add to database");
+ }
+ else MessageBox.Show("Database is null");
+ }
+ }
+ else MessageBox.Show("Invalid Selection");
+
+ }
+
+ private void btnDelSIID_Click(object sender, RoutedEventArgs e)
+ {
+ SIID selected = GetSIID();
+ if (selected != null)
+ {
+ MessageBoxResult result = MessageBox.Show("Delete SIID " + selected.ToString() + " ?", "Delete SIID", MessageBoxButton.YesNo);
+ if (result == MessageBoxResult.Yes)
+ {
+ if (database != null)
+ {
+ if (database.RemoveFSMDatabySIID(selected.ToString()))
+ {
+ FSMTable.ItemsSource = database.GetFSMDatas();
+ FSMSIID.Remove(selected.ToString());
+ }
+ else MessageBox.Show("Failed to delete from database");
+ }
+ else MessageBox.Show("Database is null");
+ }
+ }
+ else MessageBox.Show("Invalid Selection");
+ }
+
+ private void btnClearSIID_Click(object sender, RoutedEventArgs e)
+ {
+ MessageBoxResult result = MessageBox.Show("Clear all SIID ?", "Clear SIID", MessageBoxButton.YesNo);
+ if (result == MessageBoxResult.Yes)
+ {
+ if (database != null)
+ {
+ if (database.ClearFSMTable())
+ {
+ FSMTable.ItemsSource = database.GetFSMDatas();
+ FSMSIID.Clear();
+ }
+ else MessageBox.Show("Failed to clear database");
+ }
+ else MessageBox.Show("Database is null");
+
+ }
+ }
+
+ private SIID GetSIID()
+ {
+ int netgroup = 0;
+ int netnode = 0;
+ SIType sitype = 0;
+ int sinumber = 0;
+ int sisub = 0;
+
+ if (netGroupNumber.SelectedItem != null)
+ {
+ netgroup = (int)netGroupNumber.SelectedItem;
+ if (netNodeNumber.SelectedItem != null)
+ {
+ netnode = (int)netNodeNumber.SelectedItem;
+ if (siType.SelectedItem != null)
+ {
+ sitype = (SIType)siType.SelectedItem;
+ if (siNumber.SelectedItem != null)
+ {
+ sinumber = (int)siNumber.SelectedItem;
+ if (siSub.SelectedItem != null)
+ {
+ sisub = (int)siSub.SelectedItem;
+
+ return new SIID((byte)netgroup, (byte)netnode, sitype, (ushort)sinumber, (byte)sisub);
+ }
+ }
+ }
+ }
+ }
+ return null;
+
+ }
+
+ private void btnAddModbus_Click(object sender, RoutedEventArgs e)
+ {
+ String ssid = "";
+ int reg = 0;
+ if (ModbusSIIDComboBox.SelectedItem != null)
+ {
+ ssid = (string)ModbusSIIDComboBox.SelectedItem;
+ }
+ else
+ {
+ MessageBox.Show("Invalid SIID"); return;
+ }
+ if (ModbusRegister.SelectedItem != null)
+ {
+ reg = (int)ModbusRegister.SelectedItem;
+ }
+ else
+ {
+ MessageBox.Show("Invalid Register"); return;
+ }
+
+ MessageBoxResult result = MessageBox.Show("Add SIID " + ssid + " Link with Register " + reg + " ?", "Add Modbus Linkage", MessageBoxButton.YesNo);
+ if (result == MessageBoxResult.Yes)
+ {
+ if (database != null)
+ {
+ ModbusData m = new ModbusData(ssid, (ushort)reg);
+ if (database.AddModbusData(m))
+ {
+ ModbusTable.ItemsSource = database.GetModbusDatas();
+ }
+ else MessageBox.Show("Failed to add to database");
+ }
+ else MessageBox.Show("Database is null");
+ }
+ }
+
+ private void btnDelModbus_Click(object sender, RoutedEventArgs e)
+ {
+ String ssid = "";
+ String reg = "";
+ if (ModbusSIIDComboBox.SelectedItem != null)
+ {
+ ssid = (string)ModbusSIIDComboBox.SelectedItem;
+ }
+ else
+ {
+ MessageBox.Show("Invalid SIID"); return;
+ }
+ if (ModbusRegister.SelectedItem != null)
+ {
+ reg = (String)ModbusRegister.SelectedItem;
+ }
+ else
+ {
+ MessageBox.Show("Invalid Register"); return;
+ }
+ MessageBoxResult result = MessageBox.Show("Delete SIID " + ssid + " Link with Register " + reg + " ?", "Delete Modbus Linkage", MessageBoxButton.YesNo);
+ if (result == MessageBoxResult.Yes)
+ {
+ if (database != null)
+ {
+ if (database.RemoveModbusDatabySIID(ssid))
+ {
+ ModbusTable.ItemsSource = database.GetModbusDatas();
+ }
+ else MessageBox.Show("Failed to delete from database");
+ }
+ else MessageBox.Show("Database is null");
+ }
+ }
+
+ private void btnClearModbus_Click(object sender, RoutedEventArgs e)
+ {
+ MessageBoxResult result = MessageBox.Show("Clear all Modbus Linkage ?", "Clear Modbus Linkage", MessageBoxButton.YesNo);
+ if (result == MessageBoxResult.Yes)
+ {
+ if (database != null)
+ {
+ if (database.ClearModbusTable())
+ {
+ ModbusTable.ItemsSource = database.GetModbusDatas();
+ }
+ else MessageBox.Show("Failed to clear database");
+ }
+ else MessageBox.Show("Database is null");
+ }
+ }
+
+ private void btnAddVX_Click(object sender, RoutedEventArgs e)
+ {
+ String ssid = "";
+ int id = 0;
+ int cin = 0;
+ if (VXSIIDComboBox.SelectedItem != null)
+ {
+ ssid = (string)VXSIIDComboBox.SelectedItem;
+ }
+ else
+ {
+ MessageBox.Show("Invalid SIID"); return;
+ }
+ if (VXFrame.SelectedItem != null)
+ {
+ id = (int)VXFrame.SelectedItem;
+ }
+ else
+ {
+ MessageBox.Show("Invalid ID"); return;
+ }
+ if (VXCIN.SelectedItem != null)
+ {
+ cin = (int)VXCIN.SelectedItem;
+ }
+ else
+ {
+ MessageBox.Show("Invalid CIN"); return;
+ }
+
+ MessageBoxResult result = MessageBox.Show("Add SIID " + ssid + " Link with Frame " + id + " CIN " + cin + " ?", "Add VX Linkage", MessageBoxButton.YesNo);
+ if (result == MessageBoxResult.Yes)
+ {
+ if (database != null)
+ {
+ VXData v = new VXData(ssid, (byte)id, (byte)cin);
+ if (database.AddVXData(v))
+ {
+ VXTable.ItemsSource = database.GetVXDatas();
+ }
+ else MessageBox.Show("Failed to add to database");
+ }
+ else MessageBox.Show("Database is null");
+ }
+ }
+
+ private void btnDelVX_Click(object sender, RoutedEventArgs e)
+ {
+ String ssid = "";
+ int id = 0;
+ int cin = 0;
+ if (VXSIIDComboBox.SelectedItem != null)
+ {
+ ssid = (string)VXSIIDComboBox.SelectedItem;
+ }
+ else
+ {
+ MessageBox.Show("Invalid SIID"); return;
+ }
+ if (VXFrame.SelectedItem != null)
+ {
+ id = (int)VXFrame.SelectedItem;
+ }
+ else
+ {
+ MessageBox.Show("Invalid ID"); return;
+ }
+ if (VXCIN.SelectedItem != null)
+ {
+ cin = (int)VXCIN.SelectedItem;
+ }
+ else
+ {
+ MessageBox.Show("Invalid CIN"); return;
+ }
+ MessageBoxResult result = MessageBox.Show("Delete SIID " + ssid + " Link with Frame " + id + " CIN " + cin + " ?", "Delete VX Linkage", MessageBoxButton.YesNo);
+ if (result == MessageBoxResult.Yes)
+ {
+ if (database != null)
+ {
+ if (database.RemoveVXDatabySIID(ssid))
+ {
+ VXTable.ItemsSource = database.GetVXDatas();
+ }
+ else MessageBox.Show("Failed to delete from database");
+ }
+ else MessageBox.Show("Database is null");
+ }
+ }
+
+ private void btnClearVX_Click(object sender, RoutedEventArgs e)
+ {
+ MessageBoxResult result = MessageBox.Show("Clear all VX Linkage ?", "Clear VX Linkage", MessageBoxButton.YesNo);
+ if (result == MessageBoxResult.Yes)
+ {
+ if (database != null)
+ {
+ if (database.ClearVXTable())
+ {
+ VXTable.ItemsSource = database.GetVXDatas();
+ }
+ else MessageBox.Show("Failed to clear database");
+ }
+ else MessageBox.Show("Database is null");
+ }
+ }
+
+ private void FSMTable_AutoGeneratingColumn(object sender, DataGridAutoGeneratingColumnEventArgs e)
+ {
+ Debug.WriteLine("FSMTable_AutoGeneratingColumn : " + e.PropertyName);
+ switch (e.PropertyName)
+ {
+ case "SIID":
+ e.Column.Width = new DataGridLength(0, DataGridLengthUnitType.Auto);
+ break;
+ case "Enable":
+ e.Column.Width = new DataGridLength(0, DataGridLengthUnitType.Auto);
+ break;
+ case "Description":
+ e.Column.Width = new DataGridLength(1, DataGridLengthUnitType.Star);
+ break;
+ case "Value":
+ e.Column.Width = new DataGridLength(0, DataGridLengthUnitType.Auto);
+ break;
+ case "LastUpdate":
+ e.Column.Width = new DataGridLength(0, DataGridLengthUnitType.Auto);
+ break;
+ }
+ }
+
+ private void ModbusTable_AutoGeneratingColumn(object sender, DataGridAutoGeneratingColumnEventArgs e)
+ {
+ Debug.WriteLine("ModbusTable_AutoGeneratingColumn : " + e.PropertyName);
+ switch (e.PropertyName)
+ {
+ case "SIID":
+ e.Column.Width = new DataGridLength(0, DataGridLengthUnitType.Auto);
+ break;
+ case "Register":
+ e.Column.Width = new DataGridLength(0, DataGridLengthUnitType.Auto);
+ break;
+ case "Description":
+ e.Column.Width = new DataGridLength(1, DataGridLengthUnitType.Star);
+ break;
+ case "Value":
+ e.Column.Width = new DataGridLength(0, DataGridLengthUnitType.Auto);
+ break;
+ case "LastUpdate":
+ e.Column.Width = new DataGridLength(0, DataGridLengthUnitType.Auto);
+ break;
+ }
+ }
+
+ private void VXTable_AutoGeneratingColumn(object sender, DataGridAutoGeneratingColumnEventArgs e)
+ {
+ Debug.WriteLine("VXTable_AutoGeneratingColumn : " + e.PropertyName);
+ switch (e.PropertyName)
+ {
+ case "SIID":
+ e.Column.Width = new DataGridLength(0, DataGridLengthUnitType.Auto);
+ break;
+ case "ID":
+ e.Column.Width = new DataGridLength(0, DataGridLengthUnitType.Auto);
+ break;
+ case "CIN":
+ e.Column.Width = new DataGridLength(0, DataGridLengthUnitType.Auto);
+ break;
+ case "Description":
+ e.Column.Width = new DataGridLength(1, DataGridLengthUnitType.Star);
+ break;
+ case "Value":
+ e.Column.Width = new DataGridLength(0, DataGridLengthUnitType.Auto);
+ break;
+ case "LastUpdate":
+ e.Column.Width = new DataGridLength(0, DataGridLengthUnitType.Auto);
+ break;
+ }
+ }
+ }
+
+ // event handler for VX3K
+ class VX3KEvent : EventInterface
+ {
+ private TextBlock statusbar;
+ public VX3KEvent(TextBlock statusbar)
+ {
+ this.statusbar = statusbar;
+ }
+ public void ConnectStatus(bool success, string message)
+ {
+ Debug.WriteLine("VX-3000 ConnectStatus: " + success + " " + message);
+ if (statusbar != null) statusbar.Background = success ? Brushes.Green : Brushes.Transparent;
+ }
+
+ public void StatisticUpdate(uint TXOK, uint RXOK, uint TXErr, uint RXerr, uint TXBytes, uint RXBytes)
+ {
+ if (statusbar != null) statusbar.Text = "VX-3000 : TXOK: " + TXOK + " RXOK: " + RXOK + " TXErr: " + TXErr + " RXErr: " + RXerr + " TXBytes: " + TXBytes + " RXBytes: " + RXBytes;
+ }
+
+ public void Log(string msg)
+ {
+ Debug.WriteLine("VX-3000 Log: " + msg);
+ }
+ }
+
+ class ModbusEvent : ModbusSlaveEvent
+ {
+ private TextBlock statusbar;
+ private ListBox connectedlist;
+ private Label connectedcount;
+ private ObservableCollection ModbusSlave;
+
+ public ModbusEvent(TextBlock statusbar, ListBox connectedlist, Label connectedcount)
+ {
+ this.statusbar = statusbar;
+ this.connectedlist = connectedlist;
+ this.connectedcount = connectedcount;
+ ModbusSlave = new ObservableCollection();
+ ModbusSlave.CollectionChanged += (sender, e) => refresh_connectedlist();
+ }
+
+ private void refresh_connectedlist()
+ {
+
+ connectedlist.Items.Clear();
+ foreach (ModbusClientRecord client in ModbusSlave)
+ {
+ TextBlock l = new TextBlock();
+ l.Width = connectedlist.Width;
+ l.Margin = new Thickness(5, 0, 5, 0);
+ l.TextWrapping = TextWrapping.Wrap;
+ UpdateLabel(l, client);
+
+ connectedlist.Items.Add(l);
+ }
+ if (connectedcount != null) connectedcount.Content = "Connected : " + connectedlist.Items.Count;
+ }
+
+ void ModbusSlaveEvent.Log(string msg)
+ {
+ Debug.WriteLine("Modbus Log: " + msg);
+ }
+
+ void ModbusSlaveEvent.NewConnection(ModbusClientRecord client)
+ {
+ ModbusSlave.Add(client);
+ }
+
+ void ModbusSlaveEvent.CloseConnection(ModbusClientRecord client)
+ {
+ ModbusSlave.Remove(client);
+ }
+
+ void ModbusSlaveEvent.ConnectStatus(bool success, string message)
+ {
+ Debug.WriteLine("Modbus ConnectStatus: " + success + " " + message);
+ if (statusbar != null) statusbar.Background = success ? Brushes.Green : Brushes.Transparent;
+ }
+
+ public void TXRXStatusUpdate(ModbusClientRecord client)
+ {
+ foreach (var item in connectedlist.Items)
+ {
+ TextBlock l = (TextBlock)item;
+ if (l.Tag == client)
+ {
+ UpdateLabel(l, client);
+ return;
+ }
+ }
+ }
+
+ private void UpdateLabel(TextBlock l, ModbusClientRecord client)
+ {
+
+ l.Text = client.remoteEP + " TX: " + client.TXBytes + " RX: " + client.RXBytes + "TXOK: " + client.TXResponse + " RXOK: " + client.RXValidRequest;
+
+ }
+ }
+
+ class FSMEvent : EventInterface
+ {
+ private TextBlock statusbar;
+ public FSMEvent(TextBlock statusbar)
+ {
+ this.statusbar = statusbar;
+ }
+ public void ConnectStatus(bool success, string message)
+ {
+ if (statusbar != null)
+ {
+ statusbar.Dispatcher.Invoke(() =>
+ {
+ statusbar.Background = success ? Brushes.Green : Brushes.Transparent;
+ statusbar.Text = "FSM : " + message;
+ });
+
+ }
+ Debug.WriteLine("FSM ConnectStatus: " + success + " " + message);
+ }
+
+ public void Log(string msg)
+ {
+ Debug.WriteLine("FSM Log: " + msg);
+ }
+
+ public void StatisticUpdate(uint TXOK, uint RXOK, uint TXErr, uint RXerr, uint TXBytes, uint RXBytes)
+ {
+ if (statusbar != null)
+ {
+ statusbar.Dispatcher.Invoke(() =>
+
+ {
+ statusbar.Text = "FSM : TXOK: " + TXOK + " RXOK: " + RXOK + " TXErr: " + TXErr + " RXErr: " + RXerr + " TXBytes: " + TXBytes + " RXBytes: " + RXBytes;
+ });
+
+
+ }
+ }
+ }
+
+ // Class ini untuk update Table FSM
+ class FSMTableUpdater : FSMResultInterface
+ {
+ ObservableCollection data;
+ ListBox listbox;
+ Label countlabel;
+ int count = 0;
+ public FSMTableUpdater(ObservableCollection data, ListBox listbox, Label countlabel)
+ {
+ this.data = data;
+ this.listbox = listbox;
+ this.countlabel = countlabel;
+ }
+
+ public void DiscoveredSIID(string SIID, NodeData type)
+ {
+ Debug.WriteLine("Discovered SIID : " + SIID + " Type : " + type);
+ count++;
+ countlabel.Dispatcher.Invoke(() => countlabel.Content = "Count : " + count);
+ listbox.Dispatcher.Invoke(() => listbox.Items.Add(new Label() { Content = SIID + " : " + type.ToString() }));
+ }
+
+
+ public void NewState(string SIID, NodeState previous, NodeState current)
+ {
+ Debug.WriteLine("New State : " + SIID + " Previous : " + previous + " Current : " + current);
+
+ //FSMData dd = data.First(d => d.SIID == SIID);
+ //if (dd != null)
+ //{
+ // dd.LastUpdate = DateTime.Now.ToString();
+ // dd.Value = current.LogicalState ?? "Unknown";
+ //}
+
+ }
+
+ }
+
+ // Class ini untuk Update Modbus Register dari FSM Update
+ class ModbusTriggerFromFSM : FSMResultInterface
+ {
+ ObservableCollection source;
+ ObservableCollection data;
+ ModbusSlave slave;
+ public ModbusTriggerFromFSM(ObservableCollection source, ObservableCollection data, ModbusSlave slave)
+ {
+ this.source = source;
+ this.data = data;
+ this.slave = slave;
+ }
+ public void DiscoveredSIID(string SIID, NodeData type)
+ {
+
+ }
+
+
+ public void NewState(string SIID, NodeState previous, NodeState current)
+ {
+ //FSMData src = source.First(d => d.SIID == SIID);
+ //ModbusData dt = data.First(d => d.SIID == SIID);
+ //if (src != null && dt != null)
+ //{
+ // if (src.Enable)
+ // {
+ // if (dt.Register >= 0)
+ // {
+ // dt.LastUpdate = DateTime.Now.ToString();
+ // dt.Value = current.LogicalState ?? "Unknown";
+ // if (slave != null)
+ // {
+ // switch (current.LogicalState)
+ // {
+ // case "ON":
+ // slave.SetRegister(dt.Register, 1);
+ // break;
+ // case "NORMAL":
+ // case "OFF":
+ // slave.SetRegister(dt.Register, 0);
+ // break;
+ // }
+ // }
+ // }
+
+ // }
+ //}
+ }
+ }
+
+ // Class ini untuk Trigger VX3K dari FSM Update
+ class VXTriggerFromFSM : FSMResultInterface
+ {
+ ObservableCollection data;
+ ObservableCollection source;
+ VX3K vx;
+ public VXTriggerFromFSM(ObservableCollection source, ObservableCollection data, VX3K vx)
+ {
+ this.source = source;
+ this.data = data;
+ this.vx = vx;
+ }
+ public void DiscoveredSIID(string SIID, NodeData type)
+ {
+
+ }
+
+
+ public void NewState(string SIID, NodeState previous, NodeState current)
+ {
+ //FSMData src = source.First(d => d.SIID == SIID);
+ //VXData dt = data.First(d => d.SIID == SIID);
+ //if (src != null && dt != null)
+ //{
+ // if (src.Enable)
+ // {
+ // dt.LastUpdate = DateTime.Now.ToString();
+ // dt.Value = current.LogicalState ?? "Unknown";
+ // if (vx != null && vx.IsConnected())
+ // {
+ // switch (current.LogicalState)
+ // {
+ // case "ON":
+ // vx.Virtual_Contact_Input(dt.FrameID, dt.CIN, true);
+ // break;
+ // case "NORMAL":
+ // case "OFF":
+ // vx.Virtual_Contact_Input(dt.FrameID, dt.CIN, false);
+ // break;
+ // }
+
+ // }
+
+ // }
+ //}
+ }
+ }
+}
diff --git a/MappingData.cs b/MappingData.cs
new file mode 100644
index 0000000..9138f0b
--- /dev/null
+++ b/MappingData.cs
@@ -0,0 +1,26 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace FAtoPA
+{
+ class MappingData
+ {
+ // key is SIID
+ public String SIID { get; set; }
+ // if enable, will trigger with Modbus Register
+ public Boolean enableModbus { get; set; }
+ // if SIID active, link with which Modbus Register
+ public UInt16 ModbusRegister { get; set; }
+ // if SIID active, modify which bit from Modbus Register
+ public UInt16 ModbusBit { get; set; }
+ // if enable, will trigger with Virtual Contact
+ public Boolean enableVirtualContactInput { get; set; }
+ // if SIID active, Link with Virtual Contact Input from which Frame ID
+ public Byte VXFrame { get; set; }
+ // if SIID active, trigger which Virtual Contact Input from Frame ID
+ public Byte VXCin { get; set; }
+ }
+}
diff --git a/ModbusSlave.cs b/ModbusSlave.cs
new file mode 100644
index 0000000..b93c40a
--- /dev/null
+++ b/ModbusSlave.cs
@@ -0,0 +1,545 @@
+using System;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.Linq;
+using System.Net.Sockets;
+using System.Runtime.CompilerServices;
+using System.Text;
+using System.Threading.Tasks;
+using System.Threading;
+using System.Windows.Input;
+
+namespace FAtoPA
+{
+ // Source : https://www.simplymodbus.ca/TCP.htm
+ // Source : https://www.modbustools.com/modbus.html
+
+ internal class ModbusSlave
+ {
+ public static bool Started = false;
+ TcpListener listener;
+ Dictionary clientmap;
+ UInt16[] Registers;
+ Boolean running = false;
+ public byte ID { get; set; } = 1;
+
+ private ModbusSlaveEvent _event;
+
+ ///
+ /// Modbus Slave is actually a server (TCPListener),
+ /// that contain data and can be read or written by
+ /// a Modbus Master (which actually a TCPClient)
+ ///
+ /// Maximum Register that can be read or written
+ public ModbusSlave(ModbusSlaveEvent callback, UInt16 maxregister)
+ {
+ _event = callback;
+ clientmap = new Dictionary();
+ Registers = new UInt16[maxregister];
+ }
+
+ ///
+ /// Get Register Count available in Modbus Slave
+ ///
+ /// Register Count
+ public UInt16 GetRegisterCount()
+ {
+ return (UInt16)Registers.Length;
+ }
+
+ ///
+ /// Set Register Value
+ ///
+ /// start from 0 until GetRegisterCount() - 1
+ /// between 0 until 65535
+ public void SetRegister(UInt16 address, UInt16 value)
+ {
+ if (address < Registers.Length)
+ {
+ Registers[address] = value;
+ }
+ }
+
+ ///
+ /// Get Register Value
+ ///
+ /// start from 0 until GetRegisterCount() - 1
+ /// between 0 until 65535
+ public UInt16 GetRegister(UInt16 address)
+ {
+ if (address < Registers.Length)
+ {
+ return Registers[address];
+ }
+ return 0;
+ }
+
+ ///
+ /// Set Bit Value in Register
+ ///
+ /// start from 0 until GetRegisterCount() -1
+ /// start from 0 until 15
+ /// true = ON, false = OFF
+ public void SetBit(UInt16 address, UInt16 bit, Boolean value)
+ {
+ if (address < Registers.Length)
+ {
+ UInt16 reg = Registers[address];
+ if (value)
+ {
+ reg |= (UInt16)(1 << bit);
+ }
+ else
+ {
+ reg &= (UInt16)~(1 << bit);
+ }
+ Registers[address] = reg;
+ }
+ }
+
+ ///
+ /// Get Bit Value in Register
+ ///
+ /// start from 0 until 65535
+ ///
+ ///
+ public Boolean GetBit(UInt16 address, UInt16 bit)
+ {
+ if (address < Registers.Length)
+ {
+ UInt16 reg = Registers[address];
+ return (reg & (1 << bit)) != 0;
+ }
+ return false;
+ }
+
+ ///
+ /// Start Modbus Slave
+ ///
+ /// Listening Port
+ /// true if success
+ public bool Start(UInt16 port)
+ {
+
+ try
+ {
+ TcpListener newlistener = new TcpListener(System.Net.IPAddress.Any, port);
+ newlistener.Start();
+
+ new Thread(()=> DoListening(newlistener, port)).Start();
+ if (_event != null) _event.ConnectStatus(true, "Modbus Listener Started at port "+port);
+ return true;
+ }
+ catch (Exception e)
+ {
+ if (_event!=null) _event.ConnectStatus(false, "Modbus Listener failed to start at port "+port+", Message : "+ e.Message);
+ }
+ return false;
+ }
+
+ ///
+ /// Stop Modbus Slave
+ ///
+ public void Stop()
+ {
+ running = false;
+ if (listener != null)
+ {
+ try
+ {
+
+ listener.Stop();
+ if (_event != null)
+ {
+ _event.ConnectStatus(true, "ModbusSlave: Stop Success");
+ }
+ } catch (Exception e)
+ {
+ if (_event != null)
+ {
+ _event.ConnectStatus(false, "ModbusSlave: Stop failed, exception: " + e.Message);
+ }
+ }
+ listener = null;
+ }
+ foreach (ModbusClientRecord client in clientmap.Values)
+ {
+ CloseConnection(client.Client);
+ if (_event != null)
+ {
+ _event.CloseConnection(client);
+ }
+ }
+
+ }
+
+ private void CloseConnection(TcpClient client)
+ {
+ if (client != null)
+ {
+ client.Close();
+ }
+ }
+
+ ///
+ /// Get Transaction ID from Modbus Command
+ /// TransactionID is 2 bytes at index [0,1]
+ ///
+ ///
+ /// Modbus Command
+ /// TransactionID or 0 if failed
+ private UInt16 GetTransactionID(byte[] cmd)
+ {
+ if (cmd.Length >= 2)
+ {
+ return (UInt16)((cmd[0] << 8) + cmd[1]);
+
+ }
+ return 0;
+ }
+
+ ///
+ /// Get Protocol ID from Modbus Command
+ /// ProtocolID is 2 bytes at index [2,3]
+ /// For TCP Modbus, ProtocolID is always 0
+ ///
+ /// Modbus Command
+ /// ProtocolID
+ private UInt16 GetProtocolID(byte[] cmd)
+ {
+ if (cmd.Length >= 4)
+ {
+ return (UInt16)((cmd[2] << 8) + cmd[3]);
+
+ }
+ return 0;
+ }
+
+ ///
+ /// Get Length from Modbus Command
+ /// Length is 2 bytes at index [4,5]
+ /// Length is Modbus Payload Length + 2 (Unit ID and Function Code)
+ ///
+ /// Modbus Command
+ /// Length or 0 if failed
+ private UInt16 GetLength(byte[] cmd)
+ {
+ if (cmd.Length >= 6)
+ {
+ return (UInt16)((cmd[4] << 8) + cmd[5]);
+
+ }
+ return 0;
+ }
+
+ ///
+ /// Get Unit ID from Modbus Command
+ /// UnitID is 1 byte at index [6]
+ /// For Modbus Slave, UnitID is always 1
+ ///
+ /// Modbus Command
+ /// Unit ID or 0 if failed
+ private Byte GetUnitID(byte[] cmd)
+ {
+ if (cmd.Length >= 7)
+ {
+ return cmd[6];
+
+ }
+ return 0;
+ }
+
+ ///
+ /// Get Function Code from Modbus Command
+ /// Function Code is 1 byte at index [7]
+ ///
+ ///
+ /// Modbus Command
+ /// Function Code or 0 if failed
+ private Byte GetFunctionCode(byte[] cmd)
+ {
+ if (cmd.Length >= 8)
+ {
+ return cmd[7];
+
+ }
+ return 0;
+ }
+
+ ///
+ /// Get Modbus Payload from Modbus Command
+ /// Modbus Payload begins at index 8
+ ///
+ /// Modbus Command
+ /// Modbus Payload or empty array if failed
+ private byte[] GetModbusPayload(byte[] cmd)
+ {
+ if (cmd.Length > 8)
+ {
+ byte[] data = new byte[cmd.Length - 8];
+ Array.Copy(cmd,8, data, 0, data.Length);
+ return data;
+ }
+ return Array.Empty();
+ }
+
+ async void DoListening(TcpListener listener, int port)
+ {
+ running = true;
+ if (_event != null) _event.Log("ModbusSlave: Start Accepting Connections on port " + port);
+ while (running)
+ {
+ TcpClient client = null;
+ String key = null;
+ try
+ {
+ client = await listener.AcceptTcpClientAsync();
+ key = client.Client.RemoteEndPoint?.ToString();
+ if (key == null)
+ {
+ CloseConnection(client);
+ client = null;
+ }
+
+ }
+ catch (Exception e)
+ {
+ if (_event != null) _event.Log("ModbusSlave: Exception " + e.Message);
+ }
+
+ if (key != null && client != null)
+ {
+ if (_event!=null) _event.Log("ModbusSlave: New Connection from " + key);
+ ModbusClientRecord prev = clientmap[key];
+
+ if (prev != null)
+ {
+ CloseConnection(prev.Client);
+ if (_event != null)
+ {
+ _event.CloseConnection(prev);
+ }
+ }
+
+ ModbusClientRecord newclient = new ModbusClientRecord(client);
+ clientmap[key] = newclient;
+ new Thread(() => DoCommunication(newclient)).Start();
+
+ }
+
+ }
+ if (_event!=null) _event.Log("ModbusSlave: Stop Accepting Connections on port " + port);
+ }
+
+ async void DoCommunication(ModbusClientRecord client)
+ {
+ // komunikasi di sini
+ String key = client.remoteEP ?? "";
+ if (_event != null)
+ {
+ _event.NewConnection(client);
+ _event.Log("ModbusSlave: Start Communication with " + key);
+ }
+ NetworkStream stream = client.GetStream();
+ while (running && stream != null)
+ {
+ // spare 12 bytes untuk protocol modbus Read Register :
+ // 2 bytes Transaction ID, index [0,1]
+ // 2 bytes Protocol ID (always 0) , index [2,3]
+ // 2 bytes Length , index [4,5]
+ // 1 byte Unit ID, index [6]
+ // 1 byte Function Code, index [7]
+ // (3 = Read Holding Register)
+ // 2 byte Register Address, index [8,9]
+ // 2 byte Register Count, index [10,11]
+ byte[] cmd = new byte[12];
+ try {
+ int readcount = await stream.ReadAsync(new byte[12], 0, 12);
+ if (readcount >= 12)
+ {
+ client.RXBytes += (uint)readcount;
+ UInt16 transactionID = GetTransactionID(cmd);
+ UInt16 protocolID = GetProtocolID(cmd);
+ UInt16 length = GetLength(cmd);
+ Byte unitID = GetUnitID(cmd);
+ Byte functionCode = GetFunctionCode(cmd);
+ byte[] payload = GetModbusPayload(cmd);
+
+ if (protocolID == 0 && length == payload.Length + 2 && unitID == 1)
+ {
+ if (functionCode == 3)
+ {
+ //Implement Read Register
+ UInt16 registerAddress = (UInt16)((payload[0] << 8) + payload[1]);
+ UInt16 registerCount = (UInt16)((payload[2] << 8) + payload[3]);
+ Boolean valid = (registerAddress + registerCount <= Registers.Length);
+ byte[] response;
+ if (valid)
+ {
+ response = new byte[9 + registerCount * 2];
+ }
+ else
+ {
+ // Register Address out of range
+ response = new byte[9];
+ }
+
+ // 2 bytes Transaction ID, index [0,1]
+ response[0] = (byte)(transactionID >> 8);
+ response[1] = (byte)(transactionID & 0xFF);
+ // 2 bytes Protocol ID (always 0) , index [2,3]
+ response[2] = 0;
+ response[3] = 0;
+ // 2 bytes Length , index [4,5]
+ if (valid)
+ {
+ UInt16 _length = (UInt16)(3 + registerCount * 2);
+ response[4] = (byte)(_length >> 8);
+ response[5] = (byte)(_length & 0xFF);
+ }
+ else
+ {
+ response[4] = 0;
+ response[5] = 3;
+ }
+ // Unit ID, index [6]
+ response[6] = unitID;
+ // Function Code, index [7]
+ response[7] = valid ? functionCode : (byte)(functionCode + 0x80);
+
+ // Number of Register or Exception Code, index [8]
+ if (valid)
+ {
+ response[8] = (byte)(registerCount * 2);
+ // Register values, 2 bytes each , start from index [9]
+ for (int i = 0; i < registerCount; i++)
+ {
+ UInt16 value = GetRegister((UInt16)(registerAddress + i));
+ response[9 + i * 2] = (byte)(value >> 8);
+ response[10 + i * 2] = (byte)(value & 0xFF);
+ }
+ client.RXValidRequest++;
+ }
+ else
+ {
+ // Exception Code 2 : Illegal Data Address
+ response[8] = 2;
+ client.RXInvalidRequest++;
+ }
+ // Send Response
+ await stream.WriteAsync(response, 0, response.Length);
+ client.TXBytes += (uint)response.Length;
+ client.TXResponse++;
+ }
+ else
+ {
+ client.RXInvalidRequest++;
+ // exception, invalid function code
+ if (_event != null) _event.Log("ModbusSlave: Invalid Function Code " + functionCode);
+ byte[] response = new byte[9];
+ // 2 bytes Transaction ID, index [0,1]
+ response[0] = (byte)(transactionID >> 8);
+ response[1] = (byte)(transactionID & 0xFF);
+ // 2 bytes Protocol ID (always 0) , index [2,3]
+ response[2] = (byte)(protocolID >> 8);
+ response[3] = (byte)(protocolID & 0xFF);
+ // 2 bytes Length , index [4,5]
+ response[4] = 0;
+ response[5] = 3;
+ // Unit ID, index [6]
+ response[6] = unitID;
+ // Function Code, index [7]
+ response[7] = (byte)(functionCode + 0x80);
+ // Exception Code 1 : Illegal Function
+ response[8] = 1;
+ await stream.WriteAsync(response, 0, response.Length);
+ client.TXBytes += (uint)response.Length;
+ client.TXResponse++;
+ }
+ }
+ else
+ {
+ client.RXInvalidRequest++;
+ // exception, invalid protocol ID, length, or unit ID
+ if (_event != null) _event.Log("ModbusSlave: Invalid Protocol ID, Length, or Unit ID");
+ byte[] response = new byte[9];
+ // 2 bytes Transaction ID, index [0,1]
+ response[0] = (byte)(transactionID >> 8);
+ response[1] = (byte)(transactionID & 0xFF);
+ // 2 bytes Protocol ID (always 0) , index [2,3]
+ response[2] = (byte)(protocolID >> 8);
+ response[3] = (byte)(protocolID & 0xFF);
+ // 2 bytes Length , index [4,5]
+ response[4] = 0;
+ response[5] = 3;
+ // Unit ID, index [6]
+ response[6] = unitID;
+ // Function Code, index [7]
+ response[7] = (byte)(functionCode + 0x80);
+ // Exception Code 3 : Illegal Query
+ response[8] = 3;
+ await stream.WriteAsync(response, 0, response.Length);
+ client.TXBytes += (uint)response.Length;
+ client.TXResponse++;
+ }
+ }
+ }
+ catch (Exception e)
+ {
+ if (_event!=null) _event.Log("ModbusSlave: Exception " + e.Message);
+ break;
+ }
+
+ }
+ if (_event != null) _event.Log("ModbusSlave: Stop Communication with " + key);
+ CloseConnection(client.Client);
+ clientmap.Remove(key);
+ if (_event != null) _event.CloseConnection(client);
+
+ }
+ }
+
+
+
+ class ModbusClientRecord
+ {
+ public TcpClient Client;
+ public UInt32 TXBytes, RXBytes;
+ public UInt32 RXValidRequest, RXInvalidRequest, TXResponse;
+ public String remoteEP;
+ public ModbusClientRecord(TcpClient client)
+ {
+ Client = client;
+ remoteEP = "";
+ TXBytes = 0;
+ RXBytes = 0;
+ RXValidRequest = 0;
+ RXInvalidRequest = 0;
+ TXResponse = 0;
+ if (Client != null)
+ {
+ if (Client.Connected)
+ {
+ if (Client.Client.RemoteEndPoint != null)
+ {
+ remoteEP = Client.Client.RemoteEndPoint.ToString();
+ }
+ }
+ }
+ }
+
+ public NetworkStream GetStream()
+ {
+ return Client?.GetStream() ?? null;
+ }
+ }
+
+ interface ModbusSlaveEvent
+ {
+ void Log(String msg);
+ void NewConnection(ModbusClientRecord client);
+ void CloseConnection(ModbusClientRecord client);
+ void ConnectStatus(bool success, string message);
+ void TXRXStatusUpdate(ModbusClientRecord client);
+ }
+}
diff --git a/NodeData.cs b/NodeData.cs
new file mode 100644
index 0000000..05a34a1
--- /dev/null
+++ b/NodeData.cs
@@ -0,0 +1,41 @@
+using FSM5000FSIAPI.Version3;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace FAtoPA
+{
+ class NodeData
+ {
+ public SIID SIID { get; set; }
+ public int Type { get; set; }
+ public String Label { get; set; }
+ public String Description { get; set; }
+ public NodeState State { get; set; }
+ public NodeData() { }
+ }
+
+ class NodeState
+ {
+ public DateTime TimeStamp { get; set; }
+ public String AdminState { get; set; }
+ public String LogicalState { get; set; }
+ public String CompoundState { get; set; }
+ public String StateOfHandling { get; set; }
+ public NodeState() { }
+ public NodeState(ItemState itemState)
+ {
+ if (itemState != null)
+ {
+ this.TimeStamp = itemState.TimeStamp;
+ this.AdminState = itemState.AdminState.ToString();
+ this.LogicalState = itemState.LogicalState.ToString();
+ this.StateOfHandling = itemState.StateOfHandling.ToString();
+ this.CompoundState = itemState.CompoundState.ToString();
+
+ }
+ }
+ }
+}
diff --git a/Properties/AssemblyInfo.cs b/Properties/AssemblyInfo.cs
new file mode 100644
index 0000000..99c410c
--- /dev/null
+++ b/Properties/AssemblyInfo.cs
@@ -0,0 +1,52 @@
+using System.Reflection;
+using System.Resources;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+using System.Windows;
+
+// General Information about an assembly is controlled through the following
+// set of attributes. Change these attribute values to modify the information
+// associated with an assembly.
+[assembly: AssemblyTitle("FAtoPA.Net")]
+[assembly: AssemblyDescription("")]
+[assembly: AssemblyConfiguration("")]
+[assembly: AssemblyCompany("")]
+[assembly: AssemblyProduct("FAtoPA.Net")]
+[assembly: AssemblyCopyright("Copyright © 2024")]
+[assembly: AssemblyTrademark("")]
+[assembly: AssemblyCulture("")]
+
+// Setting ComVisible to false makes the types in this assembly not visible
+// to COM components. If you need to access a type in this assembly from
+// COM, set the ComVisible attribute to true on that type.
+[assembly: ComVisible(false)]
+
+//In order to begin building localizable applications, set
+//CultureYouAreCodingWith in your .csproj file
+//inside a . For example, if you are using US english
+//in your source files, set the to en-US. Then uncomment
+//the NeutralResourceLanguage attribute below. Update the "en-US" in
+//the line below to match the UICulture setting in the project file.
+
+//[assembly: NeutralResourcesLanguage("en-US", UltimateResourceFallbackLocation.Satellite)]
+
+
+[assembly: ThemeInfo(
+ ResourceDictionaryLocation.None, //where theme specific resource dictionaries are located
+ //(used if a resource is not found in the page,
+ // or application resource dictionaries)
+ ResourceDictionaryLocation.SourceAssembly //where the generic resource dictionary is located
+ //(used if a resource is not found in the page,
+ // app, or any theme specific resource dictionaries)
+)]
+
+
+// Version information for an assembly consists of the following four values:
+//
+// Major Version
+// Minor Version
+// Build Number
+// Revision
+//
+[assembly: AssemblyVersion("1.0.0.0")]
+[assembly: AssemblyFileVersion("1.0.0.0")]
diff --git a/Properties/Resources.Designer.cs b/Properties/Resources.Designer.cs
new file mode 100644
index 0000000..c1ef761
--- /dev/null
+++ b/Properties/Resources.Designer.cs
@@ -0,0 +1,71 @@
+//------------------------------------------------------------------------------
+//
+// This code was generated by a tool.
+// Runtime Version:4.0.30319.42000
+//
+// Changes to this file may cause incorrect behavior and will be lost if
+// the code is regenerated.
+//
+//------------------------------------------------------------------------------
+
+namespace FAtoPA.Net.Properties
+{
+
+
+ ///
+ /// A strongly-typed resource class, for looking up localized strings, etc.
+ ///
+ // This class was auto-generated by the StronglyTypedResourceBuilder
+ // class via a tool like ResGen or Visual Studio.
+ // To add or remove a member, edit your .ResX file then rerun ResGen
+ // with the /str option, or rebuild your VS project.
+ [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")]
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
+ [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
+ internal class Resources
+ {
+
+ private static global::System.Resources.ResourceManager resourceMan;
+
+ private static global::System.Globalization.CultureInfo resourceCulture;
+
+ [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
+ internal Resources()
+ {
+ }
+
+ ///
+ /// Returns the cached ResourceManager instance used by this class.
+ ///
+ [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
+ internal static global::System.Resources.ResourceManager ResourceManager
+ {
+ get
+ {
+ if ((resourceMan == null))
+ {
+ global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("FAtoPA.Net.Properties.Resources", typeof(Resources).Assembly);
+ resourceMan = temp;
+ }
+ return resourceMan;
+ }
+ }
+
+ ///
+ /// Overrides the current thread's CurrentUICulture property for all
+ /// resource lookups using this strongly typed resource class.
+ ///
+ [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
+ internal static global::System.Globalization.CultureInfo Culture
+ {
+ get
+ {
+ return resourceCulture;
+ }
+ set
+ {
+ resourceCulture = value;
+ }
+ }
+ }
+}
diff --git a/Properties/Resources.resx b/Properties/Resources.resx
new file mode 100644
index 0000000..af7dbeb
--- /dev/null
+++ b/Properties/Resources.resx
@@ -0,0 +1,117 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ text/microsoft-resx
+
+
+ 2.0
+
+
+ System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
+
+
+ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
+
+
\ No newline at end of file
diff --git a/Properties/Settings.Designer.cs b/Properties/Settings.Designer.cs
new file mode 100644
index 0000000..e164b8c
--- /dev/null
+++ b/Properties/Settings.Designer.cs
@@ -0,0 +1,30 @@
+//------------------------------------------------------------------------------
+//
+// This code was generated by a tool.
+// Runtime Version:4.0.30319.42000
+//
+// Changes to this file may cause incorrect behavior and will be lost if
+// the code is regenerated.
+//
+//------------------------------------------------------------------------------
+
+namespace FAtoPA.Net.Properties
+{
+
+
+ [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
+ [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "11.0.0.0")]
+ internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase
+ {
+
+ private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings())));
+
+ public static Settings Default
+ {
+ get
+ {
+ return defaultInstance;
+ }
+ }
+ }
+}
diff --git a/Properties/Settings.settings b/Properties/Settings.settings
new file mode 100644
index 0000000..033d7a5
--- /dev/null
+++ b/Properties/Settings.settings
@@ -0,0 +1,7 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/VX3K.cs b/VX3K.cs
new file mode 100644
index 0000000..ff6a3d2
--- /dev/null
+++ b/VX3K.cs
@@ -0,0 +1,361 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Net.Sockets;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace FAtoPA
+{
+ class VX3K
+ {
+ public static bool Started = false;
+ private String targetip = "192.168.14.1";
+ private UInt16 targetport = 5000;
+ private TcpClient socket;
+
+ // Statistics
+ public uint TXBytes { get; private set; } = 0;
+ public uint RXBytes { get; private set; } = 0;
+ public uint TXOK { get; private set; } = 0;
+ public uint RXOK { get; private set; } = 0;
+ public uint TXErr { get; private set; } = 0;
+ public uint RXErr { get; private set; } = 0;
+
+ private readonly EventInterface _event;
+
+ public VX3K(EventInterface callback){
+ this._event = callback;
+ }
+
+ ///
+ /// Connect to VX-3000
+ ///
+ /// target IP address, default to 192.168.14.1
+ /// target port, default to 5000
+ public Boolean Connect(String ipaddress, UInt16 port)
+ {
+ TXBytes = 0;
+ RXBytes = 0;
+ TXOK = 0;
+ RXOK = 0;
+ TXErr = 0;
+ RXErr = 0;
+
+ raise_statistic();
+
+ if (ipaddress!=null && ipaddress.Length>0)
+ {
+ targetip=ipaddress;
+ raise_log("Target IP changed to " + targetip);
+ }
+ if (port>0 && port<65535)
+ {
+ targetport=port;
+ raise_log("Target Port changed to " + targetport);
+ }
+
+ try
+ {
+ TcpClient newclient = new TcpClient();
+ bool success = newclient.ConnectAsync(targetip, targetport).Wait(1000);
+ if (success && newclient.Connected)
+ {
+ socket = newclient;
+ raise_connect_status(true, "Connected to " + targetip + ":" + targetport);
+ return true;
+ }
+ else raise_connect_status(false, "Failed to connect to " + targetip + ":" + targetport);
+
+ } catch(Exception e)
+ {
+ raise_connect_status(false, "Error: " + e.Message);
+
+ }
+ return false;
+ }
+
+ ///
+ /// Check if connected to VX-3000
+ ///
+ /// true if connected
+ public Boolean IsConnected()
+ {
+ if (socket != null)
+ {
+ return socket.Connected;
+ }
+ return false;
+ }
+
+ ///
+ /// Disconnect from VX-3000
+ ///
+ public void Disconnect()
+ {
+ if (socket != null)
+ {
+ socket.Close();
+ socket = null;
+ raise_connect_status(false, "Disconnected from " + targetip + ":" + targetport);
+ }
+ else raise_connect_status(false, "Not connected yet");
+
+ }
+
+ ///
+ /// Set Virtual Contact Input ON/OFF
+ /// Contact Input ID :
+ /// 0 - 15 : Contact Input 1 - 16
+ /// 16 : Emergency Contact input 1
+ /// 17 : Emergency Contact input 2
+ ///
+ /// VX Frame ID
+ /// Contact Input ID
+ /// true = ON, false = OFF
+ ///
+ public Boolean Virtual_Contact_Input(UInt16 vxID, UInt16 CinID, Boolean isON)
+ {
+ byte[] payload = new byte[6];
+ payload[0] = HighByte(vxID);
+ payload[1] = LowByte(vxID);
+ payload[2] = HighByte(CinID);
+ payload[3] = LowByte(CinID);
+ payload[4] = 0x00;
+ payload[5] = (byte)(isON ? 0x01 : 0x00);
+
+ byte[] cmd = make_header_request_command(0x1001, payload);
+
+ if (SendCommand(cmd))
+ {
+ byte[] response = ReadResponse();
+ if (GetCommandCode(response) == 0x1001)
+ {
+ if (GetResponseCode(response) == 0x0000)
+ {
+ raise_log("Virtual Contact Input Command Success");
+ return true;
+ } else raise_log("Virtual Contact Input Command Failed, Invalid Response Code");
+ } else raise_log("Virtual Contact Input Command Failed, Invalid Command Code");
+ }
+ return false;
+ }
+
+ ///
+ /// Sending command to VX-3000
+ ///
+ /// Array of bytes containing command
+ /// true if success
+ private Boolean SendCommand(byte[] cmd)
+ {
+ if (IsConnected() && socket != null)
+ {
+ if (cmd != null && cmd.Length > 0)
+ {
+ try
+ {
+ NetworkStream stream = socket.GetStream();
+ stream.Write(cmd, 0, cmd.Length);
+ TXBytes += (uint)cmd.Length;
+ TXOK++;
+ raise_statistic();
+ return true;
+ }
+ catch (Exception e)
+ {
+ raise_log("SendCommand failed, Error : "+e);
+ }
+ }
+ else raise_log("SendCommand failed, Invalid Command");
+ }
+ else raise_log("SendCommand failed, Not Connected to VX-3000");
+
+ TXErr++;
+ raise_statistic();
+ return false;
+ }
+
+ ///
+ /// Reading response from VX-3000
+ ///
+ /// array of bytes containing response , or empty array if failed
+ private byte[] ReadResponse()
+ {
+ if (IsConnected() && socket != null)
+ {
+ try
+ {
+ NetworkStream stream = socket.GetStream();
+ byte[] response = new byte[1500];
+ int bytes = stream.Read(response, 0, response.Length);
+ RXBytes += (uint)bytes;
+ RXOK++;
+ raise_statistic();
+ byte[] result = new byte[bytes];
+ Array.Copy(response, result, bytes);
+ return result;
+ }
+ catch (Exception e)
+ {
+ raise_log("ReadResponse failed, Error : " + e);
+ }
+ }
+ else raise_log("ReadResponse failed, Not Connected to VX-3000");
+ RXErr++;
+ raise_statistic();
+ return Array.Empty();
+ }
+
+ ///
+ /// Create VX-3000 command bytes
+ ///
+ /// command code
+ /// payload to send
+ /// array of bytes
+ private byte[] make_header_request_command(UInt16 command, byte[] payload)
+ {
+ UInt16 cmdlen = 8;
+ if (payload != null && payload.Length > 0) cmdlen += (UInt16)payload.Length;
+
+ byte[] header = new byte[cmdlen];
+ header[0] = HighByte(command);
+ header[1] = LowByte(command);
+ header[2] = 0x00;
+ header[3] = 0x00;
+ header[4] = HighByte(cmdlen);
+ header[5] = LowByte(cmdlen);
+ header[6] = 0x80; // request to VX3000
+ header[7] = 0x00;
+
+ if (payload == null) return header; // no payload
+ if (payload.Length == 0) return header; // no payload
+
+ for(int i=0; i
+ /// Return High Byte of a 16-bit value
+ ///
+ /// 16 bit value
+ /// Byte value
+ private byte HighByte(UInt16 value)
+ {
+ return (byte)((value >> 8) & 0xFF);
+ }
+
+ ///
+ /// Return Low Byte of a 16-bit value
+ ///
+ /// 16 bit value
+ /// Byte value
+ private byte LowByte(UInt16 value)
+ {
+ return (byte)(value & 0xFF);
+ }
+
+ ///
+ /// Get Command Code from response
+ /// Command Code value is Big-Endian UInt16 from index 0 and 1
+ ///
+ /// array of byte from Response
+ /// Command Code or 0 if failed
+ private UInt16 GetCommandCode(byte[] value)
+ {
+ if (value != null && value.Length >= 2)
+ {
+ return (UInt16)((value[0] << 8) | value[1]);
+ }
+ return 0;
+ }
+
+ ///
+ /// Get Response Code from response
+ /// Response Code value is Big-Endian UInt16 from index 2 and 3
+ ///
+ /// array of byte from Response
+ /// Response Code or 0 if failed
+ private UInt16 GetResponseCode(byte[] value)
+ {
+ if (value != null && value.Length >= 4)
+ {
+ return (UInt16)((value[2] << 8) | value[3]);
+ }
+ return 0;
+ }
+
+ ///
+ /// Get Command Length from response
+ /// Command Length value is Big-Endian UInt16 from index 4 and 5
+ ///
+ /// array of byte from Response
+ /// Command Length or 0 if failed
+ private UInt16 GetCommandLength(byte[] value)
+ {
+ if (value != null && value.Length >= 6)
+ {
+ return (UInt16)((value[4] << 8) | value[5]);
+ }
+ return 0;
+ }
+
+ ///
+ /// Get Command Flag from response
+ /// Command Flag value is Byte at index 6
+ ///
+ /// array of byte from Response
+ /// Command Flag or 0 if failed
+ private byte GetCommandFlag(byte[] value)
+ {
+ if (value != null && value.Length >= 7)
+ {
+ return value[6];
+ }
+ return 0;
+ }
+
+ ///
+ /// Get Payload from Response
+ /// Payload is located at index 8 to end of array
+ ///
+ /// array of byte from Response
+ /// Payload bytes or empty array if failed
+ private byte[] GetPayload(byte[] value)
+ {
+ if (value != null && value.Length > 8)
+ {
+ if ((GetCommandFlag(value) & 0x40) != 0)
+ {
+ byte[] payload = new byte[value.Length - 8];
+ Array.Copy(value, 8, payload, 0, payload.Length);
+ return payload;
+
+ }
+ }
+ return Array.Empty();
+ }
+
+ private void raise_log(String msg)
+ {
+ if (_event!=null) _event.Log(msg);
+ }
+
+ private void raise_statistic()
+ {
+ if (_event != null) _event.StatisticUpdate(TXOK,RXOK, TXErr,RXErr,TXBytes,RXBytes);
+ }
+
+ private void raise_connect_status(bool success, String message)
+ {
+ if (_event != null) _event.ConnectStatus(success, message);
+ }
+ }
+
+
+
+}
diff --git a/packages.config b/packages.config
new file mode 100644
index 0000000..01375b4
--- /dev/null
+++ b/packages.config
@@ -0,0 +1,19 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file