From bf4fb469da9ff9b4643e6b2e5ae38a9ea4714daa Mon Sep 17 00:00:00 2001 From: Michal Buzik Date: Sun, 7 Sep 2025 22:13:58 +0200 Subject: [PATCH] Add initial implementation for AppDiagnostic project This commit introduces the AppDiagnostic project structure, including core components like `MainForm`, logging utilities (`LogAdapter`, `LogFilter`), and configuration files. It integrates with `log4net` for logging and uses `SharedComponents` for shared utilities. --- AppDiagnostic/App.config | 18 + AppDiagnostic/AppDiagnostic.csproj | 101 +++++ AppDiagnostic/BufferedListView.cs | 16 + AppDiagnostic/DiagApi.cs | 32 ++ AppDiagnostic/LogAdapter.cs | 174 ++++++++ AppDiagnostic/LogFilter.cs | 91 ++++ AppDiagnostic/MainForm.Designer.cs | 278 ++++++++++++ AppDiagnostic/MainForm.cs | 266 ++++++++++++ AppDiagnostic/MainForm.resx | 120 +++++ AppDiagnostic/Program.cs | 22 + AppDiagnostic/Properties/AssemblyInfo.cs | 33 ++ .../Properties/Resources.Designer.cs | 62 +++ AppDiagnostic/Properties/Resources.resx | 117 +++++ AppDiagnostic/Properties/Settings.settings | 7 + AppDiagnostic/packages.config | 4 + Config/Data.cs | 103 +++++ Config/Formulas.cs | 410 ++++++++++++++++++ SchematicDrawing/Pictures/Tank-L-hot.png | Bin 0 -> 842 bytes SchematicDrawing/Pictures/Tank-M-hot.png | Bin 0 -> 768 bytes SchematicDrawing/Pictures/Tank-S-hot.png | Bin 0 -> 746 bytes SchematicDrawing/Pictures/Tank-XL-hot.png | Bin 0 -> 894 bytes .../Pictures/ValveSw-L-closed.png | Bin 0 -> 1116 bytes .../Pictures/ValveSw-L-open-dry.png | Bin 0 -> 1196 bytes SchematicDrawing/Pictures/ValveSw-L-open.png | Bin 0 -> 1189 bytes .../Pictures/ValveSw-L-vacuum.png | Bin 0 -> 1211 bytes .../Pictures/ValveSw-M-closed.png | Bin 0 -> 1033 bytes .../Pictures/ValveSw-M-open-dry.png | Bin 0 -> 1047 bytes SchematicDrawing/Pictures/ValveSw-M-open.png | Bin 0 -> 1018 bytes .../Pictures/ValveSw-M-vacuum.png | Bin 0 -> 1051 bytes .../Pictures/ValveSw-S-closed.png | Bin 0 -> 667 bytes .../Pictures/ValveSw-S-open-dry.png | Bin 0 -> 667 bytes SchematicDrawing/Pictures/ValveSw-S-open.png | Bin 0 -> 661 bytes .../Pictures/ValveSw-S-vacuum.png | Bin 0 -> 661 bytes .../Pictures/ValveSw-XL-closed.png | Bin 0 -> 1183 bytes .../Pictures/ValveSw-XL-open-dry.png | Bin 0 -> 1212 bytes SchematicDrawing/Pictures/ValveSw-XL-open.png | Bin 0 -> 1243 bytes .../Pictures/ValveSw-XL-vacuum.png | Bin 0 -> 1245 bytes SchematicDrawing/SchematicDrawing.csproj | 16 + SharedComponents/LiveLogCache.cs | 234 ++++++++++ SharedComponents/SharedComponents.csproj | 10 + ....GeneratedMSBuildEditorConfig.editorconfig | 5 + .../net472/SharedComponents.GlobalUsings.g.cs | 7 + .../obj/SharedComponents.csproj.nuget.g.props | 15 + .../SharedComponents.csproj.nuget.g.targets | 2 + TBF/Tools/ByteFormatter.cs | 62 +++ TBF/Tools/IniUtil.cs | 108 +++++ TBF/Tools/LogChecker.cs | 50 +++ TBF/Tools/XmlTools.cs | 30 ++ TBF/UI/Shared/SuggestComboBox.cs | 240 ++++++++++ 49 files changed, 2633 insertions(+) create mode 100644 AppDiagnostic/App.config create mode 100644 AppDiagnostic/AppDiagnostic.csproj create mode 100644 AppDiagnostic/BufferedListView.cs create mode 100644 AppDiagnostic/DiagApi.cs create mode 100644 AppDiagnostic/LogAdapter.cs create mode 100644 AppDiagnostic/LogFilter.cs create mode 100644 AppDiagnostic/MainForm.Designer.cs create mode 100644 AppDiagnostic/MainForm.cs create mode 100644 AppDiagnostic/MainForm.resx create mode 100644 AppDiagnostic/Program.cs create mode 100644 AppDiagnostic/Properties/AssemblyInfo.cs create mode 100644 AppDiagnostic/Properties/Resources.Designer.cs create mode 100644 AppDiagnostic/Properties/Resources.resx create mode 100644 AppDiagnostic/Properties/Settings.settings create mode 100644 AppDiagnostic/packages.config create mode 100644 Config/Data.cs create mode 100644 Config/Formulas.cs create mode 100644 SchematicDrawing/Pictures/Tank-L-hot.png create mode 100644 SchematicDrawing/Pictures/Tank-M-hot.png create mode 100644 SchematicDrawing/Pictures/Tank-S-hot.png create mode 100644 SchematicDrawing/Pictures/Tank-XL-hot.png create mode 100644 SchematicDrawing/Pictures/ValveSw-L-closed.png create mode 100644 SchematicDrawing/Pictures/ValveSw-L-open-dry.png create mode 100644 SchematicDrawing/Pictures/ValveSw-L-open.png create mode 100644 SchematicDrawing/Pictures/ValveSw-L-vacuum.png create mode 100644 SchematicDrawing/Pictures/ValveSw-M-closed.png create mode 100644 SchematicDrawing/Pictures/ValveSw-M-open-dry.png create mode 100644 SchematicDrawing/Pictures/ValveSw-M-open.png create mode 100644 SchematicDrawing/Pictures/ValveSw-M-vacuum.png create mode 100644 SchematicDrawing/Pictures/ValveSw-S-closed.png create mode 100644 SchematicDrawing/Pictures/ValveSw-S-open-dry.png create mode 100644 SchematicDrawing/Pictures/ValveSw-S-open.png create mode 100644 SchematicDrawing/Pictures/ValveSw-S-vacuum.png create mode 100644 SchematicDrawing/Pictures/ValveSw-XL-closed.png create mode 100644 SchematicDrawing/Pictures/ValveSw-XL-open-dry.png create mode 100644 SchematicDrawing/Pictures/ValveSw-XL-open.png create mode 100644 SchematicDrawing/Pictures/ValveSw-XL-vacuum.png create mode 100644 SharedComponents/LiveLogCache.cs create mode 100644 SharedComponents/SharedComponents.csproj create mode 100644 SharedComponents/obj/Debug/net472/SharedComponents.GeneratedMSBuildEditorConfig.editorconfig create mode 100644 SharedComponents/obj/Debug/net472/SharedComponents.GlobalUsings.g.cs create mode 100644 SharedComponents/obj/SharedComponents.csproj.nuget.g.props create mode 100644 SharedComponents/obj/SharedComponents.csproj.nuget.g.targets create mode 100644 TBF/Tools/ByteFormatter.cs create mode 100644 TBF/Tools/IniUtil.cs create mode 100644 TBF/Tools/LogChecker.cs create mode 100644 TBF/Tools/XmlTools.cs create mode 100644 TBF/UI/Shared/SuggestComboBox.cs diff --git a/AppDiagnostic/App.config b/AppDiagnostic/App.config new file mode 100644 index 000000000..3bedfc101 --- /dev/null +++ b/AppDiagnostic/App.config @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/AppDiagnostic/AppDiagnostic.csproj b/AppDiagnostic/AppDiagnostic.csproj new file mode 100644 index 000000000..65f8e8e7a --- /dev/null +++ b/AppDiagnostic/AppDiagnostic.csproj @@ -0,0 +1,101 @@ + + + + + Debug + AnyCPU + {FA9ABAD1-7184-4295-ADE8-D44F2E3DE6B2} + WinExe + AppDiagnostic + AppDiagnostic + v4.7.2 + 512 + true + true + + + AnyCPU + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + AnyCPU + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + ..\packages\log4net.3.0.3\lib\net462\log4net.dll + + + + + + + + + + + + + + + + + + Component + + + + + + Form + + + MainForm.cs + + + + + MainForm.cs + + + ResXFileCodeGenerator + Resources.Designer.cs + Designer + + + True + Resources.resx + + + + SettingsSingleFileGenerator + Settings.Designer.cs + + + True + Settings.settings + True + + + + + + + + {8f942729-f454-4c99-ba6c-746962065ae3} + SharedComponents + + + + \ No newline at end of file diff --git a/AppDiagnostic/BufferedListView.cs b/AppDiagnostic/BufferedListView.cs new file mode 100644 index 000000000..c33523f7f --- /dev/null +++ b/AppDiagnostic/BufferedListView.cs @@ -0,0 +1,16 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Windows.Forms; + +public class BufferedListView : ListView +{ + public BufferedListView() + { + // Zapne dvojité bufferovanie pre ListView + this.SetStyle(ControlStyles.OptimizedDoubleBuffer | ControlStyles.AllPaintingInWmPaint, true); + this.UpdateStyles(); + } +} \ No newline at end of file diff --git a/AppDiagnostic/DiagApi.cs b/AppDiagnostic/DiagApi.cs new file mode 100644 index 000000000..2ea6b1064 --- /dev/null +++ b/AppDiagnostic/DiagApi.cs @@ -0,0 +1,32 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Windows.Forms; + +namespace AppDiagnostic +{ + public class DiagApi + { + private static MainForm diagWindow; // Udržiava referenciu na existujúce okno + + public DiagApi() + { + if (diagWindow == null || diagWindow.IsDisposed) // Ak okno neexistuje, vytvoríme ho + { + diagWindow = new MainForm(); + diagWindow.FormClosing += (s, e) => + { + diagWindow = null; // Keď sa zavrie, vyčistíme referenciu + }; + diagWindow.Show(); + } + else + { + diagWindow.BringToFront(); // Ak už beží, len ho presunieme na vrch + diagWindow.WindowState = FormWindowState.Normal; // Ak je minimalizované, obnovíme ho + } + } + } +} diff --git a/AppDiagnostic/LogAdapter.cs b/AppDiagnostic/LogAdapter.cs new file mode 100644 index 000000000..33a351c7e --- /dev/null +++ b/AppDiagnostic/LogAdapter.cs @@ -0,0 +1,174 @@ +using SharedComponents; +using System; +using System.Collections.Generic; +using System.Drawing; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Windows.Forms; + +namespace AppDiagnostic +{ + public class LogAdapter + { + private ListView _listView; + public string _filterText = string.Empty; + private bool _isUserScrolling = false; // Indikátor, že používateľ manuálne scrolluje + private bool _autoScrollEnabled = true; // Indikátor, že automatické scrollovanie je povolené + private Timer _refreshTimer; + private const int RefreshInterval = 1000; + + public LogAdapter(ListView listView) + { + _listView = listView; + + // Nastavenie ListView + _listView.View = View.Details; + _listView.Columns.Clear(); + _listView.Columns.Add("Logs"); + + _listView.Scrollable = true; + + // Nastavenie šírky stĺpca na celú šírku ListView + AdjustColumnWidth(); + + // Udalosť pre dynamickú zmenu veľkosti stĺpca pri zmene veľkosti ListView + _listView.Resize += (sender, args) => AdjustColumnWidth(); + + // Pridanie udalosti pre manuálne skrolovanie + _listView.MouseWheel += ListView_MouseWheel; + + // Inicializácia časovača na pravidelný refresh + _refreshTimer = new Timer { Interval = RefreshInterval }; + _refreshTimer.Tick += (sender, args) => RefreshListView(); + _refreshTimer.Start(); + } + + private void ListView_MouseWheel(object sender, MouseEventArgs e) + { + _isUserScrolling = true; + + // Ak sa manuálnym scrollovaním používateľ dostane na koniec, obnovíme automatické scrollovanie + if (IsOnLastItem()) + { + _isUserScrolling = false; + _autoScrollEnabled = true; + } + else + { + _autoScrollEnabled = false; + } + } + + public void RefreshListView() + { + //LiveLogCache.Instance.AddLog(string.Format("-------------------------------RunDeviceAfter()-------------------------------")); + + if (_listView.InvokeRequired) + { + _listView.Invoke(new Action(RefreshListView)); + return; + } + + _listView.BeginUpdate(); + try + { + // Získanie pozície prvého viditeľného prvku pre stabilizáciu scrollovania + int topIndexBeforeRefresh = _listView.TopItem?.Index ?? 0; + + // Pred pridaním nových položiek si pamätáme, či bol používateľ na poslednej položke + bool wasOnLastItem = IsOnLastItem(); + + // Uchovanie aktuálne označených položiek (indexov) + var selectedIndices = _listView.SelectedIndices.Cast().ToList(); + + // Načítanie logov + var logs = LiveLogCache.Instance.GetLogs(0, LiveLogCache.Instance.GetCount()) + .Where(log => string.IsNullOrEmpty(_filterText) || + log.IndexOf(_filterText, StringComparison.OrdinalIgnoreCase) >= 0) + .ToList(); + + // Vymazanie existujúcich položiek a pridanie nových + _listView.Items.Clear(); + + foreach (var log in logs) + { + var item = new ListViewItem(log) + { + BackColor = _listView.Items.Count % 2 == 0 ? Color.White : Color.FromArgb(240, 240, 240) + }; + _listView.Items.Add(item); + } + + // Obnovenie označených položiek + foreach (var index in selectedIndices) + { + if (index < _listView.Items.Count) + { + _listView.Items[index].Selected = true; + } + } + + // Ak bol používateľ na poslednom prvku, nastavíme focus a scroll na posledný prvok + if (_autoScrollEnabled && wasOnLastItem && _listView.Items.Count > 0) + { + var lastItemIndex = _listView.Items.Count - 1; + _listView.EnsureVisible(lastItemIndex); + _listView.Items[lastItemIndex].Focused = true; + } + else + { + // Ak používateľ nebol na spodku, vrátime sa na predchádzajúcu pozíciu scrollu + if (topIndexBeforeRefresh < _listView.Items.Count) + { + _listView.TopItem = _listView.Items[topIndexBeforeRefresh]; + } + } + } + finally + { + _listView.EndUpdate(); + } + } + + + + public void ApplyFilter(string filterText) + { + _filterText = filterText?.Trim() ?? string.Empty; + RefreshListView(); + } + + private bool IsOnLastItem() + { + if (_listView.Items.Count == 0) + return false; + + // Získame poslednú položku + int lastItemIndex = _listView.Items.Count - 1; + var lastItem = _listView.Items[lastItemIndex]; + + // Overíme, či je posledná položka úplne viditeľná + return lastItem.Bounds.Bottom <= _listView.ClientRectangle.Bottom; + } + + public void EnableAutoScroll() + { + _autoScrollEnabled = true; + } + + public void DisableAutoScroll() + { + _autoScrollEnabled = false; + } + + private void AdjustColumnWidth() + { + if (_listView.Columns.Count > 0) + { + _listView.Columns[0].Width = _listView.ClientSize.Width; + } + } + } + +} diff --git a/AppDiagnostic/LogFilter.cs b/AppDiagnostic/LogFilter.cs new file mode 100644 index 000000000..6a8ec4f91 --- /dev/null +++ b/AppDiagnostic/LogFilter.cs @@ -0,0 +1,91 @@ +using SharedComponents; +using System; +using System.Collections.Generic; +using System.Drawing; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Windows.Forms; + +namespace AppDiagnostic +{ + public class LogFilter + { + private ListView _listView; + private string _filterText = string.Empty; // Pre uloženie aktuálneho filtrovaného reťazca + + // Konstruktor + public LogFilter(ListView listView) + { + _listView = listView; + } + + // Metóda na aplikovanie filtra na ListView + public void ApplyFilter(string filterText) + { + _filterText = filterText.ToLower(); // Ukladáme text filtra bez ohľadu na veľkosť písmen + + // Po aplikovaní filtra obnovíme zobrazenie + RefreshListView(); + } + + public void RefreshListView() + { + try + { + // Skontrolujeme, či sme na hlavnom vlákne a v prípade potreby použijeme Invoke + if (_listView.InvokeRequired) + { + // Použijeme Invoke, ak nie sme na hlavnom vlákne + _listView.Invoke(new Action(RefreshListView)); + } + else + { + // Vymazanie existujúcich položiek + _listView.Items.Clear(); + + // Načítame všetky logy (ideálne z cache alebo databázy) + var logs = LiveLogCache.Instance.GetLogs(0, LiveLogCache.Instance.Logs.Count); + + // Pridáme len tie logy, ktoré spĺňajú filter + foreach (var log in logs) + { + if (log.ToLower().Contains(_filterText)) // Kontrola, či log obsahuje filter text + { + var listViewItem = new ListViewItem(log); + + // Striedanie farieb riadkov + if (_listView.Items.Count % 2 == 0) // Párny index => biela + { + listViewItem.BackColor = Color.White; + } + else // Nepárny index => svetlá sivá + { + listViewItem.BackColor = Color.FromArgb(240, 240, 240); // Veľmi svetlá sivá + } + + // Pridanie efektu pre novú položku + ApplyNewItemEffect(listViewItem); + + // Pridanie položky do ListView + _listView.Items.Add(listViewItem); + } + } + } + } + catch (Exception ex) + { + // Zachytíme výnimku, ak sa niečo pokazí, a zobrazíme ju užívateľovi + MessageBox.Show($"An error occurred while refreshing the log view: {ex.Message}", "Error", MessageBoxButtons.OK, MessageBoxIcon.Error); + } + } + + // Môžete pridať ďalšie metódy na zobrazenie alebo efekt pre nové položky, ak je to potrebné + private void ApplyNewItemEffect(ListViewItem item) + { + // Prípadný efekt pre nový pridaný log + // Môžete pridať animáciu alebo zmenu farby atď. + item.ForeColor = Color.Green; // Napríklad zmeníme farbu písma na zelenú + } + } +} diff --git a/AppDiagnostic/MainForm.Designer.cs b/AppDiagnostic/MainForm.Designer.cs new file mode 100644 index 000000000..1eef3958d --- /dev/null +++ b/AppDiagnostic/MainForm.Designer.cs @@ -0,0 +1,278 @@ +using System.Windows.Forms; + +namespace AppDiagnostic +{ + partial class MainForm + { + /// + /// Required designer variable. + /// + private System.ComponentModel.IContainer components = null; + + /// + /// Clean up any resources being used. + /// + /// true if managed resources should be disposed; otherwise, false. + protected override void Dispose(bool disposing) + { + if (disposing && (components != null)) + { + components.Dispose(); + } + base.Dispose(disposing); + } + + #region Windows Form Designer generated code + //private BufferedListView memoListView; + private System.Windows.Forms.ColumnHeader columnHeaderTime; + private System.Windows.Forms.ColumnHeader columnHeaderMessage; + + /// + /// Required method for Designer support - do not modify + /// the contents of this method with the code editor. + /// + private void InitializeComponent() + { + this.columnHeaderTime = ((System.Windows.Forms.ColumnHeader)(new System.Windows.Forms.ColumnHeader())); + this.columnHeaderMessage = ((System.Windows.Forms.ColumnHeader)(new System.Windows.Forms.ColumnHeader())); + this.panel1 = new System.Windows.Forms.Panel(); + this.refreshMemoButton = new System.Windows.Forms.Button(); + this.button6 = new System.Windows.Forms.Button(); + this.button7 = new System.Windows.Forms.Button(); + this.filterManagementPanel = new System.Windows.Forms.Panel(); + this.button5 = new System.Windows.Forms.Button(); + this.filterButton = new System.Windows.Forms.Button(); + this.filterTextBox = new System.Windows.Forms.TextBox(); + this.label2 = new System.Windows.Forms.Label(); + this.wndControlPanel = new System.Windows.Forms.Panel(); + this.button2 = new System.Windows.Forms.Button(); + this.flowControlsPanel = new System.Windows.Forms.Panel(); + this.runButton = new System.Windows.Forms.Button(); + this.stopButton = new System.Windows.Forms.Button(); + this.label1 = new System.Windows.Forms.Label(); + this.memoListView = new BufferedListView(); + this.panel1.SuspendLayout(); + this.filterManagementPanel.SuspendLayout(); + this.wndControlPanel.SuspendLayout(); + this.flowControlsPanel.SuspendLayout(); + this.SuspendLayout(); + // + // columnHeaderTime + // + this.columnHeaderTime.Text = "Time"; + this.columnHeaderTime.Width = 150; + // + // columnHeaderMessage + // + this.columnHeaderMessage.Text = "Message"; + this.columnHeaderMessage.Width = 350; + // + // panel1 + // + this.panel1.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left))); + this.panel1.Controls.Add(this.refreshMemoButton); + this.panel1.Controls.Add(this.button6); + this.panel1.Controls.Add(this.button7); + this.panel1.Location = new System.Drawing.Point(189, 356); + this.panel1.Name = "panel1"; + this.panel1.Size = new System.Drawing.Size(283, 26); + this.panel1.TabIndex = 16; + // + // refreshMemoButton + // + this.refreshMemoButton.Location = new System.Drawing.Point(108, 0); + this.refreshMemoButton.Name = "refreshMemoButton"; + this.refreshMemoButton.Size = new System.Drawing.Size(84, 26); + this.refreshMemoButton.TabIndex = 17; + this.refreshMemoButton.Text = "Refresh memo"; + this.refreshMemoButton.UseVisualStyleBackColor = true; + this.refreshMemoButton.Click += new System.EventHandler(this.refreshMemoButton_Click); + // + // button6 + // + this.button6.Location = new System.Drawing.Point(0, 0); + this.button6.Name = "button6"; + this.button6.Size = new System.Drawing.Size(102, 26); + this.button6.TabIndex = 4; + this.button6.Text = "Clean diag cache"; + this.button6.UseVisualStyleBackColor = true; + this.button6.Click += new System.EventHandler(this.button6_Click); + // + // button7 + // + this.button7.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right))); + this.button7.Location = new System.Drawing.Point(198, 0); + this.button7.Name = "button7"; + this.button7.Size = new System.Drawing.Size(72, 26); + this.button7.TabIndex = 5; + this.button7.Text = "Save logs"; + this.button7.UseVisualStyleBackColor = true; + this.button7.Click += new System.EventHandler(this.button7_Click); + // + // filterManagementPanel + // + this.filterManagementPanel.Controls.Add(this.button5); + this.filterManagementPanel.Controls.Add(this.filterButton); + this.filterManagementPanel.Controls.Add(this.filterTextBox); + this.filterManagementPanel.Controls.Add(this.label2); + this.filterManagementPanel.Location = new System.Drawing.Point(127, 6); + this.filterManagementPanel.Name = "filterManagementPanel"; + this.filterManagementPanel.Size = new System.Drawing.Size(393, 26); + this.filterManagementPanel.TabIndex = 15; + // + // button5 + // + this.button5.Enabled = false; + this.button5.Location = new System.Drawing.Point(294, 4); + this.button5.Name = "button5"; + this.button5.Size = new System.Drawing.Size(81, 20); + this.button5.TabIndex = 11; + this.button5.Text = "Add new filter"; + this.button5.UseVisualStyleBackColor = true; + // + // filterButton + // + this.filterButton.Location = new System.Drawing.Point(225, 4); + this.filterButton.Name = "filterButton"; + this.filterButton.Size = new System.Drawing.Size(64, 20); + this.filterButton.TabIndex = 10; + this.filterButton.Text = "Set filter"; + this.filterButton.UseVisualStyleBackColor = true; + this.filterButton.Click += new System.EventHandler(this.filterButton_Click_1); + // + // filterTextBox + // + this.filterTextBox.Location = new System.Drawing.Point(33, 4); + this.filterTextBox.Name = "filterTextBox"; + this.filterTextBox.Size = new System.Drawing.Size(187, 20); + this.filterTextBox.TabIndex = 7; + // + // label2 + // + this.label2.AutoSize = true; + this.label2.Location = new System.Drawing.Point(3, 8); + this.label2.Name = "label2"; + this.label2.Size = new System.Drawing.Size(29, 13); + this.label2.TabIndex = 6; + this.label2.Text = "Filter"; + // + // wndControlPanel + // + this.wndControlPanel.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right))); + this.wndControlPanel.Controls.Add(this.button2); + this.wndControlPanel.Location = new System.Drawing.Point(831, 356); + this.wndControlPanel.Name = "wndControlPanel"; + this.wndControlPanel.Size = new System.Drawing.Size(70, 26); + this.wndControlPanel.TabIndex = 14; + // + // button2 + // + this.button2.Location = new System.Drawing.Point(3, 0); + this.button2.Name = "button2"; + this.button2.Size = new System.Drawing.Size(64, 26); + this.button2.TabIndex = 2; + this.button2.Text = "Close"; + this.button2.UseVisualStyleBackColor = true; + this.button2.Click += new System.EventHandler(this.button2_Click); + // + // flowControlsPanel + // + this.flowControlsPanel.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left))); + this.flowControlsPanel.Controls.Add(this.runButton); + this.flowControlsPanel.Controls.Add(this.stopButton); + this.flowControlsPanel.Location = new System.Drawing.Point(12, 356); + this.flowControlsPanel.Name = "flowControlsPanel"; + this.flowControlsPanel.Size = new System.Drawing.Size(133, 26); + this.flowControlsPanel.TabIndex = 13; + // + // runButton + // + this.runButton.Enabled = false; + this.runButton.Location = new System.Drawing.Point(0, 0); + this.runButton.Name = "runButton"; + this.runButton.Size = new System.Drawing.Size(64, 26); + this.runButton.TabIndex = 4; + this.runButton.Text = "Run"; + this.runButton.UseVisualStyleBackColor = true; + this.runButton.Click += new System.EventHandler(this.runButton_Click); + // + // stopButton + // + this.stopButton.Location = new System.Drawing.Point(69, 0); + this.stopButton.Name = "stopButton"; + this.stopButton.Size = new System.Drawing.Size(64, 26); + this.stopButton.TabIndex = 5; + this.stopButton.Text = "Stop"; + this.stopButton.UseVisualStyleBackColor = true; + this.stopButton.Click += new System.EventHandler(this.stopButton_Click); + // + // label1 + // + this.label1.AutoSize = true; + this.label1.Location = new System.Drawing.Point(12, 19); + this.label1.Name = "label1"; + this.label1.Size = new System.Drawing.Size(76, 13); + this.label1.TabIndex = 12; + this.label1.Text = "Flow of events"; + // + // memoListView + // + this.memoListView.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom) + | System.Windows.Forms.AnchorStyles.Left) + | System.Windows.Forms.AnchorStyles.Right))); + this.memoListView.FullRowSelect = true; + this.memoListView.GridLines = true; + this.memoListView.HideSelection = false; + this.memoListView.Location = new System.Drawing.Point(12, 38); + this.memoListView.Name = "memoListView"; + this.memoListView.Size = new System.Drawing.Size(889, 312); + this.memoListView.TabIndex = 0; + this.memoListView.UseCompatibleStateImageBehavior = false; + this.memoListView.View = System.Windows.Forms.View.Details; + + this.memoListView.DoubleClick += new System.EventHandler(this.MemoListView_DoubleClick); + // + // MainForm + // + this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); + this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; + this.ClientSize = new System.Drawing.Size(913, 388); + this.Controls.Add(this.memoListView); + this.Controls.Add(this.panel1); + this.Controls.Add(this.filterManagementPanel); + this.Controls.Add(this.wndControlPanel); + this.Controls.Add(this.flowControlsPanel); + this.Controls.Add(this.label1); + this.Name = "MainForm"; + this.Text = "Application live diagnostic"; + this.panel1.ResumeLayout(false); + this.filterManagementPanel.ResumeLayout(false); + this.filterManagementPanel.PerformLayout(); + this.wndControlPanel.ResumeLayout(false); + this.flowControlsPanel.ResumeLayout(false); + this.ResumeLayout(false); + this.PerformLayout(); + + } + + #endregion + + private System.Windows.Forms.Panel panel1; + private System.Windows.Forms.Button button6; + private System.Windows.Forms.Button button7; + private System.Windows.Forms.Panel filterManagementPanel; + private System.Windows.Forms.Button button5; + private System.Windows.Forms.Button filterButton; + private System.Windows.Forms.TextBox filterTextBox; + private System.Windows.Forms.Label label2; + private System.Windows.Forms.Panel wndControlPanel; + private System.Windows.Forms.Button button2; + private System.Windows.Forms.Panel flowControlsPanel; + private System.Windows.Forms.Button runButton; + private System.Windows.Forms.Button stopButton; + private System.Windows.Forms.Label label1; + private System.Windows.Forms.Button refreshMemoButton; + private BufferedListView memoListView; + } +} + diff --git a/AppDiagnostic/MainForm.cs b/AppDiagnostic/MainForm.cs new file mode 100644 index 000000000..ee4f6b817 --- /dev/null +++ b/AppDiagnostic/MainForm.cs @@ -0,0 +1,266 @@ +using log4net.Config; +using log4net; +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Data; +using System.Drawing; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Windows.Forms; +using log4net.Appender; +using SharedComponents; +using static System.Windows.Forms.VisualStyles.VisualStyleElement; +using System.IO; // Pre File.WriteAllLines a prácu so súbormi +using System.Linq; + +namespace AppDiagnostic +{ + public partial class MainForm : Form + { + private bool stopUpdate = false; // Premenná na zastavenie aktualizácie + private int lastDisplayedLogIndex = 0; + private bool autoScrollEnabled = true; // Automatický posun, predvolene povolený + private MainForm appDiagnosticForm; + private Timer refreshTimer; + private LogAdapter _logAdapter; // Pre LogAdapter + private LogFilter _logFilter; // LogFilter objekt pre filtráciu + + public MainForm() + { + InitializeComponent(); + + // Inicializujeme LogAdapter a priradíme ho k memoListView + _logAdapter = new LogAdapter(memoListView); + + RefreshLogView(); + + // Pripojenie na udalosť pri pridaní nového logu + // nakoľko sa memo refreshuje cyklicky, tak refresh na udalosť nepotrebujem + //LiveLogCache.Instance.LogAdded += OnLogAdded; + + // Vytvoríme LogFilter objekt + //_logFilter = new LogFilter(memoListView); + } + + private void OnLogAdded(string log) + { + try + { + RefreshLogView(); + } + catch (Exception ex) + { + MessageBox.Show($"Error in OnLogAdded: {ex.Message}"); + throw; + } + } + + private void RefreshLogView() + { + // Skontrolujte, či voláme z iného vlákna + if (InvokeRequired) + { + Invoke((Action)RefreshLogView); + return; + } + + memoListView.BeginUpdate(); // Zabraňuje vizuálnym aktualizáciám počas vykresľovania + + try + { + // Skontrolujte, či je užívateľ na poslednom viditeľnom zázname + if (memoListView.Items.Count > 0) + { + var lastVisibleIndex = memoListView.TopItem.Index + memoListView.ClientRectangle.Height / memoListView.Items[0].Bounds.Height; + autoScrollEnabled = lastVisibleIndex >= memoListView.Items.Count - 1; + } + + // Získajte nové logy od posledného indexu + var logs = LiveLogCache.Instance.GetLogs(lastDisplayedLogIndex, LiveLogCache.Instance.Logs.Count - lastDisplayedLogIndex); + + // Pridajte len nové logy + foreach (var log in logs) + { + memoListView.Items.Add(new ListViewItem(log)); + } + + // Aktualizujte posledný zobrazený index + lastDisplayedLogIndex = LiveLogCache.Instance.Logs.Count; + + // Ak je autoScrollEnabled, posuňte sa na posledný záznam + if (autoScrollEnabled && memoListView.Items.Count > 0) + { + memoListView.EnsureVisible(memoListView.Items.Count - 1); + } + } + finally + { + memoListView.EndUpdate(); // Umožní vizuálne aktualizácie + } + } + + // Táto metóda sa spustí pri scrollovaní v memoListView (tzn. ak sa používateľ dostane na spodok listu) + private void memoListView_Scroll(object sender, EventArgs e) + { + // Skontrolujte, či používateľ dosiahol spodok ListView + if (memoListView.Items.Count > 0 && + memoListView.Items[memoListView.Items.Count - 1].Bounds.Bottom <= memoListView.ClientSize.Height) + { + // Ak áno, načítame ďalšie logy + RefreshLogView(); + } + } + + private void button6_Click(object sender, EventArgs e) + { + //LiveLogCache.Instance.AddLog("Nový log z hlavnej aplikácie"); + + LiveLogCache.Instance.ClearLogs(); + ClearListView(); + } + + private void runButton_Click(object sender, EventArgs e) + { + if (stopUpdate) // Ak je časovač zastavený, spustíme ho + { + stopUpdate = false; // Zastav aktualizáciu + runButton.Enabled = false; // Zakážeme tlačidlo Start počas behu + stopButton.Enabled = true; // Povolenie tlačidla Stop + refreshTimer.Start(); + } + } + + private void stopButton_Click(object sender, EventArgs e) + { + if (!stopUpdate) // Ak časovač beží, môžeme ho zastaviť + { + stopUpdate = true; // Spusti aktualizáciu + runButton.Enabled = true; // Povolenie tlačidla Start + stopButton.Enabled = false; // Zakážeme tlačidlo Stop + refreshTimer.Stop(); + } + } + + private void ClearListView() + { + if (InvokeRequired) + { + Invoke(new Action(ClearListView)); + return; + } + + memoListView.Items.Clear(); + } + + private void refreshMemoButton_Click(object sender, EventArgs e) + { + + } + + private void button7_Click(object sender, EventArgs e) + { + SaveLogsToFile(); + //LiveLogCache.Instance.AddLog("Toto je nový log."); + } + + protected override void OnFormClosing(FormClosingEventArgs e) + { + // Odpojenie udalosti, keď sa okno zatvára, inak môže po opätovnom spustení okna a pridaní nového itemu do listu zhhodiť program + // nakoľko sa memo refreshuje cyklicky, tak refresh na udalosť nepotrebujem + //LiveLogCache.Instance.LogAdded -= OnLogAdded; + + _logAdapter = null; + + base.OnFormClosing(e); + } + + private void filterButton_Click_1(object sender, EventArgs e) + { + // Aplikujeme filter na základe textu z filterTextBox + string filterText = filterTextBox.Text; + //_logFilter.ApplyFilter(filterText); // Aplikovanie filtra + _logAdapter.ApplyFilter(filterText); + } + private void SubForm_FormClosing(object sender, FormClosingEventArgs e) + { + try + { + // Vaša logika pri uzatváraní formy + // this.Hide(); // Podforma sa len skryje + } + catch (Exception ex) + { + // Ošetrenie výnimky + MessageBox.Show("Chyba pri zatváraní pod-aplikácie: " + ex.Message); + } + } + + private void SaveLogsToFile() + { + using (SaveFileDialog saveFileDialog = new SaveFileDialog()) + { + saveFileDialog.Filter = "Text Files (*.txt)|*.txt|All Files (*.*)|*.*"; + saveFileDialog.Title = "Save TBF live logs"; + saveFileDialog.DefaultExt = "txt"; + saveFileDialog.FileName = "TBFLiveLogs.txt"; + + if (saveFileDialog.ShowDialog() == DialogResult.OK) + { + try + { + // Načítanie všetkých logov z LiveLogCache + var logs = LiveLogCache.Instance.GetLogs(0, LiveLogCache.Instance.GetCount()); + + // Uloženie logov do vybraného súboru + File.WriteAllLines(saveFileDialog.FileName, logs); + + MessageBox.Show("Logs saved successfully.", "Save TBF live logs", MessageBoxButtons.OK, MessageBoxIcon.Information); + } + catch (Exception ex) + { + MessageBox.Show($"An error occurred while saving TBF live logs: {ex.Message}", "Error", MessageBoxButtons.OK, MessageBoxIcon.Error); + } + } + } + } + + private void button2_Click(object sender, EventArgs e) + { + this.Close(); + //this.Hide(); // Podforma sa len skryje + } + + private void MemoListView_DoubleClick(object sender, EventArgs e) + { + if (memoListView.SelectedItems.Count > 0) + { + var selectedItem = memoListView.SelectedItems[0]; + int selectedIndex = memoListView.Items.IndexOf(selectedItem); + + if (selectedIndex >= 0) + { + // Zrušenie filtra + _logAdapter._filterText = string.Empty; + + // Načítanie logov od indexu vybraného riadku + var logs = LiveLogCache.Instance.GetLogs(selectedIndex, LiveLogCache.Instance.GetCount() - selectedIndex); + + // Obnovenie ListView s novými údajmi + memoListView.BeginUpdate(); + memoListView.Items.Clear(); + foreach (var log in logs) + { + var item = new ListViewItem(log) + { + BackColor = memoListView.Items.Count % 2 == 0 ? Color.White : Color.FromArgb(240, 240, 240) + }; + memoListView.Items.Add(item); + } + memoListView.EndUpdate(); + } + } + } + } +} diff --git a/AppDiagnostic/MainForm.resx b/AppDiagnostic/MainForm.resx new file mode 100644 index 000000000..d58980a38 --- /dev/null +++ b/AppDiagnostic/MainForm.resx @@ -0,0 +1,120 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + \ No newline at end of file diff --git a/AppDiagnostic/Program.cs b/AppDiagnostic/Program.cs new file mode 100644 index 000000000..6aaa9c232 --- /dev/null +++ b/AppDiagnostic/Program.cs @@ -0,0 +1,22 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using System.Windows.Forms; + +namespace AppDiagnostic +{ + internal static class Program + { + /// + /// The main entry point for the application. + /// + [STAThread] + static void Main() + { + Application.EnableVisualStyles(); + Application.SetCompatibleTextRenderingDefault(false); + Application.Run(new MainForm()); + } + } +} diff --git a/AppDiagnostic/Properties/AssemblyInfo.cs b/AppDiagnostic/Properties/AssemblyInfo.cs new file mode 100644 index 000000000..482abc00e --- /dev/null +++ b/AppDiagnostic/Properties/AssemblyInfo.cs @@ -0,0 +1,33 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// 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("AppDiagnostic")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("AppDiagnostic")] +[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)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("fa9abad1-7184-4295-ade8-d44f2e3de6b2")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/AppDiagnostic/Properties/Resources.Designer.cs b/AppDiagnostic/Properties/Resources.Designer.cs new file mode 100644 index 000000000..5f4bc8a07 --- /dev/null +++ b/AppDiagnostic/Properties/Resources.Designer.cs @@ -0,0 +1,62 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace AppDiagnostic.Properties { + using System; + + + /// + /// A strongly-typed resource class, for looking up localized strings, etc. + /// + // This class was auto-generated by the StronglyTypedResourceBuilder + // class via a tool like ResGen or Visual Studio. + // To add or remove a member, edit your .ResX file then rerun ResGen + // with the /str option, or rebuild your VS project. + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + internal class Resources { + + private static global::System.Resources.ResourceManager resourceMan; + + private static global::System.Globalization.CultureInfo resourceCulture; + + [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] + internal Resources() { + } + + /// + /// Returns the cached ResourceManager instance used by this class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Resources.ResourceManager ResourceManager { + get { + if (object.ReferenceEquals(resourceMan, null)) { + global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("AppDiagnostic.Properties.Resources", typeof(Resources).Assembly); + resourceMan = temp; + } + return resourceMan; + } + } + + /// + /// Overrides the current thread's CurrentUICulture property for all + /// resource lookups using this strongly typed resource class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Globalization.CultureInfo Culture { + get { + return resourceCulture; + } + set { + resourceCulture = value; + } + } + } +} diff --git a/AppDiagnostic/Properties/Resources.resx b/AppDiagnostic/Properties/Resources.resx new file mode 100644 index 000000000..13b775f1d --- /dev/null +++ b/AppDiagnostic/Properties/Resources.resx @@ -0,0 +1,117 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + \ No newline at end of file diff --git a/AppDiagnostic/Properties/Settings.settings b/AppDiagnostic/Properties/Settings.settings new file mode 100644 index 000000000..07e66beb1 --- /dev/null +++ b/AppDiagnostic/Properties/Settings.settings @@ -0,0 +1,7 @@ + + + + + + + diff --git a/AppDiagnostic/packages.config b/AppDiagnostic/packages.config new file mode 100644 index 000000000..ffd035f93 --- /dev/null +++ b/AppDiagnostic/packages.config @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/Config/Data.cs b/Config/Data.cs new file mode 100644 index 000000000..88726d89d --- /dev/null +++ b/Config/Data.cs @@ -0,0 +1,103 @@ +using System; + +//formulas.cs has been added cause iPerl from master-2 ...MF + +namespace Config +{ + public class Data + { + public const string AdminUsername = "admin"; + public const string AdminPassword = "staratura"; + public const string SQLiteDbFName = "SQLite.db"; + +#if DN100 || BADGER_STREDNA_TRAT || BADGER_VELKA_TRAT || CEVAK_200 + public const int WMsCount = 3; + public const int LineSize = 3; + public const int CompoundWMsCount = 1; + public const int HeatMetersCount = 0; + public const int MaxPartNr = 1; +#elif MUNICH + public const int WMsCount = 3; + public const int LineSize = 3; + public const int CompoundWMsCount = 3; + public const int HeatMetersCount = 0; + public const int MaxPartNr = 3; +#elif MALTA_WSD25 + public const int WMsCount = 6; + public const int LineSize = 6; + public const int CompoundWMsCount = 0; + public const int HeatMetersCount = 0; + public const int MaxPartNr = 1; +#elif BADGER_MALA_TRAT || BERLIN || FUZHOU_150 || FUZHOU_300 || GELSENWASSER || GENESIS || IZRAEL_200 || PETERSBURG_200 || SENTEC || SLM_150 || TORINO_50 + public const int WMsCount = 6; + public const int LineSize = 6; + public const int CompoundWMsCount = 1; + public const int HeatMetersCount = 0; + public const int MaxPartNr = 1; +#elif PUCHONG_200 + public const int WMsCount = 6; + public const int LineSize = 6; + public const int CompoundWMsCount = 3; + public const int HeatMetersCount = 0; + public const int MaxPartNr = 3; +#elif RUM_MOB + public const int WMsCount = 8; + public const int LineSize = 8; + public const int CompoundWMsCount = 1; + public const int HeatMetersCount = 0; + public const int MaxPartNr = WMsCount / LineSize; +#elif TURA_SPECIAL + public const int WMsCount = 6; + public const int LineSize = 6; + public const int CompoundWMsCount = 1; + public const int HeatMetersCount = 0; + public const int MaxPartNr = WMsCount / LineSize; +#elif DEWA_300 || FUZHOU_100 || ROMA_200 || LUXEMBURG_40 + public const int WMsCount = 10; + public const int LineSize = 10; + public const int CompoundWMsCount = 1; + public const int HeatMetersCount = 0; + public const int MaxPartNr = WMsCount / LineSize; +#elif DUBAJ_50 + public const int WMsCount = 10; + public const int LineSize = 5; + public const int CompoundWMsCount = 0; + public const int HeatMetersCount = 0; + public const int MaxPartNr = WMsCount / LineSize; +#elif SLM_END || WARSAW_END + public const int WMsCount = 10; + public const int LineSize = 5; + public const int CompoundWMsCount = 1; + public const int HeatMetersCount = 0; + public const int MaxPartNr = 4; +#elif SLM_50 + public const int WMsCount = 12; + public const int LineSize = 12; + public const int CompoundWMsCount = 1; + public const int HeatMetersCount = 6; + public const int MaxPartNr = WMsCount / LineSize; +#elif ALZIR_25 || BAHRAIN_50 || CEVAK_40 || FEWA_50 || FILIPINY_50 || FUZHOU_50 || HONGKONG_50 || IZRAEL_25 || JUZNA_AFRIKA_50 || KEMPNO_50 || KRAKOW_50 || MILWAUKEE || MURES_40 || PETERSBURG_50 || TORUN_50 || WARSAW_50 || ZAMBIA || ZODINO + public const int WMsCount = 20; + public const int LineSize = 10; + public const int CompoundWMsCount = 1; + public const int HeatMetersCount = 0; + public const int MaxPartNr = WMsCount / LineSize; +#elif TURA_IPERL || TURA_IPERL_NEW + public const int WMsCount = 40; + public const int LineSize = 20; + public const int CompoundWMsCount = 1; + public const int HeatMetersCount = 0; + public const int MaxPartNr = WMsCount / LineSize; +#elif IZRAEL_50 + public const int WMsCount = 40; + public const int LineSize = 10; + public const int CompoundWMsCount = 0; + public const int HeatMetersCount = 0; + public const int MaxPartNr = WMsCount / LineSize; +#endif + + public static double RealDensity = 0; /// true water density [kg/m3] + public static double AtTemperature = 0; /// measured at temperature [°C] + public static double Buoyancy = 0; + } +} diff --git a/Config/Formulas.cs b/Config/Formulas.cs new file mode 100644 index 000000000..24b888476 --- /dev/null +++ b/Config/Formulas.cs @@ -0,0 +1,410 @@ +/// +/// Copyright (c) 2013-2018 Sensus Slovensko a.s. +/// +using System; +using System.Collections.Generic; +using log4net; +using Common; + +//formulas.cs has been added cause iPerl from master-2 ...MF + +namespace Config +{ + public static class Formulas + { + private static readonly ILog log = LogManager.GetLogger(typeof(Formulas)); + + + /// + /// Wrappers + /// + public static double RealDensity() { return Data.RealDensity; } + public static double AtTemperature() { return Data.AtTemperature; } + public static double Buoyancy() { return Data.Buoyancy; } + + + /// Private tables with coeficients to calculate specific enthalpy + static readonly int[] Ii; + static readonly int[] Ji; + static readonly double[] ni; + + /// Private table with coeficients to calculate temperature of a platinum thermometer + static readonly double[] Di; + + + /// + /// Constructor + /// + static Formulas() + { + /// + /// Initialize tables to calculate specific enthalpies + /// + Ii = new int[34] { 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 3, 3, 3, 4, 4, 4, 5, 8, 8, 21, 23, 29, 30, 31, 32 }; + Ji = new int[34] { -2, -1, 0, 1, 2, 3, 4, 5, -9, -7, -1, 0, 1, 3, -3, 0, 1, 3, 17, -4, 0, 6, -5, -2, 10, -8, -11, -6, -29, -31, -38, -39, -40, -41 }; + ni = new double[34] { + 0.14632971213167, /// 1 + -0.84548187169114, /// 2 + -0.37563603672040E1, /// 3 + 0.33855169168385E1, /// 4 + -0.95791963387872, /// 5 + 0.15772038513228, /// 6 + -0.16616417199501E-1, /// 7 + 0.81214629983568E-3, /// 8 + 0.28319080123804E-3, /// 9 + -0.60706301565874E-3, /// 10 + -0.18990068218419E-1, /// 11 + -0.32529748770505E-1, /// 12 + -0.21841717175414E-1, /// 13 + -0.52838357969930E-4, /// 14 + -0.47184321073267E-3, /// 15 + -0.30001780793026E-3, /// 16 + 0.47661393906987E-4, /// 17 + -0.44141845330846E-5, /// 18 + -0.72694996297594E-15, /// 19 + -0.31679644845054E-4, /// 20 + -0.28270797985312E-5, /// 21 + -0.85205128120103E-9, /// 22 + -0.22425281908000E-5, /// 23 + -0.65171222895601E-6, /// 24 + -0.14341729937924E-12, /// 25 + -0.40516996860117E-6, /// 26 + -0.12734301741641E-8, /// 27 + -0.17424871230634E-9, /// 28 + -0.68762131295531E-18, /// 29 + 0.14478307828521E-19, /// 30 + 0.26335781662795E-22, /// 31 + -0.11947622640071E-22, /// 32 + 0.18228094581404E-23, /// 33 + -0.93537087292458E-25, /// 34 + }; + + /// + /// Initialize a table to calculate temperature of a platinum thermometer from resistance + /// + Di = new double[] + { + 439.932854, + 472.418020, + 37.684494, + 7.472018, + 2.920828, + 0.005184, + -0.963864, + -0.188732, + 0.191203, + 0.049025, + }; + } + + /// + /// Calculate density of distilled water from temperature + /// + /// ITS-90 temperature in [°C] + /// Density in [kg/m3] + public static double DistilledWaterDensityFromTemp(double t) + { + if (t <= 40) + { + const double c0 = 999.839564; + const double c1 = 0.067998613; + const double c2 = -0.0091101468; + const double c3 = 0.00010058299; + const double c4 = -0.0000011275659; + const double c5 = 6.5985371e-09; + + return ((((c5 * t + c4) * t + c3) * t + c2) * t + c1) * t + c0; + } + else + { + const double a0 = 9.9983952E2; + const double a1 = 1.6952577E1; + const double a2 = -7.9905127E-3; + const double a3 = -4.6241757E-5; + const double a4 = 1.0584601E-7; + const double a5 = -2.8103006E-10; + const double b = 1.6887236E-2; + + return (((((a5 * t + a4) * t + a3) * t + a2) * t + a1) * t + a0) / (1.0 + b * t); + } + } + + /// + /// Calculate density of distilled water from temperature (obsolete) + /// + /// IPTS-68 temperature in [°C] + /// Density in [kg/m3] + public static double DistilledWaterDensityFromTempIPTS68(double t) + { + const double a0 = 999.842594; + const double a1 = 0.06793952; + const double a2 = -0.009095290; + const double a3 = 0.0001001685; + const double a4 = -0.000001120083; + const double a5 = 6.536332e-09; + + return ((((a5 * t + a4) * t + a3) * t + a2) * t + a1) * t + a0; + } + + /// + /// Calculate density by comparing calculated data and data from a certificate + /// + /// Density from a certificate in [kg/m3] + /// Temperature from a certificate in [°C] + /// Density correction in [kg/m3] + public static double DensityCorrection(double realDensity, double atTemperature) + { + /// Calculated data + double calculatedDensity = DistilledWaterDensityFromTemp(atTemperature); + + return realDensity - calculatedDensity; + } + + /// + /// Calculate corrected (real) water density from temperature + /// + /// Temperature in [°C] + /// Density in [kg/m3] + public static double WaterDensityFromTemp(double t) + { + return DistilledWaterDensityFromTemp(t) + DensityCorrection(RealDensity(), AtTemperature()); + } + + /// + /// Calculate corrected (real) water density from temperature + /// + /// Temperature in [°C] + /// Density in [kg/m3] + public static double WaterDensityFromTempPress(double temp, double pressure) + { + double x0 = 5.08821E-10; + double x1 = 1.2639418; + double x2 = 0.2660269; + double x3 = 0.3734838; + double x4 = 2.0205242; + double theta = temp / 100.0; + double B = x0 * (((x3 * theta + x2) * theta + x1) * theta + 1) / (1 + x4 * theta); + + return WaterDensityFromTemp(temp) * (1 + B * Units.ConvertTo(Unit.Pa, pressure)); + } + + + public static float AirDensityFromAmbientVales(float tempC, float pressureBar, float humiPct) + { + double pressurePa = 100000.0 * (double)pressureBar; /// [Pa] + double tempKelvin = 273.15 + (double)tempC; + double coef1 = 1.2811805 / 10000.0 * tempKelvin * tempKelvin + - 1.950987 / 100.0 * tempKelvin + + 34.04926034 + - 6.353631 * 1000.0 / tempKelvin; + double coef3 = humiPct / 100.0 * System.Math.Exp(coef1) / pressurePa; + double airDensityKgm3 = 0.00348353 * pressurePa * (1.0 - 0.378 * coef3) / tempKelvin; /// kg/m3 + return (float)airDensityKgm3; + } + + /// + /// Convert 'pulses' to 'volume', prevent division by zero + /// + public static double VolumeFromPulses(int pulses, double pulsesPerLiter) + { + if (pulsesPerLiter <= double.Epsilon) return 0; + return Convert.ToDouble(pulses) / pulsesPerLiter; + } + + /// + /// Calculate the error in % from 'measured' and 'true' volume, prevent division by zero + /// + public static double ErrorFromVolumes(double measuredVolume, double trueVolume) + { + if (-float.Epsilon <= trueVolume && trueVolume <= float.Epsilon) + { + if (-float.Epsilon <= measuredVolume && measuredVolume <= float.Epsilon) + { + log.WarnFormat("ErrorFromVolumes({0},{1}) returns {2}", measuredVolume, trueVolume, -100.0); + return -100.0; + } + + log.WarnFormat("ErrorFromVolumes({0},{1}) returns {2}", measuredVolume, trueVolume, 99.0); + return 99.0; + } + + double error = 100.0 * (measuredVolume - trueVolume) / trueVolume; + log.InfoFormat("ErrorFromVolumes({0},{1}) returns {2}", measuredVolume, trueVolume, error); + return error; + } + + + /// + /// Calculates corrected value from a list of corrections by interpolation. + /// It is assumed that values in the list 'corrections' are sorted. + /// + /// Raw uncorrected value + /// Sorted (value, correction) pairs + /// Corrected value + public static double CorrectedValue(double rawValue, IList corrections) + { + return rawValue + GetCorrection(rawValue, corrections); + } + + /// + /// Get a correction from a list of corrections by interpolation. + /// It is assumed that values in the list 'corrections' are sorted. + /// + /// Raw uncorrected value + /// Sorted (value, correction) pairs + /// Corrected value + public static double GetCorrection(double rawValue, IList corrections) + { + if ((corrections == null) || (corrections.Count == 0)) return 0; /// No correction + + if (rawValue < corrections[0].Measurement) + { + /// rawValue is below the lowest value in the correction table + return corrections[0].Correction; + } + + int count = corrections.Count; + for (int i = 1; i < count; i++) + { + if (rawValue < corrections[i].Measurement) + { + double d1 = rawValue - corrections[i - 1].Measurement; + double d2 = corrections[i].Measurement - rawValue; + + if (d1 + d2 <= float.Epsilon) + { + /// Neigboring values in the corection table are close to each other -> calculate the average + return (corrections[i - 1].Correction + corrections[i].Correction) / 2.0; + } + else + { + /// Interpolate the correction from neigboring values in the corection table + return (corrections[i - 1].Correction * d2 + corrections[i].Correction * d1) / (d1 + d2); + } + } + } + + /// rawValue is above the highest value in the correction table + return corrections[count - 1].Correction; + } + + + /// + /// Converts a measurement error to a correction (used when preparing correction tables). + /// + /// Measured value (in arbitrary units) + /// Measurement error in % + /// Correction in the same units as the measured value + public static double CorrectionFromError(double measuredValue, double error) + { + double trueValue = measuredValue / (1 + error/100); + double correction = trueValue - measuredValue; + return correction; + } + + + /// + /// Calculates the heat coefficient for water + /// + /// Pressure [bar] + /// Inlet temperature [°C] + /// Outlet temperature [°C] + /// true = flow measured @inlet, false = flow measured @outlet + /// Heat coefficient for water [J/(m3 K)] + public static double HeatCoefficientWater(double pressure, double T_in, double T_out, bool flowMeasuredAtInlet) + { + if (T_in == T_out) return 0; + + const double R = 461.526; /// [J kg^-1 K^-1] + const double p_star_Pa = 16.53E6; /// [Pa] (=16.53 MPa) + const double T_star = 1386.0; /// [K] + + double T_in_K = Units.ConvertTo(Unit.K, T_in); + double T_out_K = Units.ConvertTo(Unit.K, T_out); + double tau_in = T_star / T_in_K; + double tau_out = T_star / T_out_K; + double pi = Units.ConvertTo(Unit.Pa, pressure) / p_star_Pa; + + double h_in = tau_in * GammaTau(pi, tau_in) * R * T_in_K; + double h_out = tau_out * GammaTau(pi, tau_out) * R * T_out_K; + + double ni = flowMeasuredAtInlet ? GammaPi(pi, tau_in) * R * T_in_K / p_star_Pa + : GammaPi(pi, tau_out) * R * T_out_K / p_star_Pa; + + return (h_in - h_out) / (ni * (T_in - T_out)); + } + + /// + /// gamma(pi) see also STN EN 1434-1 Annex A (A.4) + /// + /// pi = p / p* where p* = 16.53 MPa + /// tau = T* / T where T* = 1386 K + /// gamma(pi) + static double GammaPi(double pi, double tau) + { + double result = 0; + for (int i = 0; i < 34; i++) + { + result -= ni[i] * Ii[i] * Math.Pow(7.1 - pi, Ii[i] - 1) * Math.Pow(tau - 1.222, Ji[i]); + } + return result; + } + + /// + /// gamma(tau) see also STN EN 1434-1 Annex A (A.7) + /// + /// pi = p / p* where p* = 16.53 MPa + /// tau = T* / T where T* = 1386 K + /// gamma(tau) + static double GammaTau(double pi, double tau) + { + double result = 0; + for (int i = 0; i < 34; i++) + { + result += ni[i] * Math.Pow(7.1 - pi, Ii[i]) * Ji[i] * Math.Pow(tau - 1.222, Ji[i] - 1); + } + return result; + } + + /// + /// Conversion of measured resistance of a platinum thermometer to temperature according to ITS-90 + /// + /// Measured resistance in [°C] + /// Calibrated resistance in Ohm at 0.01°C + /// Calibrated ITS-90 coefficient a7 + /// Calibrated ITS-90 coefficient b7 + /// Calibrated ITS-90 coefficient c7 + /// Temperature in [°C] + public static double PlatinumResistanceTM_ITS90_R2T(double R, double R001C, double a7, double b7, double c7) + { + double w = R / R001C; /// ratio + double r1 = w - 1.0; + double dw = r1 * (a7 + r1 * (b7 + r1 * c7)); /// = a7*r1 + b7*r1^2 + c7*r1^3 + double wr = w - dw; + double x = (wr - 2.64) / 1.64; + + double sum = 0; + for (int i = Di.Length - 1; i >= 0; i--) + { + sum = sum * x + Di[i]; + } + + return sum; + } + + /// + /// Conversion of measured resistance of a platinum thermometer to temperature using Callendar-Van Dusen equations + /// + /// Measured resistance in [°C] + /// Calibrated resistance in Ohm at 0°C + /// Calibration coefficient a + /// Calibration coefficient b + /// Temperature in [°C] + public static double PlatinumResistanceTM_ITS27_R2T(double R, double R0, double A, double B) + { + if (R0 * R0 * A * A - 4 * R0 * B * (R0 - R) <= 0) return 0; /// Out of range + + return (-(R0 * A) + Math.Sqrt(R0 * R0 * A * A - 4 * R0 * B * (R0 - R))) / (2 * R0 * B); + } + } +} diff --git a/SchematicDrawing/Pictures/Tank-L-hot.png b/SchematicDrawing/Pictures/Tank-L-hot.png new file mode 100644 index 0000000000000000000000000000000000000000..fddf1604bc62c9c96d6568d8cda35b280bb2fdf8 GIT binary patch literal 842 zcmeAS@N?(olHy`uVBq!ia0vp^(||aSgAGU?J@5*|VJr@EcVbv~PUa<$!;&U>c zv7h@-A}f&3S>O>_%)r2R2!t6$HM|;t8rm~MB1$5BeXNr6bM+Ea@{>~aDsl^esu>t; z>?;Zqle1Gx6p~WYGxKcK-|yb9u8^5xs~&FZYv5bpoSKp8QB{;0T;&&%T$P<{nWAoQ z$IE3?VFffHH?<^Dp&~aYuh^=>Rtapd6_5=Q)>pE#DN0GR3UYCSssQqAl`=|73as?? z%gf94%8m8%i_-NCEiEne4UF`SjC6r2bc-wVN)jt{^NN+B2DqdaCl_TFlw{`TDS!-2 zOv*1Uu~jN9%}lXMOH4CON=Y%*O-eLQ(KR$oNz_eDF*ejqF*Z&yH#M{{N;6DSf?8ja znTD`GuNWE(zyQ$)$>>wgQzXDnC zkO2h~Jakj@fI(Ug3_G1EGq{0K;^XP!7*Y}U_R2y*CPx9+gONc8lup_!J&8=R5J{6g zYI|Kb{mttAY0qSK+{m>*AbPEQS)gMPQ` ZBWC_Nn{1`ISV`@iy0XB4uLSEsD@VqP(yoWNJL45ua8x7ey(0(N`6wRUPW#JP&EUC zjeSKyVsdtBi9%9pdS;%j`upAc)fF;RY}La}eGPmIoKrJ0J*tXQgRA^PlB=?lEmPF( z?RdFtDy)DefHbp6ERzWUqP!&L)tx`rwNr9EV zetCJhUb(Seeo?xG?WUP)qwZeFo6)Bu;%;^d;tf|AVqJOz+} ziAnjTCALbXrI{&KX^Cm3Nhv9&x=D$~DY}M+DT%s?DaMAnDaOVr=B9=gMrnpgN>J-d zGSd+D=M_VP0T>|qAQ^o_Jp+)FL2N7kqRdpFD?nD-8QOs5QN(QYK@LH3m>om}=vN>M z9x|X{f`@Ku9xzDDfnldJWd=7eN|-!d978JN-dm{sb+^*7 ac*iEOm~Fj!-cA-!GW2xyb6Mw<&;$UWMhZ0m literal 0 HcmV?d00001 diff --git a/SchematicDrawing/Pictures/Tank-S-hot.png b/SchematicDrawing/Pictures/Tank-S-hot.png new file mode 100644 index 0000000000000000000000000000000000000000..4e052e67c0094d8de785ec57777cf0ee39944474 GIT binary patch literal 746 zcmeAS@N?(olHy`uVBq!ia0vp^DL@>+!3HGXb6$84q!^2X+?^QKos)S9a~60+7BevL9RguSQ4OyKpoaF$kcg59UmvUF{9L`nl>DSry^7odplSvN z8~cia#N_PM5{0DH^vpb4_4m8?t1D!t*s6z{`WpBaIHzW0dQ=sq23ProBv)l8Tc)Vn z+wpSQR9FE`$W1LtRH(?!$t$+1uvG#ZYz1V4g!Pr|Y>HCStb$zJpeleoTcwPWk^(Dz z{qpj1y>er{{GxPyLrY6beFGzXBO_g)3fd$_#E`xWDmqaSW-5dwX}GXtRR|>qY;l18$efPH7N4lN%m3&4r`Qu8@4d^n$+&B8EOtrRqesE8=?I6=;AZUKUy!+B b!VK;i3T(4ioqstOl;AvF{an^LB{Ts5e#G;; literal 0 HcmV?d00001 diff --git a/SchematicDrawing/Pictures/Tank-XL-hot.png b/SchematicDrawing/Pictures/Tank-XL-hot.png new file mode 100644 index 0000000000000000000000000000000000000000..34cb418d34af0102da5d242edba2097c1d690c79 GIT binary patch literal 894 zcmeAS@N?(olHy`uVBq!ia0vp^CxEz#gAGVB&u*Urq!^2X+?^QKos)S9a~60+7BevL9RguSQ4OyKpoaF$kcg59UmvUF{9L`nl>DSry^7odplSvN z8~cia#N_PM5{0DH^vpb4_4m8?t1D!t*s6z{`WpBaIHzW0dQ=sq23ProBv)l8Tc)Vn z+wpSQR9FE`$W1LtRH(?!$t$+1uvG#ZYz1V4g!Pr|Y>HCStb$zJpeleoTcwPWk^(Dz z{qpj1y>er{{GxPyLrY6beFGzXBO_g)3fd$_#E`lr(s{IEGZjy}i1Tm&s9p<)CfQfukz_I}GltwjFoi zc-dmbxav&oZqw|vbBuMn-mpK&t`&Pyx%<8SkLc@<4U-)gaI!QxC~%Atq=mtnnU>#` eKcqP->|s!AXZs&`C$$We2|Qi>T-G@yGywpat^1My literal 0 HcmV?d00001 diff --git a/SchematicDrawing/Pictures/ValveSw-L-closed.png b/SchematicDrawing/Pictures/ValveSw-L-closed.png new file mode 100644 index 0000000000000000000000000000000000000000..052aed4ee3b09a376f636fa59e9685a3f06049d4 GIT binary patch literal 1116 zcmV-i1f%Px#1ZP1_K>z@;j|==^1poj532;bRa{vGi!~g&e!~vBn4jTXf1Mx{jK~z{r&6zQ1 zT2T~+Pab5bi-DSgfz~Cobx0_fsh}=}n!!R`Tojr>4RkVvLc!3X#8PNM3!$JLQqT+? zG=qzUHV}mpXbdGorI1AmanL|qTr#xxKleQot43{&z8_vL?_Pa=@0@$iz4v77ityZ}Y<368{g25n7O-*rMFX{>K@bHjUR#wW4u@%RaZw#@FGX!zB*4kxa4?+5P4wd%k@coJ_#eVEQH zySq!tWRiSdkp{#yY=xf_+3pZ$4NNml4M0Sc>8hL;qY&m~;=Rn9tE;POuL_vSWZ1Q# zr(%f`kfRc!8}5cDkB*MGj{%YE02X-2eIu2YHNXTZAVt0dd6!jdD1sI5-;-8ER#CRL zws^qvfE!&1L5jJU&1Ol#wh8V<1nEUCVL=GFQNbU_5OZEHg2CWwOg#mrye)3@rW<1I zM`qzD9^^(~Py!P+yNyJxHyN(TR=dA`vud-cOSRe&w0ci6hH@Z+<-BjaLsm#vK@&P=l6F>xP z_KVoOcsz19gHDtqFe&u_8GbG2AebNqM5O@5o|kXr;HZQ+h<^|VHW3%_EX05)_Mi*_ zD@NaO|48)YnKV!&5>b23tt4Krm&rpf&Z20Tf??7b=~@)0G&KOBL?Xeic6o-Ic3=<~ z#%8mzC(o-15alpg`!kSJWS?O`0uBb5oU~9Xm1-$+HUUDIoHz?qjtw;DxLkK5YiMvg zJ3DRbg)M-P21#QkatKQfI=YeT{Ey^iu~<98L2UscK<@4BH3DbOIT)AY8P+*zJycrp z8y8htI|4%ZBB~CtSWLLw#BW}!)%di9wpW$QW!m1}<}Ey)F?r3(9$H^t?^;;zxrmNs z2PIsb_)&9)j7B3blZgDkYt?uGT&PRqUA literal 0 HcmV?d00001 diff --git a/SchematicDrawing/Pictures/ValveSw-L-open-dry.png b/SchematicDrawing/Pictures/ValveSw-L-open-dry.png new file mode 100644 index 0000000000000000000000000000000000000000..86d355d00d217dff1d8ebed3fa6c4802039427c7 GIT binary patch literal 1196 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 D0Qe@Y literal 0 HcmV?d00001 diff --git a/SchematicDrawing/Pictures/ValveSw-L-vacuum.png b/SchematicDrawing/Pictures/ValveSw-L-vacuum.png new file mode 100644 index 0000000000000000000000000000000000000000..ffda1b1012dd4b459928ce088bf9fb3a80f866cd GIT binary patch literal 1211 zcmV;s1VsCZP)Px#1ZP1_K>z@;j|==^1poj532;bRa{vGi!~g&e!~vBn4jTXf1W-vtK~z{r%~(-L zTUQwVOmR@5ASAxnmC%PVZW%fj3)&K~I9LYt!Mnm98XxxJU@y~$LP6NdP+IJR7Nk@Q z`jG9xhefDRvr(9dwD^#~uwd5Chops4YFcDlGN`4u=ljpSp{a?DR@3{z&x!xJT)+FD z^PltoU){#WhJ_tsWn~4iSPV-`OJbGK(9nQjFo@31PW1NnqPe-*o~a&H6RfYV$JEpm z#>dBz&1S{tKLUL5xgLF9aTSq$2>f*qNZbK(Pqi-)2w-4f06jfD+EE3nA~-uci=m+* zA zu&jS}Z~4ct#xB89(Uy0H!(l6*&zJq*j#Z2x_nB#$619;ZfPwG0r|ijh1fw{39{71y zM{{9e0ga7~_G~H25+u1$D5QQB5Q_rA6WYJmAi;0H2b!Avh{xk)Np2zNkZbZ55RX;A z&vuI?l|&)4GRYEx>Nncj+SKmIA(_~Jat=tcQ!N;?JdsFXb92+40X+67=&GtpiT8`e<7$s3Z(@CY9UfLCf|Gi11y0|Tfy;bA zjTau|U!v>UXX11NGA}Yf1X7;WslP3pID@X+IX&wVEFOJ^fVbX0sGC2M!By~mh;%w_ z6a3_&46ec(0@(_SK;~pGPrk%|9uLd&j$}%L;QZm z_PPk_x?=(3Vc&|of7o7kVDZ+X5F|H`+`}rI;&cmc>p=*z;}_UYSK!xEqDrtgl5(rN zyBn#ytM0i`GAros@5kuqs3yqjmHmjD3f9%tRb4EQASXIHI!?08WS&3Nr)^I;;@``RZP17uLajfL3V@FRRhcX1^2zLDY4N7vm|}F`DJUm>QtrbueK?l?hDWXTCQQHFqK85JkB+3Jx zd#j*oO>T(?${00*cPoi;MRub0?}(rSE(w=^Zf;IGFB%)1*TG_w@-8($CIK&j?Uu4U ZfLETt4sNjT1CIaz002ovPDHLkV1hqqL*D=Z literal 0 HcmV?d00001 diff --git a/SchematicDrawing/Pictures/ValveSw-M-closed.png b/SchematicDrawing/Pictures/ValveSw-M-closed.png new file mode 100644 index 0000000000000000000000000000000000000000..297d65bde0b0c2c94337da418064c32b2745d587 GIT binary patch literal 1033 zcmV+k1or!hP)Px#1ZP1_K>z@;j|==^1poj532;bRa{vGi!~g&e!~vBn4jTXf1D;7lK~zXfy_aoF zTU8jxf4$*lU7}qe_=Sapj?S5oZI00S68ta}1$BaerZR9bM)9Lr1Oy}zgplYrbs=GX z0U<6dAu>c65C@Ffl%S0nhIK+n3_&1)`ZgxC)O${k)xm&e>+SE$?S1~e_n+t7=bZB# zlh5Z1vJ0!LtMv5rFgiMl)9GYpW=1KXX&Py1Y1r*{va_=(EG%SWV?(Liv!n6i;v!8= zO|-SOVX;`GK}t#r2?+_r#Kg$|`2BvCmX?^Co5Sn%vcA4fd3iaNm6gQD$IJiRj@ufW z&1PxX*w~19k00xSBjo4jD|LHBrrq7$c&}Z>yw^f=b2AQyL#el-scV=(ARveAT5CY;@Fh+!95o<0Sie)Jm^@4O|m7l~|Ui?J{@ZCv5|QF!Wv3f=`JFT)RG@T2Do z`Q100hmD1hlaoW@J`V$5!UIW4xCaiT!na?+^z~lK%F0wUMA*2ZqJpIbHy7H`hf4(` zfJmU{JoNUSr?azDMV0aN^fYJBe!}PHV4p?CBTAYTT0e%Gni|&D)?`c?*VWZgoFCw^ z<1%Iljv_c@TalG6(8OHr@9*QiN_lT6@1D^!Z*7&cMC<72AoEy|LkE>=AkRFh7xA7y z=<4c18ypvq_0CmLqlleg((Itj)xfSL%k)KLjJrcxr2F<-If(1$OdeF{fUKn>S@V zqAV=J={KROs*1$KL>UV&AT2E|to&}})pF4-s$c}Tq0ie_UIW{MSyWe7tLXoXMTa;r zFo4s&pRzaf2UeAE53Km$`Io@==P`L85C?;Q2^$NAkY&MdUlSxWS3cpMoVV0MdnVp?wYHEu4`FZm4@@QyiAT?F*Z@1x& z4sGJ4?d|RI(B^jQYu+Zryv)qZlzn+=X{kJ0?F{?_mccr$SHtfw00000NkvXXu0mjf DE<5*) literal 0 HcmV?d00001 diff --git a/SchematicDrawing/Pictures/ValveSw-M-open-dry.png b/SchematicDrawing/Pictures/ValveSw-M-open-dry.png new file mode 100644 index 0000000000000000000000000000000000000000..532644076cb3fb463d7e7c83ff24e9d2302f7b94 GIT binary patch literal 1047 zcmV+y1nB#TP)Px#1ZP1_K>z@;j|==^1poj532;bRa{vGi!~g&e!~vBn4jTXf1FT6zK~zXfy_a1` z3C9>avM~_MizaL&XZ~IObSZ+M+Y)mPBm2kv;@v5N=5M;*%DL_!5YL$ks!a zvQ#ThAE2xFl4d#Vu98AxI!Ynwnl`&7?u;L||Gjf})?IaTcO8Fl_`^AW?ztc5o^!6r z=kp2v2d~#lXJ;pagM)ZH9<9AiR8$n%+1V5p7E)ebPEt~mw(N~re|JSXDJm)=C&$h& zS3jcsjEBoVz|Fs4eI+Q@=b`_b041&;>FfEB6DOWf&Sz$3^eB5ESA4gutgMK#vNCbx zh)Gnvjrj97gt)Ok`Smj5^-@G+q(xL$R|~)2zhitUJGOdxc^RkEN&F*TIzELbAJ>Na zWOxKB-h#wO9rX0{V6j-VCCD9HEuH4)=kt`!%TV8gJC)?-z||o*7VDv)pn%QIO>GGh zTJhG_7Fk(Y7yKaZ)Vrbf8kZYnA&v^l)Aw6xIF z)P%(d^AA!|Qjl^O$Sb?Lx{6t*#pL8)5Yq7pA}%gYEG{mpvZ(epEiKJx1kO?ZCj9MF zlRHpmX5pRpP{koyb_NEa)+Z(=hP=Eyjz*90-B%DBtLzWWwIw+9BAm3nMq68()^5vO zvKQ*@?Zx-6osu$GyrvBg!L%1%I1R_GFVWuKuC+n#2xFPEWka?up2wC8mj<-q0l07x zY|qM-o~5g+E7atLkXwDX$R@nLzFrvTA)KcWgO?Cn*LM$^?Gjif`_b literal 0 HcmV?d00001 diff --git a/SchematicDrawing/Pictures/ValveSw-M-open.png b/SchematicDrawing/Pictures/ValveSw-M-open.png new file mode 100644 index 0000000000000000000000000000000000000000..58c10ba4766907d7adc76d80b48af8322cbe183d GIT binary patch literal 1018 zcmVPx#1ZP1_K>z@;j|==^1poj532;bRa{vGi!~g&e!~vBn4jTXf1CL2WK~zXfy_a1` zlTjGQ|C>5oACLrAL>DegS{G`=sVO2!lRhLu6U{EV$nrwNk}kZ_K=4kGZdAO9G(`*T zgU}fkw6r$dB#9F9qYGE2IMlGUSLW%Q^BzB@Q_Hsf;Njh#=iPhWbM`#X`5(p1%nZR6 zjEsz+si_GjlL;1!MR-?GsZ@xIi-W;nKyGd>!o$NwWMh=#>J_uujP&$$BqZqY@k15z zGA-zP14z@rf5vpZh6i;tpy2WU#`zmCT%aF_{0PX*x{B)RYACE$ zE79p`CZyzuupeMehCQsHN20DI`(1VZbh()UpyOV0FeilXkQBy z3ogE4DA^_s`=Ia!fNw*HjEsbmDF+O`6W#zY+7G>64<*}sVKWM^pK&h$)=s`nDu#xJ zNYpVcNOCs)$AiQje&W|Gc7J+}iHQk$KV}-qVt#>(8R7;%ala8LEV_-dvNCyFwApN2 zBfahNzNPuXsEi_EVPS--9KJ2`a?)rtC@d`E8fj*hzno7Qrh@bOO>iL)5)#6F%hE@! zRwE@Pg=?het+I0P1rw9NsdGSdYz}H`YlU}J=VDrjsX@nVEY4>EKj;ts7dBHiXynlh zG&E5CvkH%v$vO%XWYS_XVw0&*ePFg){QP{BmXPx#1ZP1_K>z@;j|==^1poj532;bRa{vGi!~g&e!~vBn4jTXf1F%U%K~zXf<(FMZ zq-7Mxe>3X%v6#4!UHAbmL&gg0IHs1a+TsVgt+cVLMdL-V8H5{B-gu=2BHjc-AhNZP zr7YDB&Q_qS>rI;Fu)De`BsPvvNV=uXZVm2?qfhVq&a<<+ezYI+cGC|I1Lq9Sc|Okj zod09+cszoC!R>a_*Vo7B=qOI7Q+n@VGMUKD&84iY44chHQc{wP?2S?W?uweEva*u= zd@Da*{{Y)5Cs)6PJAc6XYS3;i!tf!tbN5jR>V8J~`dwaX|DySo4DP zyh6@KCoX&mr%u04XJ;pdsi`TEot=%5%Ng+@dkRg4Ex5wl4dNHckVsdh_bn8!AS{mcy533Ii%lu+ zKRw#y!pi&ty!8%RbBI=zfkH_7#Ky)_TwKh7=n2050^;Jd_+xYPcR2PU9I?DgcXzk+ zZn;2IEi^bdi05xB)wQsEQwAObmm6L<4u{Mq=;`T^-XPn`*kCYdf^1zBVJU#iBQo#^ zT(}69LUpCDV7J>tA}@rx)%Tk!!dqKgg>oLE{1{^NGGg=A?nSfJ{40pBK1V#Che%9J z6kT0iJI20iI|Th!RGr<{)~3CiQj?WuN)6WKmGmv+KXUjW(JqbB_qM($;`~eVzFp9QcX>brmgk|{sG?r VglS7?sp9|u002ovPDHLkV1jow_x=C? literal 0 HcmV?d00001 diff --git a/SchematicDrawing/Pictures/ValveSw-S-closed.png b/SchematicDrawing/Pictures/ValveSw-S-closed.png new file mode 100644 index 0000000000000000000000000000000000000000..7278a215a96b2d1a51e0ae96ef1b947d5bb271fd GIT binary patch literal 667 zcmV;M0%ZM(P)Px#1ZP1_K>z@;j|==^1poj532;bRa{vGi!~g&e!~vBn4jTXf0x(HLK~y+Tt(8wm zlW`cwKfUyBYiwwjY7|~7FH)CXJQPXZWD_)+mt988ycj`ss6#Aa*bX6T!J5P%M%ap# zscTshbRTq+9lCW;=StYf&39VA_vg=lA(5M(!wb*%`{Q|^=l$_K@1f=8<=^ZL z$&kzCFbqR#7K?@E=4QIPyYc(|Tl!9RC3H9(jE;^H4u@!JI!#+!8&0QFxf>LVMP_Gb zSzMeK=LTi+oopqvTCMneK3dLB;yM8zzj$!F-SW`iWV2b?FHA9)1A_o#V`G#`CFyDA z!>r7A^$NbuNd`k;vr6xu*pI-|VQ4uMCm0MWRV9o>BDl|`@%Kn=U)<^koj9pfO2%5b zTqgB8%)Q$(u8E;R7#Y5cuIp&ASd1Ije~Cc3HDI?xPuBv8L;@|Ho}jB!Idx!uF0*ZI z-S~>rq0~A!brKflvuMYUttqK~EQgmiZuy|lL^kcmPZb>iMzsOWSUXR-qLeyVDnP?g zH`?X)4yN8KwGLjq0&mGCv%_c48wdmfXpN1HT)cFXCo$#LzymQU zfooz32;?mf3=EK*XywBvsqKrzOPKrSrN3VcwLm7UR4PnQX9y3qG7{y#+BbKrSy^(T zgYog??)A0F%*O_WT71jc7C~ zUN9pz^P(7jG5vy-l@(kr7aos?-rinpHk;J9fj?={#>=!VoC*K{002ovPDHLkV1m_C BDV+cS literal 0 HcmV?d00001 diff --git a/SchematicDrawing/Pictures/ValveSw-S-open-dry.png b/SchematicDrawing/Pictures/ValveSw-S-open-dry.png new file mode 100644 index 0000000000000000000000000000000000000000..d54ad96da6c17691d491b43742da2c44e7e67e8c GIT binary patch literal 667 zcmV;M0%ZM(P)Px#1ZP1_K>z@;j|==^1poj532;bRa{vGi!~g&e!~vBn4jTXf0x(HLK~y+TrIb&I zQc)DfzxVWs2r`t3xo8uNn`TkYP=dG!fmY$ zOfAG{Q{b-6Yzh~`5Q-&c4LPHY>61CV_q-#c(-7(p4%~ac_df2u=iI}Q1jeXTD#&Cq z`0)NM1l1>;Ju_JU-cW%TF2m*WAQ%intJMyKTw0K2F@pzp2{XR9D&0!1;<%!*Hqm2Zp-#lqF9=%=A+6u^h)1_==#84hM95#CQ z`s9!Qi|3CeQ@4A#Q(iu{v~}Eaf5h?)(0@d(*$ z7P(vwd%w0JsP}Mq2=w~rU^>639M9+TDKBFb;4dORg)&=*!nyze002ovPDHLkV1h%- BE6M-> literal 0 HcmV?d00001 diff --git a/SchematicDrawing/Pictures/ValveSw-S-open.png b/SchematicDrawing/Pictures/ValveSw-S-open.png new file mode 100644 index 0000000000000000000000000000000000000000..0f0dbd3711480f0690a3dccbe97f422046f9de98 GIT binary patch literal 661 zcmV;G0&4wPx#1ZP1_K>z@;j|==^1poj532;bRa{vGi!~g&e!~vBn4jTXf0xC&FK~y+Tt(8w` z({L2WzeyG>g3L`J6Y(+`9vm|1It!%-Q79Bcr3hZ?p_d9?1fe!ED5Fvs6D_k8DuTs7 z7t!gVC|>mBVIV5j|J$QJv@z9nf|Z7o7mKky)VFTXGEm*0Eu$H=lw=s@!! zolfJ$^QYjs9EOJ){?)G>SbhRtuMeS62sWFo5%?2buxYBQQaBtY=M+y5?ts*9AX;y? z^%cHeCg0$oJ0A zGH`ir5c&K^Fp?zE>^m74*>8p|viuf_lMXP4><-GEdkFB`%!ozgRd}9U1e+_>`xn#M zDPZx#3RvveWqSIjR3Fd;|1&J7teKDfGtB)0J2Av-=dPHpKF|)uF}ep#EEc2a%TK`G z17^e`ORsR_%rP*L9g4dF@!lnvbnHR8>UxqQhQmGUw$~3fQx5U}=fYW^o ze!stQ%8^I}Z{B-x@4gZ5iK|)*R}nPC44Pv{pEy3BNYaJwbFkAkil9&s6buI2W@4)j z%jGf_79K;`m%x#ugSegszIUmp&tHJqYkS-iRnqke(8**n$Y!%B{df(2 vpoF^Cp_STh2qSi5dx1az)oRuJx(ToaIG}za*dOXH00000NkvXXu0mjfkeDQQ literal 0 HcmV?d00001 diff --git a/SchematicDrawing/Pictures/ValveSw-S-vacuum.png b/SchematicDrawing/Pictures/ValveSw-S-vacuum.png new file mode 100644 index 0000000000000000000000000000000000000000..5dbc7edee5b56cffa3925c6c01351cb73559d243 GIT binary patch literal 661 zcmV;G0&4wPx#1ZP1_K>z@;j|==^1poj532;bRa{vGi!~g&e!~vBn4jTXf0xC&FK~y+TrIb%- zl3^Ujzwc|Wh?h|&CfK3aytG5PQGy5y0znc%f;#BIi-DIQ5FG;>h7d-{5+isOt#B68kf9!)ET4{d(WWXR_B;pnl*7&-42}&zI-#PiQuqgaMQs3WWmR zuDph%dyivBd-<>5bP%Pp@Ou4-L?SR43_al>s^NaL+ii-)V&pt8(TgXb_ID6%Yh$|( zdRzd_oHJ57ou+QL+n=w7#?a+*(ozQ0ZT@XE7`vOG#d(nLvX?DHW2h$FZYRBYdHCf2 z#nXq1sn6G6si!0!@m)gH^`ggqu1*pTCG-gpyQ%?*zI-*sZ@%}GnYoA zR;wWzjY4QN8ZuT(~%a2L<5ACKnIz`3rF8KH&0L*fz7-ELyD=SCwj`y7IP$VzG!)sf4Yc vYmkm?VQ&xE?H-5a#0=YBFc@S%LksW+i4}oWgTq;H00000NkvXXu0mjfJiIW$ literal 0 HcmV?d00001 diff --git a/SchematicDrawing/Pictures/ValveSw-XL-closed.png b/SchematicDrawing/Pictures/ValveSw-XL-closed.png new file mode 100644 index 0000000000000000000000000000000000000000..bf00b396ec80d76552344acba8b798dec1a57624 GIT binary patch literal 1183 zcmV;Q1YrA#P)Px#1ZP1_K>z@;j|==^1poj532;bRa{vGi!~g&e!~vBn4jTXf1T;xRK~!i%?V3GM z6G0TmmsE6yj!HrWjWf_OqfU~F8MO$tMMx($kV>rxm3B!QEfWg0ObW-AWYig`$fyM! zU`0pl=wg2F_T`Hs7ZQ`)IsRr~@Af|bZ{K@++1;6Xz1}c?f#c(2Q>|7_rBX3xXJ_)~ z#>&cy35UZb7K@o^G-_5?SM8C}7)i*%!GYP|-#6uQ+16ZVc6L_EtE($1dqGqpkucla z+h%cb(bnFcp@fu5C9}7;*Jbtj`FRtGM9k9ClDs_oZd_bknC3Ih`T4nA&%Qz{?e6Xl z)k?P#g8QUWsV)KQ>+5EDd08ww42U{9Ix>fchqlHMR45e8+}xb4@g`);zBC#Qv$L~f z;_4CzH*Au6I)tC^L1&EWoQ%x+W2IKf!iH_bTw;B1pfcn2>rHwsY^_;BKh zBLXp#f@dCobDoJIy~MZ*=@hZ~v~8V9LoBKRy~JD-!igIj8!ZuqclN-Pz<`}%$eM0k z6Qb#r3#TnYK}_jmJ7(OnS(gwxI!@FJ{RD2Nh9#R_US2vomX;7)iW7jW(b-q{1rmU! z=3WRoHBx?n+H9)%xX%S~L_yk@E-3I&@ z;up?-Ipx0VfY^QUQ6L?^c|VqQbY)7C2BPr_z^^CYcNKww1m(qnM_8u(=Ytl zNs^3mHa2?4e_KL;SfO*_g7iyU=@T$)>X}m#2E7xeT|#DNW<)9TdbpUM;swKEU<{qj zW^HYoE+N1qq@I{*3PI4cNo_grfq^kJ2JhvX5Fm)u2f2y*s~E0w6Q3-XBZ^+$aQ8jK)m-(PUCH&q4$-L7Mdv0eyu4Vz`lJGsMt3<0AgJ-THhw zF&ye80z8EPH$sf|GgwUNTuU-6CnqO%%lV?!pk}(mEwJ6V^`aW)?wDZVAqo#>J<7tu zg1CaN2)YSY3i0wRtfADEL)?d@BJ^j23Q&e#=7b>TCK%T5Tx0|NRtNQ-3%D&`v2=8| x6`+i?UQ6j*bf$yi{lN0N@QiYh80e2Ne*p1J`on+Kqq+b9002ovPDHLkV1mt_7t;U$ literal 0 HcmV?d00001 diff --git a/SchematicDrawing/Pictures/ValveSw-XL-open-dry.png b/SchematicDrawing/Pictures/ValveSw-XL-open-dry.png new file mode 100644 index 0000000000000000000000000000000000000000..653a59064b4e21512dc751c7826f1dfa8d3f3554 GIT binary patch literal 1212 zcmV;t1Vj6YP)Px#1ZP1_K>z@;j|==^1poj532;bRa{vGi!~g&e!~vBn4jTXf1W`#uK~!i%?V3Go zQc)DgPZ?cojf;ayM`Ja#F2;{QM~y=n)uD9M1=w@~CUrs)K?hg7zkA<>$2X-deXsGCyj*#{zVN^2PC^eGOxJ zdRiiph=jvoJN!30*=HgVvC??>HUART0l^f-i z4^K)2;I`F|W&-hs#l=PK;{E7p5w{2g@Uo+1<$-axL6+$ff@;(=3%F83WhE~Vy=Sr; zYYt*M6F{45kz%m~BCvv2L;#*avLSPld^%|p!aBi&Iy)(AcnRV&|H_Ok#%zn+G+m1o z+xlMl?ct}`abcZFtBbU3*EMKbglz}lQeG7Sb|GeeM^b8nMO1)e72jRTDh<9rm$nG8 z;YY9v84PrckkYuBN~QEoKd`Z}A!}=EdOt?+2Z(-so;ZEhdBh(qEG+1e^AH3$*=era zGNYe3H>U@T5wJ(o)YUV3^c5Gb=>cOjh4>LW`Diff$cYJVYYzcp+z2^;Q4fBlCFB_3 ze<4m-{I`f__-l${$IK$%KIy@)uvZLlp!Yjy2v7}&%+FJL^b?uSzx0TP0HuA+cOSZ! z6B1}>T139QGozo_{i@w+etup86dVW^o5uVC2P@!UrQw8_&1QR^iT!Y?vxq*kYaz8- zt)WcR^Fc3PDlFQI50oG26hY*sdg70FuiB4RG2LBbXs+zEy9gYbEEJ5r3I#g)CL}3> z+|pLWwpDl(Q_?@^j?{jkh)ppYD7Mec%$N^~w!IcTJF)DlSJ>Q?Iyj~Y9%j7+rc-MHxzLg3?K#0#-<)MEJZi?>Y+j`fa-?nRdnV3DL0=@!Ea zCk^i87SXH0W9cabI>p!*P6`5h{`zPlpsx_HVt5fRL=5TgFv0S)rpZn=CWeWCfkMEF z;ddgum?q2Y+`+Fky!-hJ^KM2UJ5it>CIWhPb^Ae$m5iZ_-U`+ke!{`;PsUwyuyj#P z3KL`Fkj0Lm?o6?R*pmacUa?1I zEFl(Lu2?KKOZ&Y*5HoFOyAzTLaljS{Hy)RL32}ocs^T;#v6S0dZb%#~xQv|a#+m(Y aME(H~I`EOeI@H<#0000 literal 0 HcmV?d00001 diff --git a/SchematicDrawing/Pictures/ValveSw-XL-open.png b/SchematicDrawing/Pictures/ValveSw-XL-open.png new file mode 100644 index 0000000000000000000000000000000000000000..937f352be60b8566e7872a459bf04c8d2e741060 GIT binary patch literal 1243 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 Hu0mjfXBR&; literal 0 HcmV?d00001 diff --git a/SchematicDrawing/SchematicDrawing.csproj b/SchematicDrawing/SchematicDrawing.csproj index 09adf733c..c438c5178 100644 --- a/SchematicDrawing/SchematicDrawing.csproj +++ b/SchematicDrawing/SchematicDrawing.csproj @@ -98,6 +98,16 @@ + + + + + + + + + + @@ -191,6 +201,12 @@ + + + + + + diff --git a/SharedComponents/LiveLogCache.cs b/SharedComponents/LiveLogCache.cs new file mode 100644 index 000000000..656974152 --- /dev/null +++ b/SharedComponents/LiveLogCache.cs @@ -0,0 +1,234 @@ +using System.IO.MemoryMappedFiles; +using System.Text; + +namespace SharedComponents +{ + public class LiveLogCache + { + /*// Staticka instancia pre singleton + private static readonly LiveLogCache _instance = new LiveLogCache(); + + // Uzamykaci objekt pre bezpecny pristup z viacerych vlakien + private static readonly object _lock = new object(); + + // Zoznam na ukladanie logov + private readonly List _logs = new List(); + + // Udalost pre notifikaciu pri zmene logov + public event Action LogAdded; + + // Sukromny konstruktor (singleton pattern) + private LiveLogCache() { } + + // Metoda na ziskanie poctu logov + public int GetCount() + { + return _logs.Count; + } + + // Staticka metoda na ziskanie instancie + public static LiveLogCache Instance + { + get + { + lock (_lock) + { + return _instance; + } + } + } + + // Verejna vlastnost na ziskanie logov + public IReadOnlyList Logs + { + get + { + lock (_lock) + { + return _logs.AsReadOnly(); + } + } + } + + // Metoda na pridanie logu + public void AddLog(string log) + { + // Ziskanie aktualneho casu v pozadovanom formate + string timeStamp = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss:"); + + // Vytvorenie noveho logu s casovou peciatkou na zaciatku + string logWithTimeStamp = $"{timeStamp} {log}"; + + // Pridanie logu do zoznamu + lock (_lock) + { + _logs.Add(logWithTimeStamp); + } + + // Spustenie udalosti pre notifikaciu + LogAdded?.Invoke(logWithTimeStamp); + } + + // Metoda na vycistenie logov + public void ClearLogs() + { + lock (_lock) + { + _logs.Clear(); + } + + // Volitelne: Spusti udalost, ak by sme chceli notifikovat aj o vymazani logov + LogAdded?.Invoke("Logs were cleared."); + } + + // Ziskanie vsetkych logov + public List GetAllLogs() + { + return new List(_logs); // Vratime kopiu logov + } + + // Ziskanie logov medzi urcitou poziciou (s upravenym indexovanim) + public List GetLogs(int startIndex, int count) + { + // Zabezpeci, ze indexy nebudu mimo rozsahu + return _logs.Skip(startIndex).Take(count).ToList(); + }*/ + + // Zlepseny kod, ktory umoznuje pracu s MemoryMappedFile, namiesto len priestoru vo virtualnej pamati, navyse je tento subor velkostne obmedzeny na 1MB + + // Staticka instancia pre singleton + private static readonly LiveLogCache _instance = new LiveLogCache(true); + + // Uzamykaci objekt pre bezpecny pristup z viacerych vlakien + private static readonly object _lock = new object(); + + // Zoznam na ukladanie logov + private readonly List _logs = new List(); + + // Udalost pre notifikaciu pri zmene logov + public event Action LogAdded; + + // Memory-mapped file + private MemoryMappedFile _mmf; + private MemoryMappedViewAccessor _accessor; + private const int MaxLogSize = 1024 * 1024; // 1MB + + // Sukromny konstruktor (singleton pattern) + private LiveLogCache(bool itsLogsCreator) + { + if (itsLogsCreator == true) + { + _mmf = MemoryMappedFile.CreateOrOpen("LiveLogCacheMMF", MaxLogSize); + } + else + { + _mmf = MemoryMappedFile.OpenExisting("LiveLogCacheMMF"); + } + + _accessor = _mmf.CreateViewAccessor(); + } + + // Metoda na ziskanie poctu logov + public int GetCount() + { + return _logs.Count; + } + + // Staticka metoda na ziskanie instancie + public static LiveLogCache Instance + { + get + { + lock (_lock) + { + return _instance; + } + } + } + + // Verejna vlastnost na ziskanie logov + public IReadOnlyList Logs + { + get + { + lock (_lock) + { + return _logs.AsReadOnly(); + } + } + } + + // Metoda na pridanie logu + public void AddLog(string log) + { + // Ziskanie aktualneho casu v pozadovanom formate + string timeStamp = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss:"); + + // Vytvorenie noveho logu s casovou peciatkou na zaciatku + string logWithTimeStamp = $"{timeStamp} {log}"; + + // Pridanie logu do zoznamu + lock (_lock) + { + _logs.Add(logWithTimeStamp); + WriteLogToMemoryMappedFile(logWithTimeStamp); + } + + // Spustenie udalosti pre notifikaciu + LogAdded?.Invoke(logWithTimeStamp); + } + + // Metoda na vycistenie logov + public void ClearLogs() + { + lock (_lock) + { + _logs.Clear(); + ClearMemoryMappedFile(); + } + + // Volitelne: Spusti udalost, ak by sme chceli notifikovat aj o vymazani logov + LogAdded?.Invoke("Logs were cleared."); + } + + // Ziskanie vsetkych logov + public List GetAllLogs() + { + return new List(_logs); // Vratime kopiu logov + } + + // Ziskanie logov medzi urcitou poziciou (s upravenym indexovanim) + public List GetLogs(int startIndex, int count) + { + // Zabezpeci, ze indexy nebudu mimo rozsahu + return _logs.Skip(startIndex).Take(count).ToList(); + } + + // Metoda na naplnenie ListView + /*public void PopulateListView(ListView listView) + { + listView.Items.Clear(); + lock (_lock) + { + foreach (var log in _logs) + { + listView.Items.Add(new ListView Item(log)); + } + } + }*/ + + // Write log to memory-mapped file + private void WriteLogToMemoryMappedFile(string log) + { + byte[] logBytes = Encoding.UTF8.GetBytes(log + Environment.NewLine); + _accessor.WriteArray(0, logBytes, 0, logBytes.Length); + } + + // Clear memory-mapped file + private void ClearMemoryMappedFile() + { + byte[] emptyBytes = new byte[MaxLogSize]; + _accessor.WriteArray(0, emptyBytes, 0, emptyBytes.Length); + } + } +} diff --git a/SharedComponents/SharedComponents.csproj b/SharedComponents/SharedComponents.csproj new file mode 100644 index 000000000..06e92ea43 --- /dev/null +++ b/SharedComponents/SharedComponents.csproj @@ -0,0 +1,10 @@ + + + + net472 + 10.0 + enable + enable + + + diff --git a/SharedComponents/obj/Debug/net472/SharedComponents.GeneratedMSBuildEditorConfig.editorconfig b/SharedComponents/obj/Debug/net472/SharedComponents.GeneratedMSBuildEditorConfig.editorconfig new file mode 100644 index 000000000..a02128ff9 --- /dev/null +++ b/SharedComponents/obj/Debug/net472/SharedComponents.GeneratedMSBuildEditorConfig.editorconfig @@ -0,0 +1,5 @@ +is_global = true +build_property.RootNamespace = SharedComponents +build_property.ProjectDir = C:\Users\micha\git\tbf\SharedComponents\ +build_property.EnableComHosting = +build_property.EnableGeneratedComInterfaceComImportInterop = diff --git a/SharedComponents/obj/Debug/net472/SharedComponents.GlobalUsings.g.cs b/SharedComponents/obj/Debug/net472/SharedComponents.GlobalUsings.g.cs new file mode 100644 index 000000000..c11666bec --- /dev/null +++ b/SharedComponents/obj/Debug/net472/SharedComponents.GlobalUsings.g.cs @@ -0,0 +1,7 @@ +// +global using global::System; +global using global::System.Collections.Generic; +global using global::System.IO; +global using global::System.Linq; +global using global::System.Threading; +global using global::System.Threading.Tasks; diff --git a/SharedComponents/obj/SharedComponents.csproj.nuget.g.props b/SharedComponents/obj/SharedComponents.csproj.nuget.g.props new file mode 100644 index 000000000..c2b287e0d --- /dev/null +++ b/SharedComponents/obj/SharedComponents.csproj.nuget.g.props @@ -0,0 +1,15 @@ + + + + True + NuGet + $(MSBuildThisFileDirectory)project.assets.json + $(UserProfile)\.nuget\packages\ + C:\Users\micha\.nuget\packages\ + PackageReference + 6.14.0 + + + + + \ No newline at end of file diff --git a/SharedComponents/obj/SharedComponents.csproj.nuget.g.targets b/SharedComponents/obj/SharedComponents.csproj.nuget.g.targets new file mode 100644 index 000000000..babc2c6b6 --- /dev/null +++ b/SharedComponents/obj/SharedComponents.csproj.nuget.g.targets @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/TBF/Tools/ByteFormatter.cs b/TBF/Tools/ByteFormatter.cs new file mode 100644 index 000000000..0e1d3ff99 --- /dev/null +++ b/TBF/Tools/ByteFormatter.cs @@ -0,0 +1,62 @@ +/// +/// Copyright (c) 2013-2015 Sensus Metering Systems +/// +using System; +using System.Globalization; + +namespace TBF.Tools +{ + public class ByteFormatter : IFormatProvider, ICustomFormatter + { + public ByteFormatter() + { + } + + public object GetFormat(Type formatType) + { + if (formatType == typeof(ICustomFormatter)) + return this; + else + return null; + } + + public string Format(string fmt, object arg, IFormatProvider formatProvider) + { + if (arg.GetType() != typeof(byte)) + { + try + { + return HandleOtherFormats(fmt, arg); + } + catch (FormatException e) + { + throw new FormatException(String.Format("The format of '{0}' is invalid.", fmt), e); + } + } + + /// + /// Convert one byte to string + /// + byte b = (byte)arg; + if ((32 <= b) && (b <= 127)) return "'" + ((char)b).ToString() + "'"; + if ((char)b == '\r') return "'\\r'"; + if ((char)b == '\n') return "'\\n'"; + if ((char)b == '\t') return "'\\t'"; + if (b == 17) return "XON"; + if (b == 19) return "XOFF"; + if (b == 26) return "Ctrl-Z"; + return b.ToString(); + } + + + private string HandleOtherFormats(string format, object arg) + { + if (arg is IFormattable) + return ((IFormattable)arg).ToString(format, CultureInfo.CurrentCulture); + else if (arg != null) + return arg.ToString(); + else + return String.Empty; + } + } +} diff --git a/TBF/Tools/IniUtil.cs b/TBF/Tools/IniUtil.cs new file mode 100644 index 000000000..a190050a9 --- /dev/null +++ b/TBF/Tools/IniUtil.cs @@ -0,0 +1,108 @@ +/// +/// Copyright (c) 2013-2015 Sensus Metering Systems +/// +using System; +using System.Text; +using System.Runtime.InteropServices; +using System.Collections.Specialized; + +namespace TBF.Tools +{ + /// + /// The class use the WinApi GetPrivateProfileSectionNames, + /// GetPrivateProfileSection, GetPrivateProfileString, WritePrivateProfileString + /// and present easy methods to work from a NET point of view. + /// Usage: + /// IniUtil ini = new IniUtil(@"C:\program files (x86)\myapp\myapp.ini"); + /// string country = ini.GetValue("Carrier","Country","NoCountry"); + /// + public class IniUtil + { + [DllImport("kernel32.dll")] + private static extern int GetPrivateProfileSectionNames(byte[] lpszReturnBuffer, int nSize, string lpFileName); + [DllImport("kernel32.dll")] + private static extern int GetPrivateProfileSection(string lpAppName, byte[] lpReturnedString, int nSize, string lpFileName); + [DllImport("kernel32.dll")] + private static extern int GetPrivateProfileString(string lpApplicationName, string lpKeyName, string lpDefault, byte[] lpReturnedString, int nSize, string lpFileName); + [DllImport("kernel32.dll")] + private static extern bool WritePrivateProfileString(string lpApplicationName, string lpKeyName, string lpString, string lpFileName); + + private const int VALUE_BUFFER = 511; + private const int SECTION_BUFFER = (1024 * 16); + private string m_sIniFile; + + /// + /// .ctor with INI file name + /// + /// Fullpath to the INI file + public IniUtil(string fileName) + { + m_sIniFile = fileName; + } + + /// + /// Set the value for a specific key in a section + /// + /// Section containing the key to write to + /// Key to insert/update + /// Value for the key + /// True if OK + public bool SetValue(string section, string key, string keyvalue) + { + return WritePrivateProfileString(section, key, keyvalue, m_sIniFile); + } + + /// + /// Gets the value of the specidied key in the specified section, + /// If the key doesn't exists returns the default value + /// + /// Section containing the key to read from + /// Required key + /// Value to return in case the key is missing + /// string value of the key or missing value + public string GetValue(string section, string key, string ifMissing) + { + byte[] by = new byte[VALUE_BUFFER]; + int n = GetPrivateProfileString(section, key, ifMissing, by, VALUE_BUFFER, m_sIniFile); + string s = Encoding.ASCII.GetString(by); + return s.Substring(0, n); + } + + /// + /// Returns the NameValueCollection for every key in the section + /// + /// Section name + /// NameValueCollection with nake=Key and value=value + public NameValueCollection GetSectionKeysvalues(string section) + { + NameValueCollection n = new NameValueCollection(); + if(section.Length > 0) + { + byte[] by = new byte[SECTION_BUFFER]; + int x = GetPrivateProfileSection(section, by, SECTION_BUFFER, m_sIniFile); + if(x > 0) x--; + string keysvalues = Encoding.ASCII.GetString(by, 0, x); + string[] temp = keysvalues.Split('\0'); + foreach(string s in temp) + { + string[] t = s.Split('='); + n.Add(t[0], t[1]); + } + } + return n; + } + + /// + /// Get the names of all sections in .INI + /// + /// string array with all the key names + public string[] GetSectionNames() + { + byte[] by = new byte[SECTION_BUFFER]; + int x = GetPrivateProfileSectionNames(by, SECTION_BUFFER, m_sIniFile); + if(x > 0) x--; + string keys = Encoding.ASCII.GetString(by, 0, x); + return keys.Split('\0'); + } + } +} diff --git a/TBF/Tools/LogChecker.cs b/TBF/Tools/LogChecker.cs new file mode 100644 index 000000000..fa76f96e9 --- /dev/null +++ b/TBF/Tools/LogChecker.cs @@ -0,0 +1,50 @@ +using log4net.Appender; +using log4net.Core; +using log4net.Repository.Hierarchy; +using log4net; +using System; +using System.Collections.Generic; + +namespace TBF.Tools +{ + public class LogChecker : IDisposable + { + readonly Logger _logger; + readonly Level _previousLevel; + readonly MemoryAppender _appender = new MemoryAppender(); + + public LogChecker(string logName, Level levelToCheck) + { + _logger = (Logger)LogManager.GetLogger(logName).Logger; + _logger.AddAppender(_appender); + _previousLevel = _logger.Level; + _logger.Level = levelToCheck; + } + + public List Messages + { + get + { + return new List(_appender.GetEvents()) + .ConvertAll(x => x.RenderedMessage); + } + } + + public void Dispose() + { + _logger.Level = _previousLevel; + _logger.RemoveAppender(_appender); + } + } +} + +// Example of use +// +// using (Tools.LogChecker logChecker = new Tools.LogChecker("RfidData", log4net.Core.Level.Debug)) +// { +// Execute query using NHibernate +// .... +// +// MessageBox.Show(String.Join(Environment.NewLine,logChecker.Messages)); +// } +// diff --git a/TBF/Tools/XmlTools.cs b/TBF/Tools/XmlTools.cs new file mode 100644 index 000000000..5deff02a9 --- /dev/null +++ b/TBF/Tools/XmlTools.cs @@ -0,0 +1,30 @@ +/// +/// Copyright (c) 2013-2015 Sensus Metering Systems +/// +using System.IO; +using System.Xml.Serialization; + +namespace TBF.Tools +{ + public static class XmlTools + { + public static string ToXmlString(this T input) + { + using (var writer = new StringWriter()) + { + input.ToXml(writer); + return writer.ToString(); + } + } + + public static void ToXml(this T objectToSerialize, Stream stream) + { + new XmlSerializer(typeof(T)).Serialize(stream, objectToSerialize); + } + + public static void ToXml(this T objectToSerialize, StringWriter writer) + { + new XmlSerializer(typeof(T)).Serialize(writer, objectToSerialize); + } + } +} diff --git a/TBF/UI/Shared/SuggestComboBox.cs b/TBF/UI/Shared/SuggestComboBox.cs new file mode 100644 index 000000000..0ece9c518 --- /dev/null +++ b/TBF/UI/Shared/SuggestComboBox.cs @@ -0,0 +1,240 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Drawing; +using System.Linq; +using System.Linq.Expressions; +using System.Windows.Forms; + +namespace TBF.UI.Shared +{ + public class SuggestComboBox : ComboBox + { + #region fields and properties + + private readonly ListBox _suggLb = new ListBox { Visible = false, TabStop = false }; + private readonly BindingList _suggBindingList = new BindingList(); + private Expression>> _propertySelector; + private Func> _propertySelectorCompiled; + private Expression> _filterRule; + private Func _filterRuleCompiled; + private Expression> _suggestListOrderRule; + private Func _suggestListOrderRuleCompiled; + + public int SuggestBoxHeight + { + get { return _suggLb.Height; } + set { if (value > 0) _suggLb.Height = value; } + } + + /// + /// If the item-type of the ComboBox is not string, + /// you can set here which property should be used + /// + public Expression>> PropertySelector + { + get { return _propertySelector; } + set + { + if (value == null) return; + _propertySelector = value; + _propertySelectorCompiled = value.Compile(); + } + } + + /// + /// Lambda-Expression to determine the suggested items + /// (as Expression here because simple lamda (func) is not serializable) + /// default: case-insensitive contains search + /// 1st string: list item + /// 2nd string: typed text + /// + public Expression> FilterRule + { + get { return _filterRule; } + set + { + if (value == null) return; + _filterRule = value; + _filterRuleCompiled = item => value.Compile()(item, Text); + } + } + + /// + /// Lambda-Expression to order the suggested items + /// (as Expression here because simple lamda (func) is not serializable) + /// default: alphabetic ordering + /// + public Expression> SuggestListOrderRule + { + get { return _suggestListOrderRule; } + set + { + if (value == null) return; + _suggestListOrderRule = value; + _suggestListOrderRuleCompiled = value.Compile(); + } + } + + #endregion + + /// + /// ctor + /// + public SuggestComboBox() + { + // set the standard rules: + _filterRuleCompiled = s => s.ToLower().Contains(Text.Trim().ToLower()); + _suggestListOrderRuleCompiled = s => s; + _propertySelectorCompiled = collection => collection.Cast(); + + _suggLb.DataSource = _suggBindingList; + _suggLb.Click += SuggLbOnClick; + + ParentChanged += OnParentChanged; + } + + /// + /// the magic happens here ;-) + /// + /// + protected override void OnTextChanged(EventArgs e) + { + base.OnTextChanged(e); + + if (!Focused) return; + + _suggBindingList.Clear(); + _suggBindingList.RaiseListChangedEvents = false; + _propertySelectorCompiled(Items) + .Where(_filterRuleCompiled) + .OrderBy(_suggestListOrderRuleCompiled) + .ToList() + .ForEach(_suggBindingList.Add); + _suggBindingList.RaiseListChangedEvents = true; + _suggBindingList.ResetBindings(); + + _suggLb.Visible = _suggBindingList.Any(); + + if (_suggBindingList.Count == 1 && + _suggBindingList.Single().Length == Text.Trim().Length) + { + Text = _suggBindingList.Single(); + Select(0, Text.Length); + _suggLb.Visible = false; + } + } + + #region size and position of suggest box + + /// + /// suggest-ListBox is added to parent control + /// (in ctor parent isn't already assigned) + /// + /// + /// + private void OnParentChanged(object sender, EventArgs e) + { + Parent.Controls.Add(_suggLb); + Parent.Controls.SetChildIndex(_suggLb, 0); + _suggLb.Top = Top + Height - 3; + _suggLb.Left = Left + 3; + _suggLb.Width = Width - 20; + _suggLb.Font = new Font("Segoe UI", 9); + } + + protected override void OnLocationChanged(EventArgs e) + { + base.OnLocationChanged(e); + _suggLb.Top = Top + Height + 0; + _suggLb.Left = Left + 3; + } + + protected override void OnSizeChanged(EventArgs e) + { + base.OnSizeChanged(e); + _suggLb.Width = Width; + } + + #endregion + + #region visibility of suggest box + + protected override void OnLostFocus(EventArgs e) + { + // _suggLb can only getting focused by clicking (because TabStop is off) + // --> click-eventhandler 'SuggLbOnClick' is called + if (!_suggLb.Focused) + HideSuggBox(); + base.OnLostFocus(e); + } + + private void SuggLbOnClick(object sender, EventArgs eventArgs) + { + Text = _suggLb.Text; + Focus(); + } + + private void HideSuggBox() + { + _suggLb.Visible = false; + } + + protected override void OnDropDown(EventArgs e) + { + HideSuggBox(); + base.OnDropDown(e); + } + + #endregion + + #region keystroke events + + /// + /// if the suggest-ListBox is visible some keystrokes + /// should behave in a custom way + /// + /// + protected override void OnPreviewKeyDown(PreviewKeyDownEventArgs e) + { + if (!_suggLb.Visible) + { + base.OnPreviewKeyDown(e); + return; + } + + switch (e.KeyCode) + { + case Keys.Down: + if (_suggLb.SelectedIndex < _suggBindingList.Count - 1) + _suggLb.SelectedIndex++; + return; + case Keys.Up: + if (_suggLb.SelectedIndex > 0) + _suggLb.SelectedIndex--; + return; + case Keys.Enter: + Text = _suggLb.Text; + Select(0, Text.Length); + _suggLb.Visible = false; + return; + case Keys.Escape: + HideSuggBox(); + return; + } + + base.OnPreviewKeyDown(e); + } + + private static readonly Keys[] KeysToHandle = new[] { Keys.Down, Keys.Up, Keys.Enter, Keys.Escape }; + protected override bool ProcessCmdKey(ref Message msg, Keys keyData) + { + // the keysstrokes of our interest should not be processed be base class: + if (_suggLb.Visible && KeysToHandle.Contains(keyData)) + return true; + return base.ProcessCmdKey(ref msg, keyData); + } + + #endregion + } +}