Add project files.

This commit is contained in:
2024-11-21 13:51:18 +07:00
parent 6997700fbb
commit 84885e5047
22 changed files with 4152 additions and 0 deletions

22
App.config Normal file
View File

@@ -0,0 +1,22 @@
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<startup>
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.8" />
</startup>
<runtime>
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
<dependentAssembly>
<assemblyIdentity name="System.Runtime.CompilerServices.Unsafe" publicKeyToken="b03f5f7f11d50a3a" culture="neutral" />
<bindingRedirect oldVersion="0.0.0.0-6.0.0.0" newVersion="6.0.0.0" />
</dependentAssembly>
<dependentAssembly>
<assemblyIdentity name="System.Memory" publicKeyToken="cc7b13ffcd2ddd51" culture="neutral" />
<bindingRedirect oldVersion="0.0.0.0-4.0.1.2" newVersion="4.0.1.2" />
</dependentAssembly>
<dependentAssembly>
<assemblyIdentity name="System.ValueTuple" publicKeyToken="cc7b13ffcd2ddd51" culture="neutral" />
<bindingRedirect oldVersion="0.0.0.0-4.0.3.0" newVersion="4.0.3.0" />
</dependentAssembly>
</assemblyBinding>
</runtime>
</configuration>

9
App.xaml Normal file
View File

@@ -0,0 +1,9 @@
<Application x:Class="FAtoPA.Net.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:FAtoPA.Net"
StartupUri="MainWindow.xaml">
<Application.Resources>
</Application.Resources>
</Application>

17
App.xaml.cs Normal file
View File

@@ -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
{
/// <summary>
/// Interaction logic for App.xaml
/// </summary>
public partial class App : Application
{
}
}

112
Config.cs Normal file
View File

@@ -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<Config>(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");
}
}
}

475
Database.cs Normal file
View File

@@ -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";
/// <summary>
/// Create Database Object
/// </summary>
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<FSMData> GetFSMDatas()
{
List<FSMData> fsmDatas = new List<FSMData>();
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<ModbusData> GetModbusDatas()
{
List<ModbusData> modbusDatas = new List<ModbusData>();
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<UInt16>(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<VXData> GetVXDatas()
{
List<VXData> vxDatas = new List<VXData>();
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 = "";
}
}
}

15
EventInterface.cs Normal file
View File

@@ -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);
}
}

207
FAtoPA.Net.csproj Normal file
View File

