Imports System.IO Imports System.Net.Sockets Imports System.Reflection Imports System.Security.Cryptography Imports System.Text Imports System.Text.Json Imports System.Threading Public Class Form1 Private Const ConfigFileName As String = "moxa-okuma-settings.json" Private ReadOnly cboAzienda As New ComboBox() Private ReadOnly cboMacchina As New ComboBox() Private ReadOnly txtIpMoxa As New TextBox() Private ReadOnly nudPorta As New NumericUpDown() Private ReadOnly nudTimeoutConnessione As New NumericUpDown() Private ReadOnly nudTimeoutInattivita As New NumericUpDown() Private ReadOnly nudPausaRiga As New NumericUpDown() Private ReadOnly nudPausaCarattere As New NumericUpDown() Private ReadOnly nudDimensioneBlocco As New NumericUpDown() Private ReadOnly nudPausaBlocco As New NumericUpDown() Private ReadOnly nudAttesaPrimaInvio As New NumericUpDown() Private ReadOnly nudAttesaDopoInvio As New NumericUpDown() Private ReadOnly chkInvioRigaPerRiga As New CheckBox() Private ReadOnly chkInvioCaratterePerCarattere As New CheckBox() Private ReadOnly chkInvioBlocchi As New CheckBox() Private ReadOnly cboTerminatoreInvio As New ComboBox() Private ReadOnly txtFileInvio As New TextBox() Private ReadOnly txtFileRicezione As New TextBox() Private ReadOnly btnSfogliaInvio As New Button() Private ReadOnly btnSfogliaRicezione As New Button() Private ReadOnly btnTestConnessione As New Button() Private ReadOnly btnInvia As New Button() Private ReadOnly btnRicevi As New Button() Private ReadOnly btnAnnulla As New Button() Private ReadOnly mnuSblocca As New ToolStripMenuItem("Sblocca controlli") Private ReadOnly mnuCambiaPassword As New ToolStripMenuItem("Cambia password") Private ReadOnly mnuPulisciLog As New ToolStripMenuItem("Pulisci log") Private ReadOnly mnuInformazioni As New ToolStripMenuItem("Informazioni su") Private ReadOnly btnAggiornaStatoMoxa As New Button() Private ReadOnly chkMonitorStatoMoxa As New CheckBox() Private ReadOnly nudIntervalloStatoMoxa As New NumericUpDown() Private ReadOnly lstStatoMoxa As New ListView() Private ReadOnly imgStatoMoxa As New ImageList() Private ReadOnly statusTimer As New System.Windows.Forms.Timer() Private ReadOnly txtLog As New TextBox() Private ReadOnly progress As New ProgressBar() Private ReadOnly lockedControls As New List(Of Control)() Private appConfig As AppConfig Private cts As CancellationTokenSource Private controlsUnlocked As Boolean Private loadingSelection As Boolean Private checkingMoxaStatus As Boolean Public Sub New() InitializeComponent() appConfig = LoadOrCreateConfig() CleanupOldTemporaryReceives() BuildInterface() LoadCompanies() SetControlsUnlocked(False) End Sub Private Sub BuildInterface() Text = "LMEMoxaTransfer" Icon = New Icon(Path.Combine(Application.StartupPath, "Assets", "app-icon.ico")) MinimumSize = New Size(1030, 690) Size = New Size(1120, 760) StartPosition = FormStartPosition.CenterScreen Dim shell As New TableLayoutPanel With { .Dock = DockStyle.Fill, .ColumnCount = 1, .RowCount = 2, .Margin = Padding.Empty, .Padding = Padding.Empty } shell.RowStyles.Add(New RowStyle(SizeType.AutoSize)) shell.RowStyles.Add(New RowStyle(SizeType.Percent, 100)) Controls.Add(shell) Dim mainMenu = BuildMainMenu() MainMenuStrip = mainMenu mainMenu.Dock = DockStyle.Fill shell.Controls.Add(mainMenu, 0, 0) Dim tabs As New TabControl With {.Dock = DockStyle.Fill, .Margin = Padding.Empty} Dim tabTrasferimento As New TabPage("Trasferimento programmi") Dim tabStatoMoxa As New TabPage("Stato Moxa") tabs.TabPages.Add(tabTrasferimento) tabs.TabPages.Add(tabStatoMoxa) shell.Controls.Add(tabs, 0, 1) Dim root As New TableLayoutPanel With { .Dock = DockStyle.Fill, .ColumnCount = 1, .RowCount = 4, .Padding = New Padding(14) } root.RowStyles.Add(New RowStyle(SizeType.AutoSize)) root.RowStyles.Add(New RowStyle(SizeType.AutoSize)) root.RowStyles.Add(New RowStyle(SizeType.Percent, 100)) root.RowStyles.Add(New RowStyle(SizeType.AutoSize)) tabTrasferimento.Controls.Add(root) Dim grpConnessione As New GroupBox With { .Text = "Connessione Moxa", .Dock = DockStyle.Top, .AutoSize = True, .Padding = New Padding(12) } root.Controls.Add(grpConnessione, 0, 0) Dim conn As New TableLayoutPanel With {.Dock = DockStyle.Fill, .ColumnCount = 10, .AutoSize = True} conn.ColumnStyles.Add(New ColumnStyle(SizeType.AutoSize)) conn.ColumnStyles.Add(New ColumnStyle(SizeType.Absolute, 150)) conn.ColumnStyles.Add(New ColumnStyle(SizeType.AutoSize)) conn.ColumnStyles.Add(New ColumnStyle(SizeType.Absolute, 230)) conn.ColumnStyles.Add(New ColumnStyle(SizeType.AutoSize)) conn.ColumnStyles.Add(New ColumnStyle(SizeType.Absolute, 135)) conn.ColumnStyles.Add(New ColumnStyle(SizeType.AutoSize)) conn.ColumnStyles.Add(New ColumnStyle(SizeType.Absolute, 90)) conn.ColumnStyles.Add(New ColumnStyle(SizeType.AutoSize)) conn.ColumnStyles.Add(New ColumnStyle(SizeType.Absolute, 90)) grpConnessione.Controls.Add(conn) cboAzienda.DropDownStyle = ComboBoxStyle.DropDownList cboAzienda.Dock = DockStyle.Fill cboMacchina.DropDownStyle = ComboBoxStyle.DropDownList cboMacchina.Dock = DockStyle.Fill txtIpMoxa.Dock = DockStyle.Fill txtIpMoxa.ReadOnly = True nudPorta.Minimum = 1 nudPorta.Maximum = 65535 nudTimeoutConnessione.Minimum = 1 nudTimeoutConnessione.Maximum = 120 nudTimeoutInattivita.Minimum = 1 nudTimeoutInattivita.Maximum = 120 conn.Controls.Add(New Label With {.Text = "Azienda", .AutoSize = True, .Anchor = AnchorStyles.Left}, 0, 0) conn.Controls.Add(cboAzienda, 1, 0) conn.Controls.Add(New Label With {.Text = "Macchina", .AutoSize = True, .Anchor = AnchorStyles.Left, .Margin = New Padding(14, 0, 3, 0)}, 2, 0) conn.Controls.Add(cboMacchina, 3, 0) conn.Controls.Add(New Label With {.Text = "IP Moxa", .AutoSize = True, .Anchor = AnchorStyles.Left, .Margin = New Padding(14, 0, 3, 0)}, 4, 0) conn.Controls.Add(txtIpMoxa, 5, 0) conn.Controls.Add(New Label With {.Text = "Porta", .AutoSize = True, .Anchor = AnchorStyles.Left, .Margin = New Padding(14, 0, 3, 0)}, 6, 0) conn.Controls.Add(nudPorta, 7, 0) conn.Controls.Add(New Label With {.Text = "Timeout s", .AutoSize = True, .Anchor = AnchorStyles.Left, .Margin = New Padding(14, 0, 3, 0)}, 8, 0) conn.Controls.Add(nudTimeoutConnessione, 9, 0) Dim grpTrasferimento As New GroupBox With { .Text = "Invio e ricezione programmi", .Dock = DockStyle.Top, .AutoSize = True, .Padding = New Padding(12), .Margin = New Padding(0, 12, 0, 12) } root.Controls.Add(grpTrasferimento, 0, 1) Dim transfer As New TableLayoutPanel With {.Dock = DockStyle.Fill, .ColumnCount = 4, .RowCount = 10, .AutoSize = True} transfer.ColumnStyles.Add(New ColumnStyle(SizeType.AutoSize)) transfer.ColumnStyles.Add(New ColumnStyle(SizeType.Percent, 100)) transfer.ColumnStyles.Add(New ColumnStyle(SizeType.AutoSize)) transfer.ColumnStyles.Add(New ColumnStyle(SizeType.Absolute, 120)) grpTrasferimento.Controls.Add(transfer) ConfigureButton(btnSfogliaInvio, "Sfoglia") ConfigureButton(btnSfogliaRicezione, "Sfoglia") ConfigureButton(btnTestConnessione, "Test connessione") ConfigureButton(btnInvia, "Invia a Okuma") ConfigureButton(btnRicevi, "Ricevi da Okuma") ConfigureButton(btnAnnulla, "Annulla") btnAnnulla.Enabled = False txtFileInvio.Dock = DockStyle.Fill txtFileRicezione.Dock = DockStyle.Fill cboTerminatoreInvio.DropDownStyle = ComboBoxStyle.DropDownList cboTerminatoreInvio.Items.AddRange(New Object() {"Nessuno", "CR", "LF", "CRLF", "%", "% + CRLF"}) chkInvioRigaPerRiga.Text = "Invio riga per riga" chkInvioRigaPerRiga.AutoSize = True chkInvioCaratterePerCarattere.Text = "Invio carattere per carattere" chkInvioCaratterePerCarattere.AutoSize = True chkInvioBlocchi.Text = "Invio a blocchi (% + CRLF)" chkInvioBlocchi.AutoSize = True ConfigureNumericDefaults() transfer.Controls.Add(New Label With {.Text = "File da inviare", .AutoSize = True, .Anchor = AnchorStyles.Left}, 0, 0) transfer.Controls.Add(txtFileInvio, 1, 0) transfer.Controls.Add(btnSfogliaInvio, 2, 0) transfer.Controls.Add(btnInvia, 3, 0) transfer.Controls.Add(New Label With {.Text = "Cartella ricezione", .AutoSize = True, .Anchor = AnchorStyles.Left}, 0, 1) transfer.Controls.Add(txtFileRicezione, 1, 1) transfer.Controls.Add(btnSfogliaRicezione, 2, 1) transfer.Controls.Add(btnRicevi, 3, 1) transfer.Controls.Add(New Label With {.Text = "Terminatore invio", .AutoSize = True, .Anchor = AnchorStyles.Left}, 0, 2) transfer.Controls.Add(cboTerminatoreInvio, 1, 2) transfer.Controls.Add(btnAnnulla, 3, 2) transfer.Controls.Add(New Label With {.Text = "Pausa riga ms", .AutoSize = True, .Anchor = AnchorStyles.Left}, 0, 3) transfer.Controls.Add(nudPausaRiga, 1, 3) transfer.Controls.Add(chkInvioRigaPerRiga, 2, 3) transfer.Controls.Add(New Label With {.Text = "Pausa carattere ms", .AutoSize = True, .Anchor = AnchorStyles.Left}, 0, 4) transfer.Controls.Add(nudPausaCarattere, 1, 4) transfer.Controls.Add(chkInvioCaratterePerCarattere, 2, 4) transfer.Controls.Add(New Label With {.Text = "Modo Okuma READ", .AutoSize = True, .Anchor = AnchorStyles.Left}, 0, 5) transfer.Controls.Add(chkInvioBlocchi, 1, 5) transfer.Controls.Add(New Label With {.Text = "Dim. blocco byte", .AutoSize = True, .Anchor = AnchorStyles.Left}, 2, 5) transfer.Controls.Add(nudDimensioneBlocco, 3, 5) transfer.Controls.Add(New Label With {.Text = "Pausa blocco ms", .AutoSize = True, .Anchor = AnchorStyles.Left}, 0, 6) transfer.Controls.Add(nudPausaBlocco, 1, 6) transfer.Controls.Add(New Label With {.Text = "Fine ricez. s", .AutoSize = True, .Anchor = AnchorStyles.Left}, 2, 6) transfer.Controls.Add(nudTimeoutInattivita, 3, 6) transfer.Controls.Add(New Label With {.Text = "Attesa prima invio s", .AutoSize = True, .Anchor = AnchorStyles.Left}, 0, 7) transfer.Controls.Add(nudAttesaPrimaInvio, 1, 7) transfer.Controls.Add(btnTestConnessione, 3, 7) transfer.Controls.Add(New Label With {.Text = "Attesa dopo invio s", .AutoSize = True, .Anchor = AnchorStyles.Left}, 0, 8) transfer.Controls.Add(nudAttesaDopoInvio, 1, 8) Dim hint As New Label With { .Text = "Prima di ricevere o inviare, selezionare azienda e macchina. I parametri Moxa vengono compilati dal file JSON nello start path.", .AutoSize = True, .Dock = DockStyle.Fill, .ForeColor = Color.FromArgb(70, 70, 70), .Margin = New Padding(0, 10, 0, 0) } transfer.SetColumnSpan(hint, 4) transfer.Controls.Add(hint, 0, 9) txtLog.Dock = DockStyle.Fill txtLog.Multiline = True txtLog.ScrollBars = ScrollBars.Vertical txtLog.ReadOnly = True txtLog.Font = New Font("Consolas", 10.0F) root.Controls.Add(txtLog, 0, 2) progress.Dock = DockStyle.Fill progress.Style = ProgressBarStyle.Blocks root.Controls.Add(progress, 0, 3) lockedControls.AddRange({nudPorta, nudTimeoutConnessione, nudTimeoutInattivita, cboTerminatoreInvio, nudPausaRiga, nudPausaCarattere, nudDimensioneBlocco, nudPausaBlocco, nudAttesaPrimaInvio, nudAttesaDopoInvio, chkInvioRigaPerRiga, chkInvioCaratterePerCarattere, chkInvioBlocchi}) AddHandler cboAzienda.SelectedIndexChanged, AddressOf CompanyChanged AddHandler cboMacchina.SelectedIndexChanged, AddressOf MachineChanged AddHandler mnuSblocca.Click, AddressOf UnlockClicked AddHandler mnuCambiaPassword.Click, AddressOf ChangePasswordClicked AddHandler mnuPulisciLog.Click, AddressOf ClearLogClicked AddHandler mnuInformazioni.Click, AddressOf AboutClicked AddHandler btnSfogliaInvio.Click, AddressOf SelectInputFile AddHandler btnSfogliaRicezione.Click, AddressOf SelectOutputFile AddHandler btnTestConnessione.Click, AddressOf TestConnectionClicked AddHandler btnInvia.Click, AddressOf SendClicked AddHandler btnRicevi.Click, AddressOf ReceiveClicked AddHandler btnAnnulla.Click, Sub() cts?.Cancel() txtFileRicezione.Text = GetInitialReceiveDirectory() BuildStatusTab(tabStatoMoxa) Log($"Pronto. Configurazione: {ConfigPath()}") End Sub Private Function BuildMainMenu() As MenuStrip Dim menu As New MenuStrip() Dim strumenti As New ToolStripMenuItem("Strumenti") strumenti.DropDownItems.Add(mnuSblocca) strumenti.DropDownItems.Add(mnuCambiaPassword) strumenti.DropDownItems.Add(New ToolStripSeparator()) strumenti.DropDownItems.Add(mnuPulisciLog) Dim aiuto As New ToolStripMenuItem("?") aiuto.DropDownItems.Add(mnuInformazioni) menu.Items.Add(strumenti) menu.Items.Add(aiuto) Return menu End Function Private Function GetInitialReceiveDirectory() As String If Not String.IsNullOrWhiteSpace(appConfig.LastReceiveDirectory) AndAlso Directory.Exists(appConfig.LastReceiveDirectory) Then Return appConfig.LastReceiveDirectory End If Return Environment.GetFolderPath(Environment.SpecialFolder.DesktopDirectory) End Function Private Sub BuildStatusTab(tab As TabPage) Dim root As New TableLayoutPanel With { .Dock = DockStyle.Fill, .ColumnCount = 1, .RowCount = 2, .Padding = New Padding(14) } root.RowStyles.Add(New RowStyle(SizeType.AutoSize)) root.RowStyles.Add(New RowStyle(SizeType.Percent, 100)) tab.Controls.Add(root) Dim actions As New FlowLayoutPanel With {.Dock = DockStyle.Fill, .AutoSize = True, .FlowDirection = FlowDirection.LeftToRight} ConfigureButton(btnAggiornaStatoMoxa, "Aggiorna") chkMonitorStatoMoxa.Text = "Controllo automatico" chkMonitorStatoMoxa.AutoSize = True chkMonitorStatoMoxa.Checked = True chkMonitorStatoMoxa.Margin = New Padding(16, 8, 0, 3) nudIntervalloStatoMoxa.Minimum = 5 nudIntervalloStatoMoxa.Maximum = 3600 nudIntervalloStatoMoxa.Value = Math.Max(nudIntervalloStatoMoxa.Minimum, Math.Min(nudIntervalloStatoMoxa.Maximum, appConfig.MonitorRefreshSeconds)) nudIntervalloStatoMoxa.Width = 80 actions.Controls.Add(btnAggiornaStatoMoxa) actions.Controls.Add(chkMonitorStatoMoxa) actions.Controls.Add(New Label With {.Text = "Intervallo s", .AutoSize = True, .Margin = New Padding(16, 9, 3, 3)}) actions.Controls.Add(nudIntervalloStatoMoxa) root.Controls.Add(actions, 0, 0) imgStatoMoxa.ImageSize = New Size(16, 16) imgStatoMoxa.ColorDepth = ColorDepth.Depth32Bit imgStatoMoxa.Images.Add("unknown", CreateStatusIcon(Color.Gray)) imgStatoMoxa.Images.Add("online", CreateStatusIcon(Color.FromArgb(0, 150, 70))) imgStatoMoxa.Images.Add("offline", CreateStatusIcon(Color.FromArgb(210, 40, 40))) lstStatoMoxa.Dock = DockStyle.Fill lstStatoMoxa.View = View.Details lstStatoMoxa.FullRowSelect = True lstStatoMoxa.GridLines = True lstStatoMoxa.SmallImageList = imgStatoMoxa lstStatoMoxa.Columns.Add("Macchina", 280) lstStatoMoxa.Columns.Add("IP Moxa", 140) lstStatoMoxa.Columns.Add("Porta", 70) lstStatoMoxa.Columns.Add("Stato", 130) lstStatoMoxa.Columns.Add("Ultimo controllo", 160) lstStatoMoxa.Columns.Add("Note", 220) root.Controls.Add(lstStatoMoxa, 0, 1) lockedControls.Add(chkMonitorStatoMoxa) lockedControls.Add(nudIntervalloStatoMoxa) statusTimer.Interval = CInt(nudIntervalloStatoMoxa.Value) * 1000 statusTimer.Enabled = chkMonitorStatoMoxa.Checked AddHandler btnAggiornaStatoMoxa.Click, AddressOf RefreshMoxaStatusClicked AddHandler statusTimer.Tick, AddressOf StatusTimerTick AddHandler nudIntervalloStatoMoxa.ValueChanged, AddressOf StatusIntervalChanged AddHandler chkMonitorStatoMoxa.CheckedChanged, AddressOf StatusMonitorChanged PopulateMoxaStatusList() End Sub Private Shared Function CreateStatusIcon(color As Color) As Bitmap Dim bitmap As New Bitmap(16, 16) Using g = Graphics.FromImage(bitmap) g.Clear(Color.Transparent) Using brush As New SolidBrush(color) g.FillEllipse(brush, 2, 2, 12, 12) End Using Using pen As New Pen(Color.FromArgb(80, 0, 0, 0)) g.DrawEllipse(pen, 2, 2, 12, 12) End Using End Using Return bitmap End Function Private Sub ConfigureNumericDefaults() nudPorta.Minimum = 1 nudPorta.Maximum = 65535 nudTimeoutConnessione.Minimum = 1 nudTimeoutConnessione.Maximum = 120 nudTimeoutInattivita.Minimum = 1 nudTimeoutInattivita.Maximum = 120 nudPausaRiga.Minimum = 0 nudPausaRiga.Maximum = 2000 nudPausaCarattere.Minimum = 0 nudPausaCarattere.Maximum = 200 nudDimensioneBlocco.Minimum = 1 nudDimensioneBlocco.Maximum = 1024 nudPausaBlocco.Minimum = 0 nudPausaBlocco.Maximum = 5000 nudAttesaPrimaInvio.Minimum = 0 nudAttesaPrimaInvio.Maximum = 60 nudAttesaDopoInvio.Minimum = 0 nudAttesaDopoInvio.Maximum = 30 End Sub Private Sub LoadCompanies() loadingSelection = True cboAzienda.Items.Clear() For Each company In appConfig.Companies cboAzienda.Items.Add(company.Name) Next loadingSelection = False If cboAzienda.Items.Count > 0 Then Dim index = Math.Max(0, appConfig.Companies.FindIndex(Function(c) c.Name = appConfig.DefaultCompany)) cboAzienda.SelectedIndex = index End If PopulateMoxaStatusList() End Sub Private Sub PopulateMoxaStatusList() If lstStatoMoxa.Columns.Count = 0 Then Return End If lstStatoMoxa.BeginUpdate() lstStatoMoxa.Items.Clear() lstStatoMoxa.Groups.Clear() For Each company In appConfig.Companies Dim group As New ListViewGroup(company.Name, HorizontalAlignment.Left) lstStatoMoxa.Groups.Add(group) For Each machine In company.Machines Dim item As New ListViewItem(machine.Name, "unknown") With { .Group = group, .Tag = New MoxaStatusTarget With {.Company = company.Name, .Machine = machine} } item.SubItems.Add(machine.IpMoxa) item.SubItems.Add(appConfig.DefaultSettings.Port.ToString()) item.SubItems.Add("Non controllato") item.SubItems.Add("") item.SubItems.Add(machine.Note) lstStatoMoxa.Items.Add(item) Next Next lstStatoMoxa.EndUpdate() End Sub Private Sub CompanyChanged(sender As Object, e As EventArgs) If loadingSelection OrElse cboAzienda.SelectedIndex < 0 Then Return End If Dim company = CurrentCompany() loadingSelection = True cboMacchina.Items.Clear() For Each machine In company.Machines cboMacchina.Items.Add(machine.Name) Next loadingSelection = False If cboMacchina.Items.Count > 0 Then cboMacchina.SelectedIndex = 0 End If End Sub Private Sub MachineChanged(sender As Object, e As EventArgs) If loadingSelection Then Return End If ApplyCurrentMachineSettings() End Sub Private Sub ApplyCurrentMachineSettings() Dim machine = CurrentMachine() If machine Is Nothing Then Return End If Dim defaults = appConfig.DefaultSettings txtIpMoxa.Text = machine.IpMoxa nudPorta.Value = defaults.Port nudTimeoutConnessione.Value = defaults.ConnectionTimeoutSeconds nudTimeoutInattivita.Value = defaults.ReceiveIdleTimeoutSeconds cboTerminatoreInvio.SelectedItem = defaults.SendTerminator nudPausaRiga.Value = defaults.LineDelayMs nudPausaCarattere.Value = defaults.CharDelayMs chkInvioRigaPerRiga.Checked = defaults.LinePaced chkInvioCaratterePerCarattere.Checked = defaults.CharPaced chkInvioBlocchi.Checked = defaults.BlockPaced nudDimensioneBlocco.Value = defaults.BlockSizeBytes nudPausaBlocco.Value = defaults.BlockDelayMs nudAttesaPrimaInvio.Value = defaults.WaitBeforeSendSeconds nudAttesaDopoInvio.Value = defaults.WaitAfterSendSeconds Log($"Macchina selezionata: {CurrentCompany().Name} / {machine.Name} - IP Moxa {machine.IpMoxa}.") End Sub Private Function CurrentCompany() As CompanyConfig If cboAzienda.SelectedIndex < 0 OrElse cboAzienda.SelectedIndex >= appConfig.Companies.Count Then Return Nothing End If Return appConfig.Companies(cboAzienda.SelectedIndex) End Function Private Function CurrentMachine() As MachineConfig Dim company = CurrentCompany() If company Is Nothing OrElse cboMacchina.SelectedIndex < 0 OrElse cboMacchina.SelectedIndex >= company.Machines.Count Then Return Nothing End If Return company.Machines(cboMacchina.SelectedIndex) End Function Private Sub UnlockClicked(sender As Object, e As EventArgs) If controlsUnlocked Then SetControlsUnlocked(False) Return End If If String.IsNullOrWhiteSpace(appConfig.AdminPasswordHash) OrElse String.IsNullOrWhiteSpace(appConfig.AdminPasswordSalt) Then Dim newPassword = PromptPassword("Password non configurata", "Inserire una nuova password amministratore:") If String.IsNullOrEmpty(newPassword) Then Return End If SetPassword(newPassword) SaveConfig() SetControlsUnlocked(True) Return End If Dim password = PromptPassword("Sblocca controlli", "Inserire la password:") If password Is Nothing Then Return End If If VerifyPassword(password) Then SetControlsUnlocked(True) Else MessageBox.Show(Me, "Password non corretta.", "Accesso negato", MessageBoxButtons.OK, MessageBoxIcon.Warning) End If End Sub Private Sub ChangePasswordClicked(sender As Object, e As EventArgs) If Not String.IsNullOrWhiteSpace(appConfig.AdminPasswordHash) AndAlso Not String.IsNullOrWhiteSpace(appConfig.AdminPasswordSalt) Then Dim oldPassword = PromptPassword("Cambia password", "Inserire la vecchia password:") If oldPassword Is Nothing Then Return End If If Not VerifyPassword(oldPassword) Then MessageBox.Show(Me, "Vecchia password non corretta.", "Password", MessageBoxButtons.OK, MessageBoxIcon.Warning) Return End If End If Dim newPassword = PromptPassword("Cambia password", "Inserire la nuova password:") If String.IsNullOrEmpty(newPassword) Then Return End If SetPassword(newPassword) SaveConfig() MessageBox.Show(Me, "Password aggiornata.", "Password", MessageBoxButtons.OK, MessageBoxIcon.Information) End Sub Private Sub ClearLogClicked(sender As Object, e As EventArgs) txtLog.Clear() End Sub Private Sub AboutClicked(sender As Object, e As EventArgs) Dim version = Assembly.GetExecutingAssembly().GetName().Version MessageBox.Show(Me, $"LMEMoxaTransfer{Environment.NewLine}{Environment.NewLine}Azienda: Compomac SPA{Environment.NewLine}Autore: Livio Merola{Environment.NewLine}Versione: {version}", "Informazioni su", MessageBoxButtons.OK, MessageBoxIcon.Information) End Sub Private Async Sub RefreshMoxaStatusClicked(sender As Object, e As EventArgs) Await RefreshMoxaStatusAsync() End Sub Private Async Sub StatusTimerTick(sender As Object, e As EventArgs) If chkMonitorStatoMoxa.Checked Then Await RefreshMoxaStatusAsync() End If End Sub Private Sub StatusIntervalChanged(sender As Object, e As EventArgs) statusTimer.Interval = CInt(nudIntervalloStatoMoxa.Value) * 1000 appConfig.MonitorRefreshSeconds = CInt(nudIntervalloStatoMoxa.Value) SaveConfig() End Sub Private Sub StatusMonitorChanged(sender As Object, e As EventArgs) statusTimer.Enabled = chkMonitorStatoMoxa.Checked End Sub Private Async Function RefreshMoxaStatusAsync() As Task If checkingMoxaStatus Then Return End If checkingMoxaStatus = True btnAggiornaStatoMoxa.Enabled = False Try For Each item As ListViewItem In lstStatoMoxa.Items Dim target = TryCast(item.Tag, MoxaStatusTarget) If target Is Nothing Then Continue For End If item.ImageKey = "unknown" item.SubItems(3).Text = "Controllo..." item.SubItems(4).Text = DateTime.Now.ToString("HH:mm:ss") Dim online = Await IsMoxaReachableAsync(target.Machine.IpMoxa, appConfig.DefaultSettings.Port, TimeSpan.FromMilliseconds(1200)) item.ImageKey = If(online, "online", "offline") item.SubItems(3).Text = If(online, "Raggiungibile", "Non raggiungibile") item.SubItems(4).Text = DateTime.Now.ToString("HH:mm:ss") Next Finally btnAggiornaStatoMoxa.Enabled = True checkingMoxaStatus = False End Try End Function Private Shared Async Function IsMoxaReachableAsync(ip As String, port As Integer, timeout As TimeSpan) As Task(Of Boolean) If String.IsNullOrWhiteSpace(ip) Then Return False End If Using client As New TcpClient() Try Dim connectTask = client.ConnectAsync(ip.Trim(), port) Dim completed = Await Task.WhenAny(connectTask, Task.Delay(timeout)) If completed IsNot connectTask Then Return False End If Await connectTask Return True Catch Return False End Try End Using End Function Private Sub SetControlsUnlocked(unlocked As Boolean) controlsUnlocked = unlocked For Each control In lockedControls control.Enabled = unlocked Next mnuSblocca.Text = If(unlocked, "Blocca controlli", "Sblocca controlli") End Sub Private Async Sub TestConnectionClicked(sender As Object, e As EventArgs) Await RunTransferAsync(Async Function(token) Using client = Await ConnectAsync(token) Log("Test OK: connessione TCP aperta, nessun dato inviato a Okuma.") End Using End Function) End Sub Private Shared Sub ConfigureButton(button As Button, text As String) button.Text = text button.AutoSize = True button.MinimumSize = New Size(110, 32) button.Margin = New Padding(8, 3, 0, 3) End Sub Private Sub SelectInputFile(sender As Object, e As EventArgs) Using dlg As New OpenFileDialog() dlg.Title = "Seleziona programma da inviare a Okuma" dlg.Filter = "Programmi CNC|*.min;*.eia;*.iso;*.txt;*.nc|Tutti i file|*.*" If dlg.ShowDialog(Me) = DialogResult.OK Then txtFileInvio.Text = dlg.FileName End If End Using End Sub Private Sub SelectOutputFile(sender As Object, e As EventArgs) Using dlg As New FolderBrowserDialog() dlg.Description = "Scegli la cartella dove salvare il programma ricevuto" dlg.UseDescriptionForTitle = True If Directory.Exists(txtFileRicezione.Text) Then dlg.SelectedPath = txtFileRicezione.Text End If If dlg.ShowDialog(Me) = DialogResult.OK Then txtFileRicezione.Text = dlg.SelectedPath appConfig.LastReceiveDirectory = dlg.SelectedPath SaveConfig() End If End Using End Sub Private Async Sub SendClicked(sender As Object, e As EventArgs) If CurrentMachine() Is Nothing Then MessageBox.Show(Me, "Selezionare azienda e macchina.", "Macchina mancante", MessageBoxButtons.OK, MessageBoxIcon.Warning) Return End If If Not File.Exists(txtFileInvio.Text) Then MessageBox.Show(Me, "Selezionare un file esistente da inviare.", "File mancante", MessageBoxButtons.OK, MessageBoxIcon.Warning) Return End If Await RunTransferAsync(Async Function(token) Dim fileBytes = Await File.ReadAllBytesAsync(txtFileInvio.Text, token) fileBytes = RemoveUtf8BomIfPresent(fileBytes) LogFirstBytes(fileBytes) LogNonStandardCncBytes(fileBytes) Dim blockPaced = chkInvioBlocchi.Checked Dim terminator = If(blockPaced, Encoding.ASCII.GetBytes("%" & vbCrLf), GetTerminatorBytes()) Using client = Await ConnectAsync(token) Using stream = client.GetStream() Dim waitBeforeSend = CInt(nudAttesaPrimaInvio.Value) If waitBeforeSend > 0 Then Log($"Attesa prima del primo byte: {waitBeforeSend} s.") Await Task.Delay(TimeSpan.FromSeconds(waitBeforeSend), token) End If If blockPaced Then Dim blockSize = CInt(nudDimensioneBlocco.Value) Dim blockDelayMs = CInt(nudPausaBlocco.Value) Log($"Invio Okuma READ: blocchi da {blockSize} byte con pausa {blockDelayMs} ms.") Await SendBlockPacedAsync(stream, fileBytes, blockSize, blockDelayMs, token) ElseIf chkInvioRigaPerRiga.Checked Then Await SendLinePacedAsync(stream, fileBytes, CInt(nudPausaRiga.Value), CInt(nudPausaCarattere.Value), chkInvioCaratterePerCarattere.Checked, token) Else Await stream.WriteAsync(fileBytes, token) End If If terminator.Length > 0 Then Await stream.WriteAsync(terminator, token) End If Await stream.FlushAsync(token) Dim waitAfterSend = CInt(nudAttesaDopoInvio.Value) If blockPaced Then waitAfterSend = Math.Max(waitAfterSend, 1) End If If waitAfterSend > 0 Then Await Task.Delay(TimeSpan.FromSeconds(waitAfterSend), token) End If End Using End Using Log($"Inviati {fileBytes.Length:N0} byte a Okuma.") End Function) End Sub Private Async Sub ReceiveClicked(sender As Object, e As EventArgs) If CurrentMachine() Is Nothing Then MessageBox.Show(Me, "Selezionare azienda e macchina.", "Macchina mancante", MessageBoxButtons.OK, MessageBoxIcon.Warning) Return End If If String.IsNullOrWhiteSpace(txtFileRicezione.Text) Then MessageBox.Show(Me, "Scegliere una cartella di destinazione per la ricezione.", "Cartella mancante", MessageBoxButtons.OK, MessageBoxIcon.Warning) Return End If Await RunTransferAsync(Async Function(token) Dim destinationDirectory = Path.GetFullPath(txtFileRicezione.Text) Directory.CreateDirectory(destinationDirectory) Dim temporaryPath = CreateTemporaryReceivePath() Using client = Await ConnectAsync(token) Using stream = client.GetStream() Dim received = Await ReceiveUntilIdleAsync(stream, temporaryPath, token) Log($"Ricevuti {received:N0} byte da Okuma.") End Using End Using Dim finalPath = PromptAndSaveReceivedFile(temporaryPath, destinationDirectory) Log($"File ricevuto salvato in: {finalPath}") End Function) End Sub Private Async Function RunTransferAsync(action As Func(Of CancellationToken, Task)) As Task SetBusy(True) cts = New CancellationTokenSource() Try Await action(cts.Token) Log("Operazione completata.") Catch ex As OperationCanceledException Log("Operazione annullata.") Catch ex As Exception Log("Errore: " & ex.Message) MessageBox.Show(Me, ex.Message, "Errore trasferimento", MessageBoxButtons.OK, MessageBoxIcon.Error) Finally cts.Dispose() cts = Nothing SetBusy(False) End Try End Function Private Shared Async Function SendLinePacedAsync(stream As NetworkStream, data As Byte(), lineDelayMs As Integer, charDelayMs As Integer, charPaced As Boolean, token As CancellationToken) As Task Dim start = 0 For i = 0 To data.Length - 1 If data(i) = 10 Then Await WriteChunkAsync(stream, data, start, i - start + 1, charDelayMs, charPaced, token) Await stream.FlushAsync(token) start = i + 1 If lineDelayMs > 0 Then Await Task.Delay(lineDelayMs, token) End If End If Next If start < data.Length Then Await WriteChunkAsync(stream, data, start, data.Length - start, charDelayMs, charPaced, token) End If End Function Private Shared Async Function WriteChunkAsync(stream As NetworkStream, data As Byte(), offset As Integer, count As Integer, charDelayMs As Integer, charPaced As Boolean, token As CancellationToken) As Task If Not charPaced Then Await stream.WriteAsync(data.AsMemory(offset, count), token) Return End If For i = offset To offset + count - 1 Await stream.WriteAsync(data.AsMemory(i, 1), token) If charDelayMs > 0 Then Await Task.Delay(charDelayMs, token) End If Next End Function Private Shared Async Function SendBlockPacedAsync(stream As NetworkStream, data As Byte(), blockSize As Integer, blockDelayMs As Integer, token As CancellationToken) As Task Dim offset = 0 While offset < data.Length Dim count = Math.Min(blockSize, data.Length - offset) Await stream.WriteAsync(data.AsMemory(offset, count), token) Await stream.FlushAsync(token) offset += count If offset < data.Length AndAlso blockDelayMs > 0 Then Await Task.Delay(blockDelayMs, token) End If End While End Function Private Async Function ConnectAsync(token As CancellationToken) As Task(Of TcpClient) Dim machine = CurrentMachine() If machine Is Nothing Then Throw New InvalidOperationException("Macchina non selezionata.") End If Dim ip = machine.IpMoxa.Trim() Dim port = CInt(nudPorta.Value) Dim timeout = TimeSpan.FromSeconds(CInt(nudTimeoutConnessione.Value)) Dim client As New TcpClient With {.NoDelay = True} Log($"Connessione a {ip}:{port}...") Dim connectTask = client.ConnectAsync(ip, port, token).AsTask() Dim completed = Await Task.WhenAny(connectTask, Task.Delay(timeout, token)) If completed IsNot connectTask Then client.Dispose() Throw New TimeoutException($"Connessione non riuscita entro {timeout.TotalSeconds:N0} secondi.") End If Await connectTask Log("Connessione aperta.") Return client End Function Private Async Function ReceiveUntilIdleAsync(stream As NetworkStream, destinationPath As String, token As CancellationToken) As Task(Of Long) Dim buffer(8191) As Byte Dim total As Long = 0 Dim idleTimeout = TimeSpan.FromSeconds(CInt(nudTimeoutInattivita.Value)) Log("In attesa dati. Avviare l'invio programma dal controllo Okuma.") Using output As New FileStream(destinationPath, FileMode.Create, FileAccess.Write, FileShare.Read, 8192, useAsync:=True) Do Dim readTask = stream.ReadAsync(buffer.AsMemory(0, buffer.Length), token).AsTask() Dim completed = Await Task.WhenAny(readTask, Task.Delay(idleTimeout, token)) If completed IsNot readTask Then If total = 0 Then Throw New TimeoutException("Nessun dato ricevuto prima del timeout di inattivita.") End If Exit Do End If Dim count = Await readTask If count = 0 Then Exit Do End If Await output.WriteAsync(buffer.AsMemory(0, count), token) total += count progress.Value = CInt(Math.Min(progress.Maximum, total Mod progress.Maximum)) Log($"Ricevuti {total:N0} byte...") Loop End Using Return total End Function Private Shared Function CreateTemporaryReceivePath() As String Dim tempDirectory = Path.Combine(Application.StartupPath, "RicezioniTemp") Directory.CreateDirectory(tempDirectory) Return Path.Combine(tempDirectory, $"ricezione_okuma_{DateTime.Now:yyyyMMdd_HHmmss}_{Guid.NewGuid():N}.tmp") End Function Private Shared Sub CleanupOldTemporaryReceives() Dim tempDirectory = Path.Combine(Application.StartupPath, "RicezioniTemp") If Not Directory.Exists(tempDirectory) Then Return End If For Each filePath In Directory.EnumerateFiles(tempDirectory, "*.tmp") Try If File.GetLastWriteTime(filePath) < DateTime.Now.AddDays(-7) Then File.Delete(filePath) End If Catch End Try Next End Sub Private Function PromptAndSaveReceivedFile(temporaryPath As String, destinationDirectory As String) As String If Not File.Exists(temporaryPath) Then Return temporaryPath End If Dim suggestedName = ExtractProgramFileName(File.ReadAllBytes(temporaryPath)) If String.IsNullOrWhiteSpace(suggestedName) Then suggestedName = "programma_okuma.min" Log("Nessun nome programma preceduto da $ trovato nel file ricevuto: propongo un nome predefinito.") End If Dim chosenName As String = Nothing Do chosenName = PromptReceivedFileName(suggestedName) If chosenName Is Nothing Then Log($"Nome finale annullato: il file temporaneo resta in {temporaryPath}") Return temporaryPath End If chosenName = Path.GetFileName(chosenName.Trim()) If Not String.IsNullOrWhiteSpace(chosenName) AndAlso chosenName.IndexOfAny(Path.GetInvalidFileNameChars()) < 0 Then Exit Do End If MessageBox.Show(Me, "Nome file non valido.", "Nome file", MessageBoxButtons.OK, MessageBoxIcon.Warning) Loop Dim finalPath = Path.Combine(destinationDirectory, chosenName) If File.Exists(finalPath) Then Dim overwrite = MessageBox.Show(Me, $"Il file {chosenName} esiste gia'. Sovrascriverlo?", "File esistente", MessageBoxButtons.YesNo, MessageBoxIcon.Question) If overwrite <> DialogResult.Yes Then Return PromptAndSaveReceivedFile(temporaryPath, destinationDirectory) End If End If File.Move(temporaryPath, finalPath, True) Return finalPath End Function Private Shared Function ExtractProgramFileName(data As Byte()) As String Dim invalidChars = Path.GetInvalidFileNameChars() For i = 0 To data.Length - 1 If data(i) <> AscW("$"c) Then Continue For End If Dim builder As New StringBuilder() For j = i + 1 To data.Length - 1 Dim value = data(j) If value = 0 OrElse value = &HD OrElse value = &HA OrElse value = &H9 OrElse value = AscW("$"c) Then Exit For End If If value < &H20 OrElse value > &H7E Then Exit For End If Dim ch = ChrW(value) If Array.IndexOf(invalidChars, ch) >= 0 Then Exit For End If builder.Append(ch) Next Dim candidate = builder.ToString().Trim() If candidate.Length > 0 AndAlso candidate.Contains("."c) Then Return candidate End If Next Return "" End Function Private Function PromptReceivedFileName(suggestedName As String) As String Using form As New Form With {.Text = "Nome file ricevuto", .StartPosition = FormStartPosition.CenterParent, .FormBorderStyle = FormBorderStyle.FixedDialog, .MinimizeBox = False, .MaximizeBox = False, .ClientSize = New Size(440, 140)} Dim label As New Label With {.Text = "Conferma o modifica il nome file da salvare:", .Left = 12, .Top = 12, .Width = 410, .AutoSize = False} Dim textBox As New TextBox With {.Left = 12, .Top = 42, .Width = 410, .Text = suggestedName} Dim ok As New Button With {.Text = "Salva", .Left = 256, .Top = 88, .Width = 78, .DialogResult = DialogResult.OK} Dim cancel As New Button With {.Text = "Annulla", .Left = 344, .Top = 88, .Width = 78, .DialogResult = DialogResult.Cancel} form.Controls.AddRange({label, textBox, ok, cancel}) form.AcceptButton = ok form.CancelButton = cancel textBox.SelectAll() If form.ShowDialog(Me) = DialogResult.OK Then Return textBox.Text End If End Using Return Nothing End Function Private Function GetTerminatorBytes() As Byte() Select Case CStr(cboTerminatoreInvio.SelectedItem) Case "CR" Return Encoding.ASCII.GetBytes(vbCr) Case "LF" Return Encoding.ASCII.GetBytes(vbLf) Case "CRLF" Return Encoding.ASCII.GetBytes(vbCrLf) Case "%" Return Encoding.ASCII.GetBytes("%") Case "% + CRLF" Return Encoding.ASCII.GetBytes("%" & vbCrLf) Case Else Return Array.Empty(Of Byte)() End Select End Function Private Shared Function RemoveUtf8BomIfPresent(data As Byte()) As Byte() If data.Length < 3 OrElse data(0) <> &HEF OrElse data(1) <> &HBB OrElse data(2) <> &HBF Then Return data End If Dim cleanLength = data.Length - 3 If cleanLength = 0 Then Return Array.Empty(Of Byte)() End If Dim clean(cleanLength - 1) As Byte Buffer.BlockCopy(data, 3, clean, 0, clean.Length) Return clean End Function Private Sub LogFirstBytes(data As Byte()) If data.Length = 0 Then Log("File vuoto: nessun byte da inviare prima del terminatore.") Return End If Dim previewLength = Math.Min(16, data.Length) Dim preview As New StringBuilder() For i = 0 To previewLength - 1 If i > 0 Then preview.Append(" ") End If preview.Append(data(i).ToString("X2")) Next Log($"Primi byte inviati: {preview}") If data(0) > &H7F Then Log($"Attenzione: il primo byte e' > 7 bit ({data(0):X2}). Per Okuma 7E1 puo' essere un carattere non valido.") End If End Sub Private Sub LogNonStandardCncBytes(data As Byte()) Dim firstInvalidPosition As Integer = -1 Dim firstInvalidValue As Byte = 0 Dim invalidCount As Integer For i = 0 To data.Length - 1 Dim value = data(i) Dim isAllowedControl = value = &H9 OrElse value = &HA OrElse value = &HD Dim isPrintableAscii = value >= &H20 AndAlso value <= &H7E If Not isAllowedControl AndAlso Not isPrintableAscii Then invalidCount += 1 If firstInvalidPosition < 0 Then firstInvalidPosition = i firstInvalidValue = value End If End If Next If invalidCount > 0 Then Log($"Avviso: trovati {invalidCount} byte di controllo/non ASCII. Primo valore 0x{firstInvalidValue:X2} alla posizione {firstInvalidPosition}. Invio comunque.") End If End Sub Private Sub SetBusy(isBusy As Boolean) btnInvia.Enabled = Not isBusy btnRicevi.Enabled = Not isBusy btnTestConnessione.Enabled = Not isBusy btnSfogliaInvio.Enabled = Not isBusy btnSfogliaRicezione.Enabled = Not isBusy mnuSblocca.Enabled = Not isBusy mnuCambiaPassword.Enabled = Not isBusy mnuPulisciLog.Enabled = Not isBusy btnAnnulla.Enabled = isBusy If Not isBusy Then For Each control In lockedControls control.Enabled = controlsUnlocked Next Else For Each control In lockedControls control.Enabled = False Next End If progress.Style = If(isBusy, ProgressBarStyle.Marquee, ProgressBarStyle.Blocks) If Not isBusy Then progress.Value = 0 End If End Sub Private Sub Log(message As String) If InvokeRequired Then BeginInvoke(Sub() Log(message)) Return End If txtLog.AppendText($"{DateTime.Now:HH:mm:ss} {message}{Environment.NewLine}") End Sub Private Shared Function ConfigPath() As String Return Path.Combine(Application.StartupPath, ConfigFileName) End Function Private Function LoadOrCreateConfig() As AppConfig Dim path = ConfigPath() If File.Exists(path) Then Dim loaded = JsonSerializer.Deserialize(Of AppConfig)(File.ReadAllText(path), JsonOptions()) If loaded IsNot Nothing Then Return loaded End If End If Dim created = CreateDefaultConfig() appConfig = created SetPassword("moxa") SaveConfig() Return appConfig End Function Private Sub SaveConfig() File.WriteAllText(ConfigPath(), JsonSerializer.Serialize(appConfig, JsonOptions())) End Sub Private Shared Function JsonOptions() As JsonSerializerOptions Return New JsonSerializerOptions With {.WriteIndented = True, .PropertyNameCaseInsensitive = True} End Function Private Function VerifyPassword(password As String) As Boolean Dim salt = Convert.FromBase64String(appConfig.AdminPasswordSalt) Dim expected = Convert.FromBase64String(appConfig.AdminPasswordHash) Using pbkdf2 As New Rfc2898DeriveBytes(password, salt, 100000, HashAlgorithmName.SHA256) Dim actual = pbkdf2.GetBytes(expected.Length) Return CryptographicOperations.FixedTimeEquals(actual, expected) End Using End Function Private Sub SetPassword(password As String) Dim salt(15) As Byte RandomNumberGenerator.Fill(salt) Using pbkdf2 As New Rfc2898DeriveBytes(password, salt, 100000, HashAlgorithmName.SHA256) appConfig.AdminPasswordSalt = Convert.ToBase64String(salt) appConfig.AdminPasswordHash = Convert.ToBase64String(pbkdf2.GetBytes(32)) End Using End Sub Private Function PromptPassword(title As String, prompt As String) As String Using form As New Form With {.Text = title, .StartPosition = FormStartPosition.CenterParent, .FormBorderStyle = FormBorderStyle.FixedDialog, .MinimizeBox = False, .MaximizeBox = False, .ClientSize = New Size(360, 130)} Dim label As New Label With {.Text = prompt, .Left = 12, .Top = 12, .Width = 330, .AutoSize = False} Dim textBox As New TextBox With {.Left = 12, .Top = 42, .Width = 330, .UseSystemPasswordChar = True} Dim ok As New Button With {.Text = "OK", .Left = 176, .Top = 84, .Width = 78, .DialogResult = DialogResult.OK} Dim cancel As New Button With {.Text = "Annulla", .Left = 264, .Top = 84, .Width = 78, .DialogResult = DialogResult.Cancel} form.Controls.AddRange({label, textBox, ok, cancel}) form.AcceptButton = ok form.CancelButton = cancel If form.ShowDialog(Me) = DialogResult.OK Then Return textBox.Text End If End Using Return Nothing End Function Private Shared Function CreateDefaultConfig() As AppConfig Return New AppConfig With { .DefaultCompany = "SECUREX", .MonitorRefreshSeconds = 30, .DefaultSettings = New TransferSettings With { .Port = 10001, .ConnectionTimeoutSeconds = 10, .ReceiveIdleTimeoutSeconds = 15, .SendTerminator = "%", .LineDelayMs = 100, .CharDelayMs = 10, .LinePaced = True, .CharPaced = False, .BlockPaced = False, .BlockSizeBytes = 32, .BlockDelayMs = 100, .WaitBeforeSendSeconds = 2, .WaitAfterSendSeconds = 2 }, .Companies = New List(Of CompanyConfig) From { New CompanyConfig With {.Name = "SECUREX", .Machines = New List(Of MachineConfig) From { New MachineConfig With {.Name = "M4 - N2LB 300", .IpMoxa = "192.168.0.101", .IpMachineWise = "192.168.0.214", .Note = "OSP-U100L"}, New MachineConfig With {.Name = "M5 - N3LB 300", .IpMoxa = "192.168.0.146", .IpMachineWise = "192.168.0.47", .Note = "OSP-E100L"}, New MachineConfig With {.Name = "M8 - CENTRO OKUMA", .IpMoxa = "192.168.0.113", .IpMachineWise = "192.168.0.68", .Note = "OSP-P200M"}, New MachineConfig With {.Name = "M11 - TORNIO OKUMA LB25I-N", .IpMoxa = "192.168.0.88", .IpMachineWise = "192.168.0.223"}, New MachineConfig With {.Name = "M12 - TORNIO OKUMA LB 400", .IpMoxa = "192.168.0.99", .IpMachineWise = "192.168.0.66"} }}, New CompanyConfig With {.Name = "EMILIA", .Machines = New List(Of MachineConfig) From { New MachineConfig With {.Name = "M1", .IpMoxa = "192.168.30.8", .IpMachineWise = "192.168.30.221"}, New MachineConfig With {.Name = "M2", .IpMoxa = "192.168.30.9", .IpMachineWise = "192.168.30.222"}, New MachineConfig With {.Name = "M3", .IpMoxa = "192.168.30.10", .IpMachineWise = "192.168.30.223"}, New MachineConfig With {.Name = "M4 - CENTRO LAVORO OKUMA", .IpMoxa = "192.168.30.11", .IpMachineWise = "192.168.30.224"}, New MachineConfig With {.Name = "M5", .IpMoxa = "192.168.30.12", .IpMachineWise = "192.168.30.225"}, New MachineConfig With {.Name = "M6", .IpMoxa = "192.168.30.13", .IpMachineWise = "192.168.30.226"} }}, New CompanyConfig With {.Name = "SMART", .Machines = New List(Of MachineConfig) From { New MachineConfig With {.Name = "M1", .IpMoxa = "192.168.35.10", .IpMachineWise = "192.168.35.212"}, New MachineConfig With {.Name = "M2", .IpMoxa = "192.168.35.11", .IpMachineWise = "192.168.35.211"}, New MachineConfig With {.Name = "M3", .IpMoxa = "192.168.35.12", .IpMachineWise = "192.168.35.213"}, New MachineConfig With {.Name = "M4 - CENTRO LAVORO OKUMA", .IpMoxa = "192.168.35.13", .IpMachineWise = "192.168.35.214"} }} } } End Function End Class Public Class AppConfig Public Property AdminPasswordHash As String = "" Public Property AdminPasswordSalt As String = "" Public Property DefaultCompany As String = "" Public Property MonitorRefreshSeconds As Integer = 30 Public Property LastReceiveDirectory As String = "" Public Property DefaultSettings As New TransferSettings() Public Property Companies As New List(Of CompanyConfig)() End Class Public Class CompanyConfig Public Property Name As String = "" Public Property Machines As New List(Of MachineConfig)() End Class Public Class MachineConfig Public Property Name As String = "" Public Property IpMoxa As String = "" Public Property IpMachineWise As String = "" Public Property Note As String = "" End Class Friend Class MoxaStatusTarget Public Property Company As String = "" Public Property Machine As MachineConfig End Class Public Class TransferSettings Public Property Port As Integer = 10001 Public Property ConnectionTimeoutSeconds As Integer = 10 Public Property ReceiveIdleTimeoutSeconds As Integer = 15 Public Property SendTerminator As String = "%" Public Property LineDelayMs As Integer = 100 Public Property CharDelayMs As Integer = 10 Public Property LinePaced As Boolean = True Public Property CharPaced As Boolean = False Public Property BlockPaced As Boolean = False Public Property BlockSizeBytes As Integer = 32 Public Property BlockDelayMs As Integer = 100 Public Property WaitBeforeSendSeconds As Integer = 2 Public Property WaitAfterSendSeconds As Integer = 2 End Class