2024-11-05 10:31:38 +03:00
using System ;
using System.Collections.Generic ;
using System.ComponentModel ;
using System.Data ;
using System.Threading.Tasks ;
using System.Windows.Forms ;
using System.IO.Ports ;
2024-12-05 15:57:07 +03:00
using Newtonsoft.Json ;
2024-11-05 10:31:38 +03:00
namespace Gidrolock_Modbus_Scanner
{
public partial class App : Form
{
2024-12-06 16:32:10 +03:00
2024-11-05 10:31:38 +03:00
public bool isAwaitingResponse = false ;
public short [ ] res = new short [ 12 ] ;
2024-12-12 15:14:13 +03:00
SerialPort port = Modbus . port ;
2024-11-05 16:57:05 +03:00
public int expectedLength = 0 ;
2024-12-05 15:57:07 +03:00
2024-12-12 15:14:13 +03:00
public byte [ ] latestMessage ;
2025-01-14 11:31:23 +03:00
public Dictionary < string , string > models = new Dictionary < string , string > ( ) ;
2025-01-21 14:38:59 +03:00
byte [ ] message = null ;
byte [ ] data = null ;
2025-01-14 11:31:23 +03:00
2025-01-21 14:38:59 +03:00
DateTime dateTime ;
Datasheet datasheet ;
2024-12-06 16:32:10 +03:00
#region Initialization
2024-11-05 10:31:38 +03:00
public App ( )
{
InitializeComponent ( ) ;
2024-12-12 15:14:13 +03:00
Modbus . Init ( ) ;
Modbus . ResponseReceived + = OnResponseReceived ;
2025-01-14 11:31:23 +03:00
this . UpDown_ModbusID . Value = 0 ;
2024-11-05 16:57:05 +03:00
TextBox_Log . Text = "Приложение готово к работе." ;
2025-01-14 11:31:23 +03:00
cBoxDevice . Items . Add ( "Standard" ) ;
cBoxDevice . Items . Add ( "Inteli" ) ;
2025-01-21 14:38:59 +03:00
cBoxDevice . Items . Add ( "Premium Plus" ) ;
2025-01-14 11:31:23 +03:00
cBoxDevice . Items . Add ( "Premium" ) ;
cBoxDevice . SelectedIndex = 0 ;
checkboxID . Checked = false ;
UpDown_ModbusID . Enabled = false ;
UpDown_ModbusID . Value = 30 ;
UpDown_ModbusID . Minimum = 1 ;
UpDown_ModbusID . Maximum = 247 ;
models . Add ( "Standard" , "STW485" ) ;
models . Add ( "Inteli" , "INTELI" ) ;
2025-01-21 14:38:59 +03:00
models . Add ( "Premuim Plus" , "BUP485" ) ;
2025-01-14 11:31:23 +03:00
models . Add ( "Premium" , "BUP485" ) ;
2024-12-23 16:30:35 +03:00
/* - Version Check - */
System . Reflection . Assembly assembly = System . Reflection . Assembly . GetExecutingAssembly ( ) ;
System . Diagnostics . FileVersionInfo fvi = System . Diagnostics . FileVersionInfo . GetVersionInfo ( assembly . Location ) ;
string version = fvi . FileVersion ;
Console . WriteLine ( "Version: " + version ) ;
2025-01-21 14:38:59 +03:00
Modbus . ResponseReceived + = ( sndr , msg ) = >
{
message = msg . Message ;
data = msg . Data ;
} ;
2024-12-12 15:14:13 +03:00
}
2024-12-23 16:30:35 +03:00
2024-11-05 10:31:38 +03:00
void App_FormClosed ( object sender , FormClosedEventArgs e )
{
port . Close ( ) ;
}
void Form1_Load ( object sender , EventArgs e )
{
CBox_Ports . Items . AddRange ( SerialPort . GetPortNames ( ) ) ;
if ( CBox_Ports . Items . Count > 0 )
CBox_Ports . SelectedIndex = 0 ;
}
2024-12-06 16:32:10 +03:00
#endregion
2024-11-05 10:31:38 +03:00
2024-11-05 16:57:05 +03:00
private async void ButtonConnect_Click ( object sender , EventArgs e )
{
2024-12-12 15:14:13 +03:00
if ( CBox_Ports . SelectedItem . ToString ( ) = = "COM1" )
2024-12-05 15:57:07 +03:00
{
2024-12-12 15:14:13 +03:00
DialogResult res = MessageBox . Show ( "Выбран серийный порт COM1, который обычно является портом PS/2 или RS-232, не подключенным к Modbus устройству. Продолжить?" , "Внимание" , MessageBoxButtons . OKCancel ) ;
if ( res = = DialogResult . Cancel )
return ;
}
2024-12-19 10:41:52 +03:00
if ( UpDown_ModbusID . Value = = 0 )
{
2025-01-14 11:31:23 +03:00
DialogResult res = MessageBox . Show ( "Указан Modbus ID 0 — глобальное вещание. Устройства не смогут отвечать на сообщения. Продолжить?" , "Внимание" , MessageBoxButtons . OKCancel ) ;
2024-12-19 10:41:52 +03:00
if ( res = = DialogResult . Cancel )
return ;
}
2025-01-14 11:31:23 +03:00
try
2024-12-12 15:14:13 +03:00
{
2025-01-14 11:31:23 +03:00
/* - Port Setup - */
if ( port . IsOpen )
port . Close ( ) ;
2024-12-06 16:32:10 +03:00
2025-01-14 11:31:23 +03:00
port . Handshake = Handshake . None ;
port . PortName = CBox_Ports . Text ;
port . BaudRate = 9600 ;
port . Parity = Parity . None ;
port . DataBits = 8 ;
port . StopBits = StopBits . One ;
2024-12-16 14:31:14 +03:00
2025-01-14 11:31:23 +03:00
port . ReadTimeout = 3000 ;
port . WriteTimeout = 3000 ;
port . ReadBufferSize = 8192 ;
2024-12-12 15:14:13 +03:00
2025-01-14 11:31:23 +03:00
port . Open ( ) ;
// send read request for 6 registers starting from 200
// if we've got error or a timeout, show appropriate errors
// else parse response to unicode and go through every .json
// if matching model is found, instantiate device window
2024-12-12 15:14:13 +03:00
2024-12-16 14:31:14 +03:00
// setup event listener
2025-01-21 14:38:59 +03:00
2024-12-12 15:14:13 +03:00
2025-01-14 11:31:23 +03:00
// send message
AddLog ( "Проверка модели устройства." ) ;
2024-12-16 14:31:14 +03:00
2025-01-21 14:38:59 +03:00
if ( await PollModel ( ) )
2025-01-14 11:31:23 +03:00
{
2025-01-21 14:38:59 +03:00
Console . WriteLine ( "true" ) ;
2025-01-14 11:31:23 +03:00
// confirm the model
string response = ByteArrayToUnicode ( data ) ;
if ( response ! = models [ cBoxDevice . SelectedItem . ToString ( ) ] )
2024-12-16 14:31:14 +03:00
{
2025-01-14 11:31:23 +03:00
// response doesn't match expected model
2025-01-21 14:38:59 +03:00
AddLog ( "Ответ устройства не соответствует выбранной модели. Поиск подходящей модели." ) ;
2025-01-14 11:31:23 +03:00
// check whether it matches anything else, offer to open that model if it does
string match = "" ;
2025-01-21 14:38:59 +03:00
string model = "" ;
int index = 0 ;
2025-01-14 11:31:23 +03:00
foreach ( string key in models . Keys )
2024-12-16 14:31:14 +03:00
{
2025-01-14 11:31:23 +03:00
if ( models [ key ] = = response )
2025-01-21 14:38:59 +03:00
{
2025-01-14 11:31:23 +03:00
match = models [ key ] ;
2025-01-21 14:38:59 +03:00
model = key ;
break ;
}
index + + ;
2024-12-16 14:31:14 +03:00
}
2024-12-18 16:49:58 +03:00
2025-01-21 14:38:59 +03:00
if ( match = = "" ) // if no matches found
MessageBox . Show ( "Подключенное устройство не соответствует ни одной из поддерживаемых моделей." ) ;
else // match found, offer to switch to that model instead
2024-12-16 14:31:14 +03:00
{
2025-01-21 14:38:59 +03:00
DialogResult result = MessageBox . Show ( ( "Подключенное устройство соответствует модели " + model + ". Нажмите О К , чтобы открыть настройки для модели " + model ) , "Внимание" , MessageBoxButtons . OKCancel ) ;
if ( result = = DialogResult . OK )
{
// instantiate panel
AddLog ( "Открываю панель конфигурации." ) ;
Device device = GetDevice ( ( DeviceType ) index ) ;
StartDatasheet ( device ) ;
}
2024-12-16 14:31:14 +03:00
}
}
2025-01-14 11:31:23 +03:00
else
2024-12-16 14:33:13 +03:00
{
2025-01-14 11:31:23 +03:00
// model is correct, instantiate config panel
2025-01-21 14:38:59 +03:00
int index = cBoxDevice . SelectedIndex ;
AddLog ( "Устройство соответстует модели, открываю панель конфигурации." ) ;
Device device = GetDevice ( ( DeviceType ) index ) ;
StartDatasheet ( device ) ;
2024-12-16 14:33:13 +03:00
}
2025-01-21 14:38:59 +03:00
}
2025-01-14 11:31:23 +03:00
}
catch ( Exception ex )
{
MessageBox . Show ( ex . Message ) ;
2024-12-12 15:14:13 +03:00
}
2024-12-05 11:10:53 +03:00
}
2025-01-21 14:38:59 +03:00
async Task < bool > PollModel ( )
{
var send = Modbus . ReadRegAsync ( ( byte ) UpDown_ModbusID . Value , FunctionCode . ReadInput , 200 , 6 ) ;
isAwaitingResponse = true ;
Task < bool > delay = Task . WhenAny ( Task . Delay ( 2000 ) , Task . Run ( ( ) = > { while ( isAwaitingResponse ) { } return true ; } ) ) . ContinueWith ( ( t ) = >
{
if ( isAwaitingResponse )
{
Console . WriteLine ( "Response timed out." ) ;
isAwaitingResponse = false ;
}
return false ;
} ) ;
2024-12-05 11:10:53 +03:00
2025-01-21 14:38:59 +03:00
return await delay ;
}
void StartDatasheet ( Device device )
{
datasheet = new Datasheet ( ( byte ) UpDown_ModbusID . Value , device ) ;
datasheet . Show ( ) ;
}
2024-12-05 11:10:53 +03:00
2024-12-12 15:14:13 +03:00
2024-11-05 10:31:38 +03:00
void CBox_Ports_Click ( object sender , EventArgs e )
{
CBox_Ports . Items . Clear ( ) ;
CBox_Ports . Items . AddRange ( SerialPort . GetPortNames ( ) ) ;
}
2024-12-12 15:14:13 +03:00
void OnResponseReceived ( object sender , ModbusResponseEventArgs e )
2024-11-05 10:31:38 +03:00
{
isAwaitingResponse = false ;
2024-12-18 11:28:20 +03:00
AddLog ( "Получен ответ: " + Modbus . ByteArrayToString ( e . Message ) ) ;
2025-01-14 11:31:23 +03:00
switch ( e . Status )
2024-12-18 11:28:20 +03:00
{
case ( ModbusStatus . ReadSuccess ) :
2024-12-18 14:28:06 +03:00
string values = "" ;
if ( e . Message [ 1 ] < = 0x02 )
{
for ( int i = 0 ; i < e . Data . Length ; i + + )
{
values + = Convert . ToString ( e . Data [ i ] , 2 ) . PadLeft ( 8 , '0' ) + " " ;
}
AddLog ( "Bin: " + values ) ;
}
2024-12-18 11:28:20 +03:00
if ( e . Message [ 1 ] = = 0x03 | | e . Message [ 1 ] = = 0x04 )
{
2024-12-18 14:28:06 +03:00
for ( int i = 0 ; i < e . Data . Length ; i + = 2 )
{
values + = ( ( e . Data [ i ] < < 8 ) + e . Data [ i + 1 ] ) . ToString ( ) + "; " ;
}
AddLog ( "Dec: " + values ) ;
2024-12-18 11:28:20 +03:00
AddLog ( "Unicode: " + ByteArrayToUnicode ( e . Data ) ) ;
2025-01-14 11:31:23 +03:00
}
2024-12-18 11:28:20 +03:00
break ;
case ( ModbusStatus . WriteSuccess ) :
AddLog ( "Write success;" ) ;
break ;
case ( ModbusStatus . Error ) :
string errorDesc ;
switch ( e . Message [ 2 ] )
{
case ( 0x01 ) :
errorDesc = "01 - Illegal Function" ;
break ;
case ( 0x02 ) :
errorDesc = "02 - Illegal Data Address" ;
break ;
case ( 0x03 ) :
errorDesc = "03 - Illegal Data Value" ;
break ;
case ( 0x04 ) :
errorDesc = "04 - Slave Device Failure" ;
break ;
case ( 0x05 ) :
errorDesc = "05 - Acknowledge" ;
break ;
case ( 0x06 ) :
errorDesc = "06 - Slave Device Busy" ;
break ;
case ( 0x07 ) :
errorDesc = "07 - Negative Acknowledge" ;
break ;
case ( 0x08 ) :
errorDesc = "08 - Memory Parity Error" ;
break ;
case ( 0x0A ) :
errorDesc = "10 - Gateway Path Unavailable" ;
break ;
case ( 0x0B ) :
errorDesc = "11 - Gateway Target Device Failed to Respond" ;
break ;
default :
errorDesc = "Unknown error code" ;
break ;
}
AddLog ( "Error code: " + errorDesc ) ;
break ;
}
2024-11-05 16:57:05 +03:00
}
void AddLog ( string message )
{
2024-12-16 11:35:20 +03:00
dateTime = DateTime . Now ;
2024-12-23 16:30:35 +03:00
TextBox_Log . Invoke ( ( MethodInvoker ) delegate { TextBox_Log . AppendText ( Environment . NewLine + "[" + dateTime . Hour . ToString ( ) . PadLeft ( 2 , '0' ) + ":" + dateTime . Minute . ToString ( ) . PadLeft ( 2 , '0' ) + ":" + dateTime . Second . ToString ( ) . PadLeft ( 2 , '0' ) + "] " + message ) ; } ) ;
2024-11-05 16:57:05 +03:00
}
2025-01-14 11:31:23 +03:00
public static string ByteArrayToUnicode ( byte [ ] input )
2024-12-18 11:28:20 +03:00
{
// stupid fucking WinForm textbox breaks from null symbols
// stupid fucking Encoding class does byte-by-byte conversion
2025-01-14 11:31:23 +03:00
List < char > result = new List < char > ( input . Length / 2 ) ;
2024-12-18 11:28:20 +03:00
byte [ ] flip = input ;
2024-12-18 14:28:06 +03:00
Array . Reverse ( flip ) ; // stupid fucking BitConverter is little-endian and spits out chinese nonsense otherwise
2024-12-18 11:28:20 +03:00
for ( int i = 0 ; i < flip . Length ; i + = 2 )
{
result . Add ( BitConverter . ToChar ( flip , i ) ) ;
}
result . Reverse ( ) ;
return new string ( result . ToArray ( ) ) ;
}
2025-01-14 11:31:23 +03:00
private void checkboxID_CheckedChanged ( object sender , EventArgs e )
{
UpDown_ModbusID . Enabled = checkboxID . Checked ? true : false ;
}
private void cBoxDevice_SelectedIndexChanged ( object sender , EventArgs e )
{
switch ( cBoxDevice . SelectedItem )
{
case "Standard" :
UpDown_ModbusID . Value = 30 ;
break ;
case "Premium Plus" :
UpDown_ModbusID . Value = 30 ;
break ;
case "Inteli" :
UpDown_ModbusID . Value = 26 ;
break ;
case "Premium" :
UpDown_ModbusID . Value = 26 ;
break ;
default :
Console . WriteLine ( "Invalid change" ) ;
break ;
}
}
2025-01-21 14:38:59 +03:00
public Device GetDevice ( DeviceType dt )
{
Device d = new Device ( ) ;
switch ( dt )
{
case DeviceType . Standard :
d . modelName = "Standard" ;
d . name = "Gidrolock Standard Wi-Fi RS485" ;
d . id = 30 ;
d . modelName = "STW485" ;
d . valveStatus = new Entry ( RegisterType . Coil , 1202 ) ;
d . alarmStatus = new Entry ( RegisterType . Coil , 1201 ) ;
d . hasCleaningMode = true ;
d . cleaningMode = new Entry ( RegisterType . Coil , 3 ) ;
d . hasBattery = false ;
d . wiredSensors = 2 ;
d . hasScenarioSensor = true ;
d . sensorsAlarm = new Entry ( RegisterType . Discrete , 1343 , 24 ) ;
d . radioStatus = new Entry ( RegisterType . Input , 1215 , 21 ) ;
break ;
case DeviceType . Inteli :
d . modelName = "Inteli" ;
break ;
case DeviceType . PremiumPlus :
d . modelName = "Premium Plus" ;
break ;
case DeviceType . Premium :
d . modelName = "Premium" ;
break ;
default :
break ;
}
return d ;
}
2024-11-05 10:31:38 +03:00
}
2024-12-12 15:14:13 +03:00
}
2024-11-05 10:31:38 +03:00
2024-12-12 15:14:13 +03:00
public enum FunctionCode { ReadCoil = 1 , ReadDiscrete = 2 , ReadHolding = 3 , ReadInput = 4 , WriteCoil = 5 , WriteRegister = 6 , WriteMultCoils = 15 , WriteMultRegisters = 16 } ;
2025-01-21 14:38:59 +03:00
//public enum SelectedPath { File, Folder };
public enum DeviceType { Standard , Inteli , PremiumPlus , Premium } ;