@@ -0,0 +1,207 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
<ProjectGuid>{C158634C-A54E-41CD-A41A-6D0AFF841BE4}</ProjectGuid>
<OutputType>WinExe</OutputType>
<RootNamespace>FAtoPA.Net</RootNamespace>
<AssemblyName>FAtoPA.Net</AssemblyName>
<TargetFrameworkVersion>v4.8</TargetFrameworkVersion>
<FileAlignment>512</FileAlignment>
<ProjectTypeGuids>{60dc8134-eba5-43b8-bcc9-bb4bc16c2548};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}</ProjectTypeGuids>
<WarningLevel>4</WarningLevel>
<AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects>
<Deterministic>true</Deterministic>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<PlatformTarget>AnyCPU</PlatformTarget>
<DebugSymbols>true</DebugSymbols>
<DebugType>full</DebugType>
<Optimize>false</Optimize>
<OutputPath>bin\Debug\</OutputPath>
<DefineConstants>DEBUG;TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<PlatformTarget>AnyCPU</PlatformTarget>
<DebugType>pdbonly</DebugType>
<Optimize>true</Optimize>
<OutputPath>bin\Release\</OutputPath>
<DefineConstants>TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Debug|x64'">
<DebugSymbols>true</DebugSymbols>
<OutputPath>bin\x64\Debug\</OutputPath>
<DefineConstants>DEBUG;TRACE</DefineConstants>
<DebugType>full</DebugType>
<PlatformTarget>x64</PlatformTarget>
<LangVersion>7.3</LangVersion>
<ErrorReport>prompt</ErrorReport>
<Prefer32Bit>true</Prefer32Bit>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Release|x64'">
<OutputPath>bin\x64\Release\</OutputPath>
<DefineConstants>TRACE</DefineConstants>
<Optimize>true</Optimize>
<DebugType>pdbonly</DebugType>
<PlatformTarget>x64</PlatformTarget>
<LangVersion>7.3</LangVersion>
<ErrorReport>prompt</ErrorReport>
<Prefer32Bit>true</Prefer32Bit>
</PropertyGroup>
<ItemGroup>
<Reference Include="ApplicationProtocolCIL">
<HintPath>..\..\..\Bosch FA\FSM5000FSI-2.0.21\Release_x64\ApplicationProtocolCIL.dll</HintPath>
</Reference>
<Reference Include="Backend">
<HintPath>..\..\..\Bosch FA\FSM5000FSI-2.0.21\Release_x64\Backend.dll</HintPath>
</Reference>
<Reference Include="DataExchangeInterfaces">
<HintPath>..\..\..\Bosch FA\FSM5000FSI-2.0.21\Release_x64\DataExchangeInterfaces.dll</HintPath>
</Reference>
<Reference Include="FSIConfig">
<HintPath>..\..\..\Bosch FA\FSM5000FSI-2.0.21\Release_x64\FSIConfig.dll</HintPath>
</Reference>
<Reference Include="FSITrace">
<HintPath>..\..\..\Bosch FA\FSM5000FSI-2.0.21\Release_x64\FSITrace.dll</HintPath>
</Reference>
<Reference Include="FSM5000FSI">
<HintPath>..\..\..\Bosch FA\FSM5000FSI-2.0.21\Release_x64\FSM5000FSI.dll</HintPath>
</Reference>
<Reference Include="FSM5000FSIAPI">
<HintPath>..\..\..\Bosch FA\FSM5000FSI-2.0.21\Release_x64\FSM5000FSIAPI.dll</HintPath>
</Reference>
<Reference Include="FSM5000FSILoader">
<HintPath>..\..\..\Bosch FA\FSM5000FSI-2.0.21\Release_x64\FSM5000FSILoader.dll</HintPath>
</Reference>
<Reference Include="Microsoft.Bcl.AsyncInterfaces, Version=9.0.0.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51, processorArchitecture=MSIL">
<HintPath>packages\Microsoft.Bcl.AsyncInterfaces.9.0.0\lib\net462\Microsoft.Bcl.AsyncInterfaces.dll</HintPath>
</Reference>
<Reference Include="Microsoft.Data.Sqlite, Version=9.0.0.0, Culture=neutral, PublicKeyToken=adb9793829ddae60, processorArchitecture=MSIL">
<HintPath>packages\Microsoft.Data.Sqlite.Core.9.0.0\lib\netstandard2.0\Microsoft.Data.Sqlite.dll</HintPath>
</Reference>
<Reference Include="MPNetCIL">
<HintPath>..\..\..\Bosch FA\FSM5000FSI-2.0.21\Release_x64\MPNetCIL.dll</HintPath>
</Reference>
<Reference Include="MPNetGate">
<HintPath>..\..\..\Bosch FA\FSM5000FSI-2.0.21\Release_x64\MPNetGate.dll</HintPath>
</Reference>
<Reference Include="Repository">
<HintPath>..\..\..\Bosch FA\FSM5000FSI-2.0.21\Release_x64\Repository.dll</HintPath>
</Reference>
<Reference Include="SQLitePCLRaw.batteries_v2, Version=2.1.10.2445, Culture=neutral, PublicKeyToken=8226ea5df37bcae9, processorArchitecture=MSIL">
<HintPath>packages\SQLitePCLRaw.bundle_e_sqlite3.2.1.10\lib\net461\SQLitePCLRaw.batteries_v2.dll</HintPath>
</Reference>
<Reference Include="SQLitePCLRaw.core, Version=2.1.10.2445, Culture=neutral, PublicKeyToken=1488e028ca7ab535, processorArchitecture=MSIL">
<HintPath>packages\SQLitePCLRaw.core.2.1.10\lib\netstandard2.0\SQLitePCLRaw.core.dll</HintPath>
</Reference>
<Reference Include="SQLitePCLRaw.provider.dynamic_cdecl, Version=2.1.10.2445, Culture=neutral, PublicKeyToken=b68184102cba0b3b, processorArchitecture=MSIL">
<HintPath>packages\SQLitePCLRaw.provider.dynamic_cdecl.2.1.10\lib\netstandard2.0\SQLitePCLRaw.provider.dynamic_cdecl.dll</HintPath>
</Reference>
<Reference Include="StandardCIL">
<HintPath>..\..\..\Bosch FA\FSM5000FSI-2.0.21\Release_x64\StandardCIL.dll</HintPath>
</Reference>
<Reference Include="System" />
<Reference Include="System.Buffers, Version=4.0.3.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51, processorArchitecture=MSIL">
<HintPath>packages\System.Buffers.4.5.1\lib\net461\System.Buffers.dll</HintPath>
</Reference>
<Reference Include="System.Data" />
<Reference Include="System.IO.Pipelines, Version=9.0.0.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51, processorArchitecture=MSIL">
<HintPath>packages\System.IO.Pipelines.9.0.0\lib\net462\System.IO.Pipelines.dll</HintPath>
</Reference>
<Reference Include="System.Memory, Version=4.0.1.2, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51, processorArchitecture=MSIL">
<HintPath>packages\System.Memory.4.5.5\lib\net461\System.Memory.dll</HintPath>
</Reference>
<Reference Include="System.Numerics" />
<Reference Include="System.Numerics.Vectors, Version=4.1.4.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
<HintPath>packages\System.Numerics.Vectors.4.5.0\lib\net46\System.Numerics.Vectors.dll</HintPath>
</Reference>
<Reference Include="System.Runtime.CompilerServices.Unsafe, Version=6.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
<HintPath>packages\System.Runtime.CompilerServices.Unsafe.6.0.0\lib\net461\System.Runtime.CompilerServices.Unsafe.dll</HintPath>
</Reference>
<Reference Include="System.Text.Encodings.Web, Version=9.0.0.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51, processorArchitecture=MSIL">
<HintPath>packages\System.Text.Encodings.Web.9.0.0\lib\net462\System.Text.Encodings.Web.dll</HintPath>
</Reference>
<Reference Include="System.Text.Json, Version=9.0.0.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51, processorArchitecture=MSIL">
<HintPath>packages\System.Text.Json.9.0.0\lib\net462\System.Text.Json.dll</HintPath>
</Reference>
<Reference Include="System.Threading.Tasks.Extensions, Version=4.2.0.1, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51, processorArchitecture=MSIL">
<HintPath>packages\System.Threading.Tasks.Extensions.4.5.4\lib\net461\System.Threading.Tasks.Extensions.dll</HintPath>
</Reference>
<Reference Include="System.ValueTuple, Version=4.0.3.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51, processorArchitecture=MSIL">
<HintPath>packages\System.ValueTuple.4.5.0\lib\net47\System.ValueTuple.dll</HintPath>
</Reference>
<Reference Include="System.Xml" />
<Reference Include="Microsoft.CSharp" />
<Reference Include="System.Core" />
<Reference Include="System.Xml.Linq" />
<Reference Include="System.Data.DataSetExtensions" />
<Reference Include="System.Net.Http" />
<Reference Include="System.Xaml">
<RequiredTargetFramework>4.0</RequiredTargetFramework>
</Reference>
<Reference Include="WindowsBase" />
<Reference Include="PresentationCore" />
<Reference Include="PresentationFramework" />
</ItemGroup>
<ItemGroup>
<ApplicationDefinition Include="App.xaml">
<Generator>MSBuild:Compile</Generator>
<SubType>Designer</SubType>
</ApplicationDefinition>
<Compile Include="VX3K.cs" />
<Page Include="MainWindow.xaml">
<Generator>MSBuild:Compile</Generator>
<SubType>Designer</SubType>
</Page>
<Compile Include="App.xaml.cs">
<DependentUpon>App.xaml</DependentUpon>
<SubType>Code</SubType>
</Compile>
<Compile Include="Config.cs" />
<Compile Include="Database.cs" />
<Compile Include="EventInterface.cs" />
<Compile Include="FSM.cs" />
<Compile Include="FSMResultInterface.cs" />
<Compile Include="MainWindow.xaml.cs">
<DependentUpon>MainWindow.xaml</DependentUpon>
<SubType>Code</SubType>
</Compile>
</ItemGroup>
<ItemGroup>
<Compile Include="MappingData.cs" />
<Compile Include="ModbusSlave.cs" />
<Compile Include="NodeData.cs" />
<Compile Include="Properties\AssemblyInfo.cs">
<SubType>Code</SubType>
</Compile>
<Compile Include="Properties\Resources.Designer.cs">
<AutoGen>True</AutoGen>
<DesignTime>True</DesignTime>
<DependentUpon>Resources.resx</DependentUpon>
</Compile>
<Compile Include="Properties\Settings.Designer.cs">
<AutoGen>True</AutoGen>
<DependentUpon>Settings.settings</DependentUpon>
<DesignTimeSharedInput>True</DesignTimeSharedInput>
</Compile>
<EmbeddedResource Include="Properties\Resources.resx">
<Generator>ResXFileCodeGenerator</Generator>
<LastGenOutput>Resources.Designer.cs</LastGenOutput>
</EmbeddedResource>
<None Include="packages.config" />
<None Include="Properties\Settings.settings">
<Generator>SettingsSingleFileGenerator</Generator>
<LastGenOutput>Settings.Designer.cs</LastGenOutput>
</None>
</ItemGroup>
<ItemGroup>
<None Include="App.config" />
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
</Project>

28
FAtoPA.Net.sln Normal file
View File

@@ -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

288
FSM.cs Normal file
View File

@@ -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<FSMResultInterface> listenerlist;
public Dictionary<int, String> ItemTypeDictionary { get; }
public Dictionary<String, NodeData> AvailableNodes { get; }
private EventInterface _event;
public FSM(EventInterface callback)
{
_event = callback;
ItemTypeDictionary = new Dictionary<int, string>();
AvailableNodes = new Dictionary<String, NodeData>();
listenerlist = new List<FSMResultInterface>();
}
/// <summary>
/// Initialize Fire Alarm Module
/// </summary>
/// <param name="netgroup">1 - 255</param>
/// <param name="netnode">1 - 255</param>
/// <param name="pna">1 - 255</param>
/// <param name="localip">on a system with multiple network adapter, use this to specify which adapter will be used</param>
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();
}
/// <summary>
/// Optional option if using Multicast Communication
/// </summary>
/// <param name="useMulticast">true if want to use Multicast</param>
/// <param name="MulticastIP">target Multicast IP, if not available, default to 239.192.0.1</param>
/// <param name="PortNumber">target Port, if not available, default to 25000</param>
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;
}
}
/// <summary>
/// Start FSM Monitoring
/// </summary>
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);
}
/// <summary>
/// Stop FSM Monitoring
/// </summary>
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);
}
}
}
}
/// <summary>
/// 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.
/// </summary>
class ConfigListener : IPanelConfigListener
{
private Dictionary<int, String> type_dictionary;
private Dictionary<String, NodeData> node_data;
private List<FSMResultInterface> listeners;
public ConfigListener(Dictionary<int, String> type, Dictionary<String, NodeData> data, List<FSMResultInterface> listener)
{
this.type_dictionary = type;
this.node_data = data;
this.listeners = listener;
}
/// <summary>
/// Akan muncul di pertama kali, untuk informasi bahwa device dengan FunctionalType [X} memiliki deskripsi [Y]
/// </summary>
/// <param name="devItemType"></param>
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);
}
}
/// <summary>
/// Akan muncul di urutan kedua, untuk informasi node device apa saja yang terdeteksi di system
/// </summary>
/// <param name="devItem"></param>
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);
}
}
}
/// <summary>
/// Akan muncul kalau suatu node hilang dari system
/// </summary>
/// <param name="address"></param>
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);
}
}
}
/// <summary>
/// The State Listener receive all states for devices send by the panel network.
/// Ini yang paling penting, untuk trigger ke Modbus dan VX3000
/// </summary>
class StateListener : IStateListener
{
private Dictionary<String,NodeData> node_data;
private List<FSMResultInterface> listeners;
public StateListener(Dictionary<String,NodeData> data, List<FSMResultInterface> listener)
{
this.node_data= data;
this.listeners = listener;
}
/// <summary>
/// Akan muncul ketiga dan seterusnya, ketika ada perubahan status dari node device
/// </summary>
/// <param name="devItemState"></param>
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");
}
/// <summary>
/// Akan muncul sekali di awal mula, sebagai informasi waktu di panel system
/// </summary>
/// <param name="mpNetTime"></param>
public void SetMPNetTime(DateTime mpNetTime)
{
Debug.WriteLine("Time is " + mpNetTime + ".");
}
}
/// <summary>
/// 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
/// </summary>
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);
}
}
}

14
FSMResultInterface.cs Normal file
View File

@@ -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);
}
}

279
MainWindow.xaml Normal file
View File

@@ -0,0 +1,279 @@
<Window x:Class="FAtoPA.Net.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:FAtoPA.Net"
mc:Ignorable="d"
Loaded="Window_Loaded"
Closing="Window_Closing"
Title="MainWindow" Height="450" Width="800">
<Grid>
<DockPanel>
<StatusBar DockPanel.Dock="Bottom" Height="30">
<StatusBar.ItemsPanel>
<ItemsPanelTemplate>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="auto"/>
<ColumnDefinition Width="auto"/>
<ColumnDefinition Width="auto"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
</Grid>
</ItemsPanelTemplate>
</StatusBar.ItemsPanel>
<StatusBarItem Grid.Column="0" HorizontalContentAlignment="Center" BorderBrush="Black" BorderThickness="1">
<TextBlock x:Name="firealarmstatusbar" Text="Fire Alarm Status" />
</StatusBarItem>
<StatusBarItem Grid.Column="1" HorizontalContentAlignment="Center" BorderBrush="Black" BorderThickness="1">
<TextBlock x:Name="modbusstatusbar" Text="Modbus Status" />
</StatusBarItem>
<StatusBarItem Grid.Column="2" HorizontalContentAlignment="Center" BorderBrush="Black" BorderThickness="1">
<TextBlock x:Name="vxstatusbar" Text="VX-3000 Status" />
</StatusBarItem>
<StatusBarItem Grid.Column="3" HorizontalContentAlignment="Center" BorderBrush="Black" BorderThickness="1">
<TextBlock x:Name="datetimebar" Text="Date and Time" />
</StatusBarItem>
</StatusBar>
<TabControl>
<TabItem Header="Fire Alarm">
<DockPanel>
<Grid DockPanel.Dock="Top" Height="75">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="150"/>
<ColumnDefinition Width="500"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Button Margin="5,5,5,5" x:Name="btnStartStopFSM" Content="Start FSM Connection" Grid.Column="0" Click="btnStartStopFSM_Click" />
<Grid Grid.Column="1">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Grid Grid.Column="0">
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<Label Content="NetGroup" HorizontalContentAlignment="Center" VerticalContentAlignment="Center" Grid.Row="0"/>
<ComboBox x:Name="netGroupNumber" HorizontalContentAlignment="Center" VerticalContentAlignment="Center" Grid.Row="1" Margin="5,0,5,5" ItemsSource="{Binding NetGroupList, IsAsync=True}" SelectedIndex="0"/>
</Grid>
<Grid Grid.Column="1">
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<Label Content="NetNode" HorizontalContentAlignment="Center" VerticalContentAlignment="Center" Grid.Row="0"/>
<ComboBox x:Name="netNodeNumber" HorizontalContentAlignment="Center" VerticalContentAlignment="Center" Grid.Row="1" Margin="5,0,5,5" ItemsSource="{Binding NetNodeList, IsAsync=True}" SelectedIndex="0"/>
</Grid>
<Grid Grid.Column="2">
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<Label Content="SI Type" HorizontalContentAlignment="Center" VerticalContentAlignment="Center" Grid.Row="0"/>
<ComboBox x:Name="siType" HorizontalContentAlignment="Center" VerticalContentAlignment="Center" Grid.Row="1" Margin="5,0,5,5" ItemsSource="{Binding SIType, IsAsync=True}" SelectedIndex="0"/>
</Grid>
<Grid Grid.Column="3">
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<Label Content="SI Number" HorizontalContentAlignment="Center" VerticalContentAlignment="Center" Grid.Row="0"/>
<ComboBox x:Name="siNumber" Text="1" HorizontalContentAlignment="Center" VerticalContentAlignment="Center" Grid.Row="1" Margin="5,0,5,5" ItemsSource="{Binding SINumberList, IsAsync=True}" SelectedIndex="0"/>
</Grid>
<Grid Grid.Column="4">
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<Label Content="SI Sub" Grid.Row="0" HorizontalContentAlignment="Center" VerticalContentAlignment="Center"/>
<ComboBox x:Name="siSub" Text="1" HorizontalContentAlignment="Center" VerticalContentAlignment="Center" Grid.Row="1" Margin="5,0,5,5" ItemsSource="{Binding SISubList, IsAsync=True}" SelectedIndex="0"/>
</Grid>
</Grid>
<Grid Grid.Column="2">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Button Grid.Column="0" Margin="5" x:Name="btnAddSIID" Content="Add To Table" Click="btnAddSIID_Click"/>
<Button Grid.Column="1" Margin="5" x:Name="btnDelSIID" Content="Remove From Table" Click="btnDelSIID_Click"/>
<Button Grid.Column="2" Margin="5" x:Name="btnClearSIID" Content="Clear Table" Click="btnClearSIID_Click"/>
</Grid>
</Grid>
<DockPanel DockPanel.Dock="Left" Width="200">
<Label Content="Detected SIID" DockPanel.Dock="Top" HorizontalContentAlignment="Center" VerticalContentAlignment="Center"/>
<Label Content="Count : 0" DockPanel.Dock="Bottom" HorizontalContentAlignment="Center" VerticalContentAlignment="Center" x:Name="DetectedSIIDCount"/>
<ListBox x:Name="DetectedSIID" />
</DockPanel>
<DataGrid MinRowHeight="50" x:Name="FSMTable" AutoGenerateColumns="True" AutoGeneratingColumn="FSMTable_AutoGeneratingColumn" />
</DockPanel>
</TabItem>
<TabItem Header="Modbus">
<DockPanel>
<Grid DockPanel.Dock="Top" Height="75">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="150"/>
<ColumnDefinition Width="500"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Button Margin="5" x:Name="btnStartStopModbus" Content="Start Modbus Connection" Grid.Column="0" Click="btnStartStopModbus_Click"/>
<Grid Grid.Column="1">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="75"/>
<ColumnDefinition/>
<ColumnDefinition Width="6*"/>
<ColumnDefinition Width="75"/>
<ColumnDefinition Width="7*"/>
</Grid.ColumnDefinitions>
<Label Grid.Column="0" Content="SIID" HorizontalContentAlignment="Center" VerticalContentAlignment="Center"/>
<ComboBox Grid.Column="1" x:Name="ModbusSIIDComboBox" HorizontalContentAlignment="Center" VerticalContentAlignment="Center" Margin="5,0,5,0" ItemsSource="{Binding FSMSIID, IsAsync=True}" Grid.ColumnSpan="2" SelectedIndex="0"/>
<Label Grid.Column="3" Content="Register" HorizontalContentAlignment="Center" VerticalContentAlignment="Center"/>
<ComboBox Grid.Column="4" x:Name="ModbusRegister" HorizontalContentAlignment="Center" VerticalContentAlignment="Center" Margin="5,0,5,0" ItemsSource="{Binding ModbusRegisters, IsAsync=True}" SelectedIndex="0" />
</Grid>
<Grid Grid.Column="2">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Button Grid.Column="0" Margin="5" x:Name="btnAddModbus" Content="Add To Table" Click="btnAddModbus_Click"/>
<Button Grid.Column="1" Margin="5" x:Name="btnDelModbus" Content="Remove From Table" Click="btnDelModbus_Click"/>
<Button Grid.Column="2" Margin="5" x:Name="btnClearModbus" Content="Clear Table" Click="btnClearModbus_Click"/>
</Grid>
</Grid>
<DockPanel DockPanel.Dock="Left" Width="200">
<Label Content="Connected Modbus Client" DockPanel.Dock="Top" HorizontalContentAlignment="Center" VerticalContentAlignment="Center"/>
<Label Content="Count : 0" DockPanel.Dock="Bottom" HorizontalContentAlignment="Center" VerticalContentAlignment="Center" x:Name="ConnectedModbusCount"/>
<ListBox x:Name="ConnectedModbusClients" />
</DockPanel>
<DataGrid x:Name="ModbusTable" AutoGenerateColumns="True" AutoGeneratingColumn="ModbusTable_AutoGeneratingColumn" />
</DockPanel>
</TabItem>
<TabItem Header="TOA VX-3000">
<DockPanel>
<Grid DockPanel.Dock="Top" Height="75">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="150"/>
<ColumnDefinition Width="500"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Button Margin="5,5,5,5" x:Name="btnStartStopVX" Content="Start VX-3000 Connection" Grid.Column="0" Click="btnStartStopVX_Click" />
<Grid Grid.Column="1">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="50"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="50"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="50"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Label Grid.Column="0" Content="SIID" HorizontalContentAlignment="Center" VerticalContentAlignment="Center"/>
<ComboBox Grid.Column="1" x:Name="VXSIIDComboBox" HorizontalContentAlignment="Center" VerticalContentAlignment="Center" Margin="5,0" ItemsSource="{Binding FSMSIID, IsAsync=True}" SelectedIndex="0"/>
<Label Grid.Column="2" Content="Frame" HorizontalContentAlignment="Center" VerticalContentAlignment="Center"/>
<ComboBox Grid.Column="3" x:Name="VXFrame" HorizontalContentAlignment="Center" VerticalContentAlignment="Center" Margin="5,0" ItemsSource="{Binding VX3KID, IsAsync=True}" SelectedIndex="0"/>
<Label Grid.Column="4" Content="C-IN" HorizontalContentAlignment="Center" VerticalContentAlignment="Center"/>
<ComboBox Grid.Column="5" x:Name="VXCIN" HorizontalContentAlignment="Center" VerticalContentAlignment="Center" Margin="5,0" ItemsSource="{Binding VX3KCIN, IsAsync=True}" SelectedIndex="0" />
</Grid>
<Grid Grid.Column="2">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Button Grid.Column="0" Margin="5" x:Name="btnAddVX" Content="Add To Table" Click="btnAddVX_Click"/>
<Button Grid.Column="1" Margin="5" x:Name="btnDelVX" Content="Remove From Table" Click="btnDelVX_Click"/>
<Button Grid.Column="2" Margin="5" x:Name="btnClearVX" Content="Clear Table" Click="btnClearVX_Click"/>
</Grid>
</Grid>
<DataGrid x:Name="VXTable" AutoGenerateColumns="True" AutoGeneratingColumn="VXTable_AutoGeneratingColumn" />
</DockPanel>
</TabItem>
<TabItem Header="Settings">
<StackPanel Orientation="Vertical">
<GroupBox Header="Fire Alarm Settings">
<DockPanel>
<Button x:Name="btnApplyFSMConfig" Content="Apply Config" Click="ApplyFSMConfig" DockPanel.Dock="Right" Margin="5"/>
<StackPanel Orientation="Vertical">
<StackPanel Orientation="Horizontal">
<Label Content="NetGroup" Width="150"/>
<TextBox x:Name="FSMConfig_NetGroup" Text="1" MinWidth="30" HorizontalContentAlignment="Center" VerticalContentAlignment="Center" PreviewTextInput="FSMConfig_NetGroup_PreviewTextInput" TextChanged="FSMConfig_NetGroup_TextChanged"/>
</StackPanel>
<StackPanel Orientation="Horizontal">
<Label Content="NetNode" Width="150"/>
<TextBox x:Name="FSMConfig_NetNode" Text="1" MinWidth="30" HorizontalContentAlignment="Center" VerticalContentAlignment="Center" PreviewTextInput="FSMConfig_NetNode_PreviewTextInput" TextChanged="FSMConfig_NetNode_TextChanged"/>
</StackPanel>
<StackPanel Orientation="Horizontal">
<Label Content="PNA" Width="150"/>
<TextBox x:Name="FSMConfig_PNA" Text="1" MinWidth="30" HorizontalContentAlignment="Center" VerticalContentAlignment="Center" PreviewTextInput="FSMConfig_PNA_PreviewTextInput" TextChanged="FSMConfig_PNA_TextChanged"/>
</StackPanel>
<StackPanel Orientation="Horizontal">
<Label Content="Local IP Address" Width="150"/>
<TextBox x:Name="FSMConfig_LocalIP" Text="0.0.0.0" MinWidth="75" HorizontalContentAlignment="Center" VerticalContentAlignment="Center" PreviewTextInput="FSMConfig_LocalIP_PreviewTextInput" TextChanged="FSMConfig_LocalIP_TextChanged"/>
</StackPanel>
<StackPanel Orientation="Horizontal">
<Label Content="Use Multicast" Width="150"/>
<CheckBox x:Name="FSM_UseMulticast" Content="No" IsChecked="False" VerticalAlignment="Center"/>
</StackPanel>
<StackPanel Orientation="Horizontal">
<Label Content="Multicast Address" Width="150"/>
<TextBox x:Name="FSMConfig_MulticastAddress" Text="239.192.0.1" MinWidth="75" HorizontalContentAlignment="Center" VerticalContentAlignment="Center" PreviewTextInput="FSMConfig_MulticastAddress_PreviewTextInput" TextChanged="FSMConfig_MulticastAddress_TextChanged"/>
</StackPanel>
<StackPanel Orientation="Horizontal">
<Label Content="Multicast Port" Width="150"/>
<TextBox x:Name="FSMConfig_MulticastPort" Text="25000" MinWidth="50" HorizontalContentAlignment="Center" VerticalContentAlignment="Center" PreviewTextInput="FSMConfig_MulticastPort_PreviewTextInput" TextChanged="FSMConfig_MulticastPort_TextChanged"/>
</StackPanel>
</StackPanel>
</DockPanel>
</GroupBox>
<GroupBox Header="Modbus Setting">
<DockPanel>
<Button x:Name="btnApplyModbusConfig" Content="Apply Config" Click="ApplyModbusConfig" DockPanel.Dock="Right" Margin="5"/>
<StackPanel Orientation="Vertical">
<StackPanel Orientation="Horizontal">
<Label Width="150" Content="Listen Port"/>
<TextBox x:Name="ModbusListenPort" Text="502" MinWidth="50" HorizontalContentAlignment="Center" VerticalContentAlignment="Center" PreviewTextInput="ModbusListenPort_PreviewTextInput" TextChanged="ModbusListenPort_TextChanged"/>
</StackPanel>
<StackPanel Orientation="Horizontal">
<Label Width="150" Content="Device ID"/>
<TextBox x:Name="ModbusDeviceID" Text="1" MinWidth="50" HorizontalContentAlignment="Center" VerticalContentAlignment="Center" PreviewTextInput="ModbusDeviceID_PreviewTextInput" TextChanged="ModbusDeviceID_TextChanged"/>
</StackPanel>
<StackPanel Orientation="Horizontal">
<Label Width="150" Content="Max. Register"/>
<TextBox x:Name="ModbusMaxRegister" Text="2000" MinWidth="50" HorizontalContentAlignment="Center" VerticalContentAlignment="Center" PreviewTextInput="ModbusMaxRegister_PreviewTextInput" TextChanged="ModbusMaxRegister_TextChanged"/>
</StackPanel>
</StackPanel>
</DockPanel>
</GroupBox>
<GroupBox Header="VX-3000">
<DockPanel Margin="-1,0,1,0">
<Button x:Name="btnApplyVX3KConfig" Content="Apply Config" Margin="5" Click="ApplyVX3KConfig" DockPanel.Dock="Right"/>
<StackPanel Orientation="Vertical">
<StackPanel Orientation="Horizontal">
<Label Width="150" Content="VX-3000 IP" />
<TextBox x:Name="VX3K_IP" Text="192.168.14.1" MinWidth="75" HorizontalContentAlignment="Center" VerticalContentAlignment="Center" PreviewTextInput="VX3K_IP_PreviewTextInput" TextChanged="VX3K_IP_TextChanged"/>
</StackPanel>
<StackPanel Orientation="Horizontal">
<Label Width="150" Content="VX-3000 Port" />
<TextBox x:Name="VX3K_Port" Text="5000" MinWidth="50" HorizontalContentAlignment="Center" VerticalContentAlignment="Center" PreviewTextInput="VX3K_Port_PreviewTextInput" TextChanged="VX3K_Port_TextChanged" />
</StackPanel>
</StackPanel>
</DockPanel>
</GroupBox>
</StackPanel>
</TabItem>
</TabControl>
</DockPanel>
</Grid>
</Window>

1417
MainWindow.xaml.cs Normal file

File diff suppressed because it is too large Load Diff

26
MappingData.cs Normal file
View File

@@ -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; }
}
}

545
ModbusSlave.cs Normal file
View File

@@ -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<String, ModbusClientRecord> clientmap;
UInt16[] Registers;
Boolean running = false;
public byte ID { get; set; } = 1;
private ModbusSlaveEvent _event;
/// <summary>
/// Modbus Slave is actually a server (TCPListener),<br/>
/// that contain data and can be read or written by <br/>
/// a Modbus Master (which actually a TCPClient)
/// </summary>
/// <param name="maxregister">Maximum Register that can be read or written</param>
public ModbusSlave(ModbusSlaveEvent callback, UInt16 maxregister)
{
_event = callback;
clientmap = new Dictionary<String, ModbusClientRecord>();
Registers = new UInt16[maxregister];
}
/// <summary>
/// Get Register Count available in Modbus Slave
/// </summary>
/// <returns>Register Count</returns>
public UInt16 GetRegisterCount()
{
return (UInt16)Registers.Length;
}
/// <summary>
/// Set Register Value
/// </summary>
/// <param name="address">start from 0 until GetRegisterCount() - 1</param>
/// <param name="value">between 0 until 65535</param>
public void SetRegister(UInt16 address, UInt16 value)
{
if (address < Registers.Length)
{
Registers[address] = value;
}
}
/// <summary>
/// Get Register Value
/// </summary>
/// <param name="address">start from 0 until GetRegisterCount() - 1 </param>
/// <returns>between 0 until 65535</returns>
public UInt16 GetRegister(UInt16 address)
{
if (address < Registers.Length)
{
return Registers[address];
}
return 0;
}
/// <summary>
/// Set Bit Value in Register
/// </summary>
/// <param name="address">start from 0 until GetRegisterCount() -1 </param>
/// <param name="bit">start from 0 until 15</param>
/// <param name="value">true = ON, false = OFF</param>
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;
}
}
/// <summary>
/// Get Bit Value in Register
/// </summary>
/// <param name="address">start from 0 until 65535</param>
/// <param name="bit"></param>
/// <returns></returns>
public Boolean GetBit(UInt16 address, UInt16 bit)
{
if (address < Registers.Length)
{
UInt16 reg = Registers[address];
return (reg & (1 << bit)) != 0;
}
return false;
}
/// <summary>
/// Start Modbus Slave
/// </summary>
/// <param name="port">Listening Port</param>
/// <returns>true if success</returns>
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;
}
/// <summary>
/// Stop Modbus Slave
/// </summary>
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();
}
}
/// <summary>
/// Get Transaction ID from Modbus Command<br/>
/// TransactionID is 2 bytes at index [0,1]
///
/// </summary>
/// <param name="cmd">Modbus Command</param>
/// <returns>TransactionID or 0 if failed</returns>
private UInt16 GetTransactionID(byte[] cmd)
{
if (cmd.Length >= 2)
{
return (UInt16)((cmd[0] << 8) + cmd[1]);
}
return 0;
}
/// <summary>
/// Get Protocol ID from Modbus Command<br/>
/// ProtocolID is 2 bytes at index [2,3]<br/>
/// For TCP Modbus, ProtocolID is always 0
/// </summary>
/// <param name="cmd">Modbus Command</param>
/// <returns>ProtocolID</returns>
private UInt16 GetProtocolID(byte[] cmd)
{
if (cmd.Length >= 4)
{
return (UInt16)((cmd[2] << 8) + cmd[3]);
}
return 0;
}
/// <summary>
/// Get Length from Modbus Command<br/>
/// Length is 2 bytes at index [4,5]<br/>
/// Length is Modbus Payload Length + 2 (Unit ID and Function Code)
/// </summary>
/// <param name="cmd">Modbus Command</param>
/// <returns>Length or 0 if failed</returns>
private UInt16 GetLength(byte[] cmd)
{
if (cmd.Length >= 6)
{
return (UInt16)((cmd[4] << 8) + cmd[5]);
}
return 0;
}
/// <summary>
/// Get Unit ID from Modbus Command<br/>
/// UnitID is 1 byte at index [6]
/// For Modbus Slave, UnitID is always 1
/// </summary>
/// <param name="cmd">Modbus Command</param>
/// <returns>Unit ID or 0 if failed</returns>
private Byte GetUnitID(byte[] cmd)
{
if (cmd.Length >= 7)
{
return cmd[6];
}
return 0;
}
/// <summary>
/// Get Function Code from Modbus Command<br/>
/// Function Code is 1 byte at index [7]
///
/// </summary>
/// <param name="cmd">Modbus Command</param>
/// <returns>Function Code or 0 if failed</returns>
private Byte GetFunctionCode(byte[] cmd)
{
if (cmd.Length >= 8)
{
return cmd[7];
}
return 0;
}
/// <summary>
/// Get Modbus Payload from Modbus Command<br/>
/// Modbus Payload begins at index 8
/// </summary>
/// <param name="cmd">Modbus Command</param>
/// <returns>Modbus Payload or empty array if failed</returns>
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<byte>();
}
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);
}
}

41
NodeData.cs Normal file
View File

@@ -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();
}
}
}
}

View File

@@ -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
//<UICulture>CultureYouAreCodingWith</UICulture> in your .csproj file
//inside a <PropertyGroup>. For example, if you are using US english
//in your source files, set the <UICulture> 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")]

71
Properties/Resources.Designer.cs generated Normal file
View File

@@ -0,0 +1,71 @@
//------------------------------------------------------------------------------
// <auto-generated>
// 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.
// </auto-generated>
//------------------------------------------------------------------------------
namespace FAtoPA.Net.Properties
{
/// <summary>
/// A strongly-typed resource class, for looking up localized strings, etc.
/// </summary>
// 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()
{
}
/// <summary>
/// Returns the cached ResourceManager instance used by this class.
/// </summary>
[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;
}
}
/// <summary>
/// Overrides the current thread's CurrentUICulture property for all
/// resource lookups using this strongly typed resource class.
/// </summary>
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
internal static global::System.Globalization.CultureInfo Culture
{
get
{
return resourceCulture;
}
set
{
resourceCulture = value;
}
}
}
}

117
Properties/Resources.resx Normal file
View File

@@ -0,0 +1,117 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
Version 2.0
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</resheader>
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
<value>[base64 mime encoded serialized .NET Framework object]</value>
</data>
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment>
</data>
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
: System.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
</root>

30
Properties/Settings.Designer.cs generated Normal file
View File

@@ -0,0 +1,30 @@
//------------------------------------------------------------------------------
// <auto-generated>
// 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.
// </auto-generated>
//------------------------------------------------------------------------------
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;
}
}
}
}

View File

@@ -0,0 +1,7 @@
<?xml version='1.0' encoding='utf-8'?>
<SettingsFile xmlns="uri:settings" CurrentProfile="(Default)">
<Profiles>
<Profile Name="(Default)" />
</Profiles>
<Settings />
</SettingsFile>

361
VX3K.cs Normal file
View File

@@ -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;
}
/// <summary>
/// Connect to VX-3000
/// </summary>
/// <param name="ipaddress">target IP address, default to 192.168.14.1</param>
/// <param name="port">target port, default to 5000</param>
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;
}
/// <summary>
/// Check if connected to VX-3000
/// </summary>
/// <returns>true if connected</returns>
public Boolean IsConnected()
{
if (socket != null)
{
return socket.Connected;
}
return false;
}
/// <summary>
/// Disconnect from VX-3000
/// </summary>
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");
}
/// <summary>
/// Set Virtual Contact Input ON/OFF <br/>
/// Contact Input ID : <br/>
/// 0 - 15 : Contact Input 1 - 16 <br/>
/// 16 : Emergency Contact input 1 <br/>
/// 17 : Emergency Contact input 2 <br/>
/// </summary>
/// <param name="vxID">VX Frame ID</param>
/// <param name="CinID">Contact Input ID</param>
/// <param name="isON">true = ON, false = OFF</param>
/// <returns></returns>
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;
}
/// <summary>
/// Sending command to VX-3000
/// </summary>
/// <param name="cmd">Array of bytes containing command</param>
/// <returns>true if success</returns>
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;
}
/// <summary>
/// Reading response from VX-3000
/// </summary>
/// <returns>array of bytes containing response , or empty array if failed</returns>
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<byte>();
}
/// <summary>
/// Create VX-3000 command bytes
/// </summary>
/// <param name="command">command code</param>
/// <param name="payload">payload to send</param>
/// <returns>array of bytes</returns>
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<payload.Length; i++)
{
header[8 + i] = payload[i];
}
return header;
}
/// <summary>
/// Return High Byte of a 16-bit value
/// </summary>
/// <param name="value">16 bit value</param>
/// <returns>Byte value</returns>
private byte HighByte(UInt16 value)
{
return (byte)((value >> 8) & 0xFF);
}
/// <summary>
/// Return Low Byte of a 16-bit value
/// </summary>
/// <param name="value">16 bit value</param>
/// <returns> Byte value</returns>
private byte LowByte(UInt16 value)
{
return (byte)(value & 0xFF);
}
/// <summary>
/// Get Command Code from response
/// Command Code value is Big-Endian UInt16 from index 0 and 1
/// </summary>
/// <param name="value">array of byte from Response</param>
/// <returns>Command Code or 0 if failed</returns>
private UInt16 GetCommandCode(byte[] value)
{
if (value != null && value.Length >= 2)
{
return (UInt16)((value[0] << 8) | value[1]);
}
return 0;
}
/// <summary>
/// Get Response Code from response
/// Response Code value is Big-Endian UInt16 from index 2 and 3
/// </summary>
/// <param name="value">array of byte from Response</param>
/// <returns>Response Code or 0 if failed</returns>
private UInt16 GetResponseCode(byte[] value)
{
if (value != null && value.Length >= 4)
{
return (UInt16)((value[2] << 8) | value[3]);
}
return 0;
}
/// <summary>
/// Get Command Length from response
/// Command Length value is Big-Endian UInt16 from index 4 and 5
/// </summary>
/// <param name="value">array of byte from Response</param>
/// <returns>Command Length or 0 if failed</returns>
private UInt16 GetCommandLength(byte[] value)
{
if (value != null && value.Length >= 6)
{
return (UInt16)((value[4] << 8) | value[5]);
}
return 0;
}
/// <summary>
/// Get Command Flag from response
/// Command Flag value is Byte at index 6
/// </summary>
/// <param name="value">array of byte from Response</param>
/// <returns>Command Flag or 0 if failed</returns>
private byte GetCommandFlag(byte[] value)
{
if (value != null && value.Length >= 7)
{
return value[6];
}
return 0;
}
/// <summary>
/// Get Payload from Response
/// Payload is located at index 8 to end of array
/// </summary>
/// <param name="value">array of byte from Response</param>
/// <returns>Payload bytes or empty array if failed</returns>
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<byte>();
}
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);
}
}
}

19
packages.config Normal file
View File

@@ -0,0 +1,19 @@
<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="Microsoft.Bcl.AsyncInterfaces" version="9.0.0" targetFramework="net48" />
<package id="Microsoft.Data.Sqlite" version="9.0.0" targetFramework="net48" />
<package id="Microsoft.Data.Sqlite.Core" version="9.0.0" targetFramework="net48" />
<package id="SQLitePCLRaw.bundle_e_sqlite3" version="2.1.10" targetFramework="net48" />
<package id="SQLitePCLRaw.core" version="2.1.10" targetFramework="net48" />
<package id="SQLitePCLRaw.lib.e_sqlite3" version="2.1.10" targetFramework="net48" />
<package id="SQLitePCLRaw.provider.dynamic_cdecl" version="2.1.10" targetFramework="net48" />
<package id="System.Buffers" version="4.5.1" targetFramework="net48" />
<package id="System.IO.Pipelines" version="9.0.0" targetFramework="net48" />
<package id="System.Memory" version="4.5.5" targetFramework="net48" />
<package id="System.Numerics.Vectors" version="4.5.0" targetFramework="net48" />
<package id="System.Runtime.CompilerServices.Unsafe" version="6.0.0" targetFramework="net48" />
<package id="System.Text.Encodings.Web" version="9.0.0" targetFramework="net48" />
<package id="System.Text.Json" version="9.0.0" targetFramework="net48" />
<package id="System.Threading.Tasks.Extensions" version="4.5.4" targetFramework="net48" />
<package id="System.ValueTuple" version="4.5.0" targetFramework="net48" />
</packages>