24 Commits

Author SHA1 Message Date
62554d7fd6 God mode non optional on final test 2025-12-12 11:22:46 +00:00
03dfb3414b V4.7 2025-12-09 09:00:16 +00:00
760987fa75 Merged colours 2025-12-02 13:08:08 +00:00
30bd2fe73c AddToAction List colours 2025-12-02 12:59:40 +00:00
4c624d7e29 V4.6 2025-12-02 11:02:24 +00:00
e29d104d47 V4.5 2025-11-26 14:39:07 +00:00
791cee91d2 V4.4 with modified god mode and 211 at end of soak test. 2025-11-11 14:17:31 +00:00
00b225a3db V4.4 merged 2025-11-11 13:58:29 +00:00
e94a1f1f5c Find cams timeout extended 2025-11-11 13:49:42 +00:00
a91c3b846f V4.4 Selenium fix 2025-11-11 13:18:02 +00:00
42a4778555 V4.3 release 2025-11-04 14:29:38 +00:00
3d71ab7e9b V4.3 changes and merge 2025-11-04 13:43:20 +00:00
78c440ab76 Updated other uses of Internal Config to GLOBAL--FlexiApplication 2025-11-04 13:16:07 +00:00
dd8e87258f V4.3 Changes - Bradley B 2025-11-04 12:56:16 +00:00
1c89cf2847 V4.2 with stats and vaxtor settings changes. Fixed merge problems 2025-10-29 16:17:59 +00:00
f95e958ffb V4.1 with excel stats changes 2025-10-28 16:13:05 +00:00
95fb5d6ab8 V4.1 with excel stats changes 2025-10-28 16:12:53 +00:00
c18514802c Merge branch 'master' of https://mavportal.com:3000/BradleyRelyea/AiQ_GUI 2025-10-28 12:35:55 +00:00
f78e44903b V4.1 changes BR 2025-10-28 12:35:28 +00:00
ed121fa3db GUI Version 4.1 CHanges are GB sticker and Updated API call from Internal Config to GLOBAL--FlexiApplication. 2025-10-27 11:14:20 +00:00
aa28a43347 V4.0 2025-10-21 15:46:28 +01:00
cfe8a57a9d Merged ready for V4 2025-10-21 14:28:45 +01:00
01268ddda5 Changes to SSH including new password 2025-10-21 14:01:27 +01:00
e39a36183a V3.15 iterim BR changes 2025-10-17 14:26:33 +01:00
31 changed files with 1242 additions and 1026 deletions

104
AiQ_GUI.Designer.cs generated
View File

@@ -141,9 +141,12 @@ namespace AiQ_GUI
groupBox3 = new GroupBox();
groupBox2 = new GroupBox();
groupBox1 = new GroupBox();
BtnBaudModules = new Button();
BtnZoom8000 = new Button();
BtnZoomWide = new Button();
TabSettings = new TabPage();
BtnUploadWonwooSetIR = new Button();
BtnUploadWonwooSetOV = new Button();
BtnAdminStart = new Button();
BtnFirewall = new Button();
TabImages = new TabPage();
@@ -158,6 +161,7 @@ namespace AiQ_GUI
LblGUIVers = new Label();
timerTypeIP = new System.Windows.Forms.Timer(components);
TimerFlash = new System.Windows.Forms.Timer(components);
BtnOpenMoba = new Button();
((System.ComponentModel.ISupportInitialize)PicBxMAV).BeginInit();
((System.ComponentModel.ISupportInitialize)PicBxAiQ).BeginInit();
((System.ComponentModel.ISupportInitialize)PicBxIRF2).BeginInit();
@@ -273,6 +277,7 @@ namespace AiQ_GUI
CbBxUserName.Items.AddRange(new object[] { "Guest", "Clive", "Conor", "Charlie", "Henry", "Sam C", "Sam L", "Simon", "Sophie", "Toby", "Tom" });
CbBxUserName.Location = new Point(10, 262);
CbBxUserName.Margin = new Padding(4, 3, 4, 3);
CbBxUserName.MaxDropDownItems = 20;
CbBxUserName.Name = "CbBxUserName";
CbBxUserName.Size = new Size(233, 25);
CbBxUserName.TabIndex = 159;
@@ -817,7 +822,7 @@ namespace AiQ_GUI
BtnCancel.Location = new Point(201, 185);
BtnCancel.Margin = new Padding(4, 3, 4, 3);
BtnCancel.Name = "BtnCancel";
BtnCancel.Size = new Size(181, 37);
BtnCancel.Size = new Size(180, 37);
BtnCancel.TabIndex = 174;
BtnCancel.Text = "Cancel";
BtnCancel.UseVisualStyleBackColor = false;
@@ -833,7 +838,7 @@ namespace AiQ_GUI
BtnSave.Location = new Point(10, 185);
BtnSave.Margin = new Padding(4, 3, 4, 3);
BtnSave.Name = "BtnSave";
BtnSave.Size = new Size(181, 37);
BtnSave.Size = new Size(180, 37);
BtnSave.TabIndex = 173;
BtnSave.Text = "Save && Check";
BtnSave.UseVisualStyleBackColor = false;
@@ -1145,7 +1150,7 @@ namespace AiQ_GUI
TxBxFlexiVer.Name = "TxBxFlexiVer";
TxBxFlexiVer.Size = new Size(205, 25);
TxBxFlexiVer.TabIndex = 162;
TxBxFlexiVer.Text = "1.6.4";
TxBxFlexiVer.Text = "1.7.1";
TxBxFlexiVer.TextAlign = HorizontalAlignment.Center;
TxBxFlexiVer.TextChanged += TxBxFlexiVer_TextChanged;
//
@@ -1251,7 +1256,7 @@ namespace AiQ_GUI
CbBxType.ForeColor = SystemColors.Control;
CbBxType.FormattingEnabled = true;
CbBxType.IntegralHeight = false;
CbBxType.Items.AddRange(new object[] { "Store & Forward", "Streaming", "Audit" });
CbBxType.Items.AddRange(new object[] { "Store & Forward", "Streaming", "Audit", "XML Server" });
CbBxType.Location = new Point(177, 62);
CbBxType.Margin = new Padding(4, 3, 4, 3);
CbBxType.MaxDropDownItems = 30;
@@ -1778,6 +1783,7 @@ namespace AiQ_GUI
//
// groupBox1
//
groupBox1.Controls.Add(BtnBaudModules);
groupBox1.Controls.Add(BtnZoom8000);
groupBox1.Controls.Add(BtnZoomWide);
groupBox1.Controls.Add(BtnLicVaxtor);
@@ -1798,18 +1804,35 @@ namespace AiQ_GUI
groupBox1.TabStop = false;
groupBox1.Text = "Controls";
//
// BtnBaudModules
//
BtnBaudModules.BackColor = Color.FromArgb(70, 65, 80);
BtnBaudModules.Enabled = false;
BtnBaudModules.FlatAppearance.BorderSize = 0;
BtnBaudModules.FlatStyle = FlatStyle.Flat;
BtnBaudModules.Font = new Font("Segoe UI Semibold", 10F, FontStyle.Bold);
BtnBaudModules.ForeColor = SystemColors.Control;
BtnBaudModules.Location = new Point(12, 155);
BtnBaudModules.Margin = new Padding(4, 3, 4, 3);
BtnBaudModules.Name = "BtnBaudModules";
BtnBaudModules.Size = new Size(180, 37);
BtnBaudModules.TabIndex = 237;
BtnBaudModules.Text = "Set baudrate to 115200";
BtnBaudModules.UseVisualStyleBackColor = false;
BtnBaudModules.Click += BtnBaudModules_Click;
//
// BtnZoom8000
//
BtnZoom8000.BackColor = Color.FromArgb(70, 65, 80);
BtnZoom8000.Enabled = false;
BtnZoom8000.FlatAppearance.BorderSize = 0;
BtnZoom8000.FlatStyle = FlatStyle.Flat;
BtnZoom8000.Font = new Font("Segoe UI Semibold", 10F, FontStyle.Bold);
BtnZoom8000.Font = new Font("Segoe UI Semibold", 8F, FontStyle.Bold);
BtnZoom8000.ForeColor = SystemColors.Control;
BtnZoom8000.Location = new Point(204, 155);
BtnZoom8000.Location = new Point(299, 155);
BtnZoom8000.Margin = new Padding(4, 3, 4, 3);
BtnZoom8000.Name = "BtnZoom8000";
BtnZoom8000.Size = new Size(180, 37);
BtnZoom8000.Size = new Size(85, 37);
BtnZoom8000.TabIndex = 236;
BtnZoom8000.Text = "Zoom to 8000";
BtnZoom8000.UseVisualStyleBackColor = false;
@@ -1821,12 +1844,12 @@ namespace AiQ_GUI
BtnZoomWide.Enabled = false;
BtnZoomWide.FlatAppearance.BorderSize = 0;
BtnZoomWide.FlatStyle = FlatStyle.Flat;
BtnZoomWide.Font = new Font("Segoe UI Semibold", 10F, FontStyle.Bold);
BtnZoomWide.Font = new Font("Segoe UI Semibold", 8F, FontStyle.Bold);
BtnZoomWide.ForeColor = SystemColors.Control;
BtnZoomWide.Location = new Point(12, 154);
BtnZoomWide.Location = new Point(204, 155);
BtnZoomWide.Margin = new Padding(4, 3, 4, 3);
BtnZoomWide.Name = "BtnZoomWide";
BtnZoomWide.Size = new Size(180, 37);
BtnZoomWide.Size = new Size(85, 37);
BtnZoomWide.TabIndex = 235;
BtnZoomWide.Text = "Zoom Full Wide";
BtnZoomWide.UseVisualStyleBackColor = false;
@@ -1835,6 +1858,9 @@ namespace AiQ_GUI
// TabSettings
//
TabSettings.BackColor = Color.FromArgb(39, 37, 55);
TabSettings.Controls.Add(BtnOpenMoba);
TabSettings.Controls.Add(BtnUploadWonwooSetIR);
TabSettings.Controls.Add(BtnUploadWonwooSetOV);
TabSettings.Controls.Add(BtnAdminStart);
TabSettings.Controls.Add(BtnFirewall);
TabSettings.Controls.Add(PanelSettings);
@@ -1845,6 +1871,40 @@ namespace AiQ_GUI
TabSettings.TabIndex = 3;
TabSettings.Text = "Settings";
//
// BtnUploadWonwooSetIR
//
BtnUploadWonwooSetIR.BackColor = Color.FromArgb(70, 65, 80);
BtnUploadWonwooSetIR.FlatAppearance.BorderColor = Color.FromArgb(70, 65, 80);
BtnUploadWonwooSetIR.FlatAppearance.BorderSize = 0;
BtnUploadWonwooSetIR.FlatStyle = FlatStyle.Flat;
BtnUploadWonwooSetIR.Font = new Font("Segoe UI Semibold", 10F, FontStyle.Bold);
BtnUploadWonwooSetIR.ForeColor = SystemColors.Control;
BtnUploadWonwooSetIR.Location = new Point(210, 304);
BtnUploadWonwooSetIR.Margin = new Padding(4, 3, 4, 3);
BtnUploadWonwooSetIR.Name = "BtnUploadWonwooSetIR";
BtnUploadWonwooSetIR.Size = new Size(180, 49);
BtnUploadWonwooSetIR.TabIndex = 244;
BtnUploadWonwooSetIR.Text = "Upload Wonwoo Settings IR";
BtnUploadWonwooSetIR.UseVisualStyleBackColor = false;
BtnUploadWonwooSetIR.Click += UploadWonwooSetIR_Click;
//
// BtnUploadWonwooSetOV
//
BtnUploadWonwooSetOV.BackColor = Color.FromArgb(70, 65, 80);
BtnUploadWonwooSetOV.FlatAppearance.BorderColor = Color.FromArgb(70, 65, 80);
BtnUploadWonwooSetOV.FlatAppearance.BorderSize = 0;
BtnUploadWonwooSetOV.FlatStyle = FlatStyle.Flat;
BtnUploadWonwooSetOV.Font = new Font("Segoe UI Semibold", 10F, FontStyle.Bold);
BtnUploadWonwooSetOV.ForeColor = SystemColors.Control;
BtnUploadWonwooSetOV.Location = new Point(19, 304);
BtnUploadWonwooSetOV.Margin = new Padding(4, 3, 4, 3);
BtnUploadWonwooSetOV.Name = "BtnUploadWonwooSetOV";
BtnUploadWonwooSetOV.Size = new Size(180, 49);
BtnUploadWonwooSetOV.TabIndex = 243;
BtnUploadWonwooSetOV.Text = "Upload Wonwoo Settings OV";
BtnUploadWonwooSetOV.UseVisualStyleBackColor = false;
BtnUploadWonwooSetOV.Click += UploadWonwooSetOV_Click;
//
// BtnAdminStart
//
BtnAdminStart.BackColor = Color.FromArgb(70, 65, 80);
@@ -1855,7 +1915,7 @@ namespace AiQ_GUI
BtnAdminStart.Location = new Point(210, 261);
BtnAdminStart.Margin = new Padding(4, 3, 4, 3);
BtnAdminStart.Name = "BtnAdminStart";
BtnAdminStart.Size = new Size(181, 37);
BtnAdminStart.Size = new Size(180, 37);
BtnAdminStart.TabIndex = 241;
BtnAdminStart.Text = "Start in admin mode";
BtnAdminStart.UseVisualStyleBackColor = false;
@@ -1871,7 +1931,7 @@ namespace AiQ_GUI
BtnFirewall.Location = new Point(19, 261);
BtnFirewall.Margin = new Padding(4, 3, 4, 3);
BtnFirewall.Name = "BtnFirewall";
BtnFirewall.Size = new Size(181, 37);
BtnFirewall.Size = new Size(180, 37);
BtnFirewall.TabIndex = 240;
BtnFirewall.Text = "Update Firewall";
BtnFirewall.UseVisualStyleBackColor = false;
@@ -2018,6 +2078,22 @@ namespace AiQ_GUI
timerTypeIP.Interval = 2000;
timerTypeIP.Tick += timerTypeIP_Tick;
//
// BtnOpenMoba
//
BtnOpenMoba.BackColor = Color.FromArgb(70, 65, 80);
BtnOpenMoba.FlatAppearance.BorderSize = 0;
BtnOpenMoba.FlatStyle = FlatStyle.Flat;
BtnOpenMoba.Font = new Font("Segoe UI Semibold", 10F, FontStyle.Bold);
BtnOpenMoba.ForeColor = SystemColors.Control;
BtnOpenMoba.Location = new Point(210, 359);
BtnOpenMoba.Margin = new Padding(4, 3, 4, 3);
BtnOpenMoba.Name = "BtnOpenMoba";
BtnOpenMoba.Size = new Size(180, 37);
BtnOpenMoba.TabIndex = 245;
BtnOpenMoba.Text = "Open Moba";
BtnOpenMoba.UseVisualStyleBackColor = false;
BtnOpenMoba.Click += BtnOpenMoba_Click;
//
// MainForm
//
AutoScaleDimensions = new SizeF(7F, 15F);
@@ -2210,5 +2286,9 @@ namespace AiQ_GUI
private Button BtnAdminStart;
private Button SetGodModeAll;
private Button BtnFactoryDefault;
private Button BtnUploadWonwooSetOV;
private Button BtnUploadWonwooSetIR;
private Button BtnBaudModules;
private Button BtnOpenMoba;
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -124,7 +124,7 @@
<value>159, 17</value>
</metadata>
<metadata name="TimerDDC.TrayLocation" type="System.Drawing.Point, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
<value>690, 19</value>
<value>531, 18</value>
</metadata>
<metadata name="timerTypeIP.TrayLocation" type="System.Drawing.Point, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
<value>305, 17</value>

View File

@@ -2,7 +2,7 @@
<PropertyGroup>
<OutputType>WinExe</OutputType>
<TargetFramework>net9.0-windows7.0</TargetFramework>
<TargetFramework>net10.0-windows7.0</TargetFramework>
<Nullable>enable</Nullable>
<UseWindowsForms>true</UseWindowsForms>
<ImplicitUsings>enable</ImplicitUsings>
@@ -16,7 +16,7 @@
<Product>AiQ GUI</Product>
<Authors>MAV Systems Ltd</Authors>
<PackageId>AiQ GUI</PackageId>
<Version>3.14.0</Version>
<Version>4.7.0</Version>
<Description>A GUI to control and test the AiQ</Description>
<Copyright>MAV Systems Ltd 2025</Copyright>
<PackageIcon>MAV - Plain - Blue.png</PackageIcon>
@@ -26,14 +26,25 @@
<ProduceReferenceAssembly>False</ProduceReferenceAssembly>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<SignAssembly>False</SignAssembly>
<StartupObject>AiQ_GUI.Program</StartupObject>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
<Optimize>True</Optimize>
<DefineConstants>$(DefineConstants);_PUBLISH_CHROMEDRIVER</DefineConstants>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x86'">
<Optimize>True</Optimize>
<DefineConstants>$(DefineConstants);_PUBLISH_CHROMEDRIVER</DefineConstants>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'">
<DefineConstants>$(DefineConstants);_PUBLISH_CHROMEDRIVER</DefineConstants>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x86'">
<DefineConstants>$(DefineConstants);_PUBLISH_CHROMEDRIVER</DefineConstants>
</PropertyGroup>
<ItemGroup>
@@ -42,19 +53,17 @@
<ItemGroup>
<PackageReference Include="ClosedXML" Version="0.105.0" />
<PackageReference Include="DocumentFormat.OpenXml" Version="3.3.0" />
<PackageReference Include="Emgu.CV" Version="4.12.0.5764" />
<PackageReference Include="Emgu.CV.runtime.windows" Version="4.12.0.5764" />
<PackageReference Include="Google.Apis.Auth" Version="1.72.0" />
<PackageReference Include="Google.Apis.Gmail.v1" Version="1.70.0.3833" />
<PackageReference Include="Google.Apis.Sheets.v4" Version="1.70.0.3819" />
<PackageReference Include="Google.Apis.Auth" Version="1.73.0" />
<PackageReference Include="Google.Apis.Sheets.v4" Version="1.72.0.3966" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.4" />
<PackageReference Include="PDFsharp-MigraDoc-gdi" Version="6.2.2" />
<PackageReference Include="Selenium.Support" Version="4.36.0" />
<PackageReference Include="Selenium.WebDriver" Version="4.36.0" />
<PackageReference Include="Selenium.WebDriver.ChromeDriver" Version="141.0.7390.7600" />
<PackageReference Include="SSH.NET" Version="2025.0.0" />
<PackageReference Include="System.Data.OleDb" Version="9.0.9" />
<PackageReference Include="PDFsharp-MigraDoc-gdi" Version="6.2.3" />
<PackageReference Include="Selenium.Support" Version="4.39.0" />
<PackageReference Include="Selenium.WebDriver" Version="4.39.0" />
<PackageReference Include="Selenium.WebDriver.ChromeDriver" Version="143.0.7499.4000" />
<PackageReference Include="SSH.NET" Version="2025.1.0" />
<PackageReference Include="System.Data.OleDb" Version="10.0.0" />
</ItemGroup>
<ItemGroup>

View File

@@ -4,11 +4,11 @@ namespace AiQ_GUI
internal class CameraModules
{
// Chack camera modules are in default state according to what the diagnostics API.
public static void CheckCamModule(Module CamMod, Label Lbl)
public static void CheckCamModule(Module CamMod, Label Lbl, Camera CamOnTest)
{
if (CamMod == null || Lbl == null)
{
MainForm.Instance.AddToActionsList("Camera module or label was null in CheckCamModule.");
MainForm.Instance.AddToActionsList("Camera module or label was null in CheckCamModule.", Level.ERROR);
return;
}
@@ -17,7 +17,21 @@ namespace AiQ_GUI
if (CamMod.zoom != 0) // Check camera module is at full wide
errMssg += $"Zoom not at 0 - {CamMod.zoom} ";
if (CamMod.firmwareVer != UniversalData.WonwooFirmware) // Check camera module firmware version is up to date.
bool LessTanOrEqualTo = false;
try
{
LessTanOrEqualTo = Convert.ToDouble(CamMod.firmwareVer) <= Convert.ToDouble(UniversalData.WonwooFirmware);
}
catch
{
MainForm.Instance.AddToActionsList($"{CamMod.firmwareVer} or {UniversalData.WonwooFirmware} could not be converted to a double", Level.ERROR);
}
if (CamOnTest.RMANum > 0 && !LessTanOrEqualTo)
errMssg += $"Firmware: {CamMod.firmwareVer} should be less than or equal to {UniversalData.WonwooFirmware} for RMA {CamOnTest.RMANum}";
else if (CamOnTest.RMANum < 1 && CamMod.firmwareVer != UniversalData.WonwooFirmware)
errMssg += $"Firmware: {CamMod.firmwareVer} should be {UniversalData.WonwooFirmware} ";
if (CamMod.expMode != 0) // Auto 0=0x00
@@ -41,7 +55,7 @@ namespace AiQ_GUI
}
catch (Exception ex)
{
MainForm.Instance.AddToActionsList("Exception in CheckCamModule: " + ex.Message);
MainForm.Instance.AddToActionsList("Exception in CheckCamModule: " + ex.Message, Level.ERROR);
}
}
@@ -51,7 +65,7 @@ namespace AiQ_GUI
{
if (Shutter.SelectedIndex == -1 || Iris.SelectedIndex == -1 || Gain.SelectedIndex == -1)
{
MainForm.Instance.AddToActionsList("Shutter, Iris and Gain need selecting in images tab.");
MainForm.Instance.AddToActionsList("Shutter, Iris and Gain need selecting in images tab.", Level.WARNING);
return;
}
@@ -61,7 +75,7 @@ namespace AiQ_GUI
if (ShutterVISCA.Contains("ERROR") || IrisVISCA.Contains("ERROR") || GainVISCA.Contains("ERROR"))
{
MainForm.Instance.AddToActionsList("Problem with selected SIG values");
MainForm.Instance.AddToActionsList("Problem with selected SIG values", Level.ERROR);
return;
}
@@ -71,7 +85,7 @@ namespace AiQ_GUI
string OneshotReply = await FlexiAPI.APIHTTPVISCA(IPAddress, "8101041801FF", true); // Oneshot auto focus
if (!ShutterReply.Contains("41") || !IrisReply.Contains("41") || !GainReply.Contains("41") || !OneshotReply.Contains("41"))
MainForm.Instance.AddToActionsList("Could not set Shutter, Iris, Gain correctly" + Environment.NewLine + "Shutter: " + ShutterReply + Environment.NewLine + "Iris: " + IrisReply + Environment.NewLine + "Gain: " + GainReply + Environment.NewLine + "Oneshot: " + OneshotReply);
MainForm.Instance.AddToActionsList("Could not set Shutter, Iris, Gain correctly" + Environment.NewLine + "Shutter: " + ShutterReply + Environment.NewLine + "Iris: " + IrisReply + Environment.NewLine + "Gain: " + GainReply + Environment.NewLine + "Oneshot: " + OneshotReply, Level.ERROR);
}
// Sets back to the latest factory defaults CSV that is in Flexi.
@@ -83,7 +97,7 @@ namespace AiQ_GUI
await Task.WhenAll(IRReply, OVReply);
if (IRReply.Result != "Factory reset OK." || OVReply.Result != "Factory reset OK.")
MainForm.Instance.AddToActionsList($"Could not reset camera modules to factory default.{Environment.NewLine}{IRReply}{Environment.NewLine}{OVReply}");
MainForm.Instance.AddToActionsList($"Could not reset camera modules to factory default.{Environment.NewLine}{IRReply}{Environment.NewLine}{OVReply}", Level.ERROR);
}
public static string BuildVISCACommand(string command, int hexValue)
@@ -93,5 +107,33 @@ namespace AiQ_GUI
// Build the final VISCA command string using the input characters split as p and q
return $"8101044{command}00000{hex[0]}0{hex[1]}FF";
}
public static async Task<bool> SetZoomLockOn(string IP)
{
// Set Zoomlock on and if it fails ask user to set it manually
if (!(await FlexiAPI.APIHTTPRequest("/api/zoomLock?enable=true", IP)).Contains("Zoom lock enabled.")
&& !await MainForm.Instance.DisplayQuestion("Could not set zoomlock on" + Environment.NewLine + "Set Zoomlock to on then click YES. Click NO to restart."))
{
return false;
}
return true;
}
public static async Task<bool> ZoomModules(string VISCAInput, string IPAddress)
{
// Populate the VISCA command with the four zoom characters
string VISCA = $"810104470{VISCAInput[0]}0{VISCAInput[1]}0{VISCAInput[2]}0{VISCAInput[3]}FF";
Task<string> TS1 = FlexiAPI.APIHTTPVISCA(IPAddress, VISCA, true);
Task<string> TS2 = FlexiAPI.APIHTTPVISCA(IPAddress, VISCA, false);
await Task.WhenAll(TS1, TS2);
const string ExpReply = "9041FF9051FF";
if (TS1.Result == ExpReply && TS1.Result == ExpReply)
return true;
return false;
}
}
}

View File

@@ -24,7 +24,7 @@ namespace AiQ_GUI
}
catch (Exception ex)
{
return $"Error during GET request: {ex.Message}";
return $"Error during GET request: {ex.Message}{Level.ERROR}";
}
}
@@ -35,12 +35,14 @@ namespace AiQ_GUI
try
{
string JSONdata = BuildJsonUpdate(jsonArrayData, ID);
JSONdata = JSONdata.Replace("\"14\"", "14").Replace("\"30\"", "30"); // Fixes & encoding issue
string url = $"http://{IPAddress}/api/update-config";
return await Network.SendHttpRequest(url, HttpMethod.Post, 2, JSONdata);
}
catch (Exception ex)
{
return $"Error in HTTP_Update: {ex.Message}";
return $"Error in HTTP_Update: {ex.Message}{Level.ERROR}";
}
}
@@ -54,7 +56,7 @@ namespace AiQ_GUI
}
catch (Exception ex)
{
return $"Error in HTTP_Fetch: {ex.Message}";
return $"Error in HTTP_Fetch: {ex.Message}{Level.ERROR}";
}
}
@@ -79,7 +81,7 @@ namespace AiQ_GUI
// Always Infrared as LED's are controlled from infrared page
// Level can be word eg. SAFE or hex eg. 0x0E
string suffix = $"/Infrared/led-controls?power={LEVEL}";
return await APIHTTPRequest(suffix, IPAddress);
return await APIHTTPRequest(suffix, IPAddress, 5);
}
public static async Task<string> SendBlobFileUpload(string url, string filePath, string fileName)
@@ -99,21 +101,22 @@ namespace AiQ_GUI
string responseBody = await response.Content.ReadAsStringAsync();
if (!response.IsSuccessStatusCode)
return $"Server returned {(int)response.StatusCode}: {response.ReasonPhrase}. Details: {responseBody}";
return $"Server returned {(int)response.StatusCode}: {response.ReasonPhrase}. Details: {responseBody}{Level.ERROR}";
return responseBody;
}
catch (TaskCanceledException)
{
return $"Timeout uploading to {url}.";
return $"Timeout uploading to {url}.{Level.ERROR}";
}
catch (HttpRequestException ex)
{
return $"HTTP error uploading to {url}: {ex.Message}";
return $"HTTP error uploading to {url}: {ex.Message}{Level.ERROR}";
}
catch (Exception ex)
{
return $"Unexpected error uploading to {url}: {ex.Message} {(ex.InnerException?.Message ?? "")}";
return $"Unexpected error uploading to {url}: {ex.Message} {(ex.InnerException?.Message ?? string.Empty)} {Level.ERROR}";
}
}
@@ -143,7 +146,7 @@ namespace AiQ_GUI
if (JSON == null || JSON.Contains("Error") || JSON.Contains("Timeout"))
{
MainForm.Instance.AddToActionsList("Error talking to Flexi, are you sure this is an AiQ?" + Environment.NewLine + JSON);
MainForm.Instance.AddToActionsList($"Error talking to Flexi, are you sure this is an AiQ?{Level.WARNING}" + Environment.NewLine + JSON);
return null;
}
@@ -159,18 +162,19 @@ namespace AiQ_GUI
return null; // If it fails to parse the JSON
}
}
public static async Task<bool> SetVaxtorMinMaxPlate(string IP)
{
try
{
// Build JSON array for Vaxtor min/max plate configuration
string[,] Vaxtor_JSON = { { "propMinCharHeight", "18" }, { "propMinGlobalConfidence", "30" } };
string[,] Vaxtor_JSON = { { "propMinCharHeight", "14" }, { "propMaxCharHeight", "40" }, { "propMinGlobalConfidence", "30" } };
string response = await HTTP_Update("RaptorOCR".Trim(), IP, Vaxtor_JSON);
// Treat "operation was canceled" as a successful apply
if (response.Contains("The operation was canceled", StringComparison.OrdinalIgnoreCase))
{
Logging.LogMessage("SetVaxtorMinMaxPlate: Camera applied config but closed connection early (safe to ignore).");
Logging.LogMessage($"SetVaxtorMinMaxPlate: Camera applied config but closed connection early (safe to ignore).");
return true;
}
@@ -184,41 +188,11 @@ namespace AiQ_GUI
}
catch (Exception ex)
{
MainForm.Instance.AddToActionsList($"Could not set Vaxtor Plate height and min confidence: {ex.Message}");
MainForm.Instance.AddToActionsList($"Could not set Vaxtor Plate height and min confidence: {ex.Message}", Level.ERROR);
return false;
}
}
public static async Task<bool> SetZoomLockOn(string IP)
{
// Set Zoomlock on and if it fails ask user to set it manually
if (!(await APIHTTPRequest("/api/zoomLock?enable=true", IP)).Contains("Zoom lock enabled.")
&& !await MainForm.Instance.DisplayQuestion("Could not set zoomlock on" + Environment.NewLine + "Set Zoomlock to on then click YES. Click NO to restart."))
{
return false;
}
return true;
}
public static async Task<bool> ZoomModules(string VISCAInput, string IPAddress)
{
// Populate the VISCA command with the four zoom characters
string VISCA = $"810104470{VISCAInput[0]}0{VISCAInput[1]}0{VISCAInput[2]}0{VISCAInput[3]}FF";
Task<string> TS1 = APIHTTPVISCA(IPAddress, VISCA, true);
Task<string> TS2 = APIHTTPVISCA(IPAddress, VISCA, false);
await Task.WhenAll(TS1, TS2);
const string ExpReply = "9041FF9051FF";
if (TS1.Result == ExpReply && TS1.Result == ExpReply)
return true;
return false;
}
public static async Task SetTrim(string IPAddress, string LblTxt, int RetryCount = 0) // Sets trim by getting plate postion as metric
{
Trim trim;
@@ -231,22 +205,21 @@ namespace AiQ_GUI
}
catch
{
MainForm.Instance.AddToActionsList("Error reading trim JSON - " + trimData);
MainForm.Instance.AddToActionsList($"Error reading trim JSON - {Level.ERROR}" + trimData);
return;
}
// Check no value is -1 (no plate found) or if the positions are identical (one plate found). If it is then try again 3 times
if (new[] { trim.infraredX, trim.infraredY, trim.colourX, trim.colourY }.Any(value => value == -1)
|| (trim.infraredX == trim.colourX && trim.infraredY == trim.colourY))
if (new[] { trim.infraredX, trim.infraredY, trim.colourX, trim.colourY }.Any(value => value == -1) || (trim.infraredX == trim.colourX && trim.infraredY == trim.colourY))
{
if (RetryCount >= 3)
{
await MainForm.Instance.DisplayOK("Please align trim in webpage then click OK."); // Awaited till OK has been clicked
await MainForm.Instance.DisplayOK($"Please align trim in webpage then click OK.{Level.WARNING}"); // Awaited till OK has been clicked
return;
}
await Task.Delay(5000); // Give 5 second delay for it to see a plate
await SetTrim(IPAddress, LblTxt, RetryCount++);
await SetTrim(IPAddress, LblTxt, RetryCount + 1);
}
int offset = 105;
@@ -269,15 +242,15 @@ namespace AiQ_GUI
}
else // Ask user to centre the plate in the field of view
{
await MainForm.Instance.DisplayOK("Please centralise plate in view THEN press OK"); // Awaited till OK has been clicked
await MainForm.Instance.DisplayOK($"Please centralise plate in view THEN press OK"); // Awaited till OK has been clicked
if (RetryCount >= 3)
{
await MainForm.Instance.DisplayOK("Please align trim in webpage then click OK."); // Awaited till OK has been clicked
await MainForm.Instance.DisplayOK($"Please align trim in webpage then click OK. {Level.WARNING}"); // Awaited till OK has been clicked
return;
}
await Task.Delay(5000); // Give 5 second delay for it to see a plate
await SetTrim(IPAddress, LblTxt, RetryCount++);
await SetTrim(IPAddress, LblTxt, RetryCount + 1);
}
}
@@ -290,7 +263,7 @@ namespace AiQ_GUI
string TrimResp = await HTTP_Update("SightingCreator", IPAddress, Trim_JSON);
if (!TrimResp.Contains($"\"propInterCameraOffsetX\": {{\"value\": \"{Convert.ToString(TrimX)}\", \"datatype\": \"int\"}}, \"propInterCameraOffsetY\": {{\"value\": \"{Convert.ToString(TrimY)}\", \"datatype\": \"int\"}},"))
MainForm.Instance.AddToActionsList("Could not set camera trim");
MainForm.Instance.AddToActionsList($"Could not set camera trim{Level.ERROR}");
}
// Processes the network config from the camera and returns a string indicating the status
@@ -308,7 +281,7 @@ namespace AiQ_GUI
}
catch (Exception ex)
{
MainForm.Instance.AddToActionsList($"Error in getting network config from camera: {ex.Message}");
MainForm.Instance.AddToActionsList($"Error in getting network config from camera: {ex.Message}{Level.ERROR}");
return null; // Return empty string if there is an error
}
}
@@ -354,11 +327,143 @@ namespace AiQ_GUI
}
// Change network settings to DHCP and restart camera for it to take effect
public async static Task ChangeNetworkToDHCP(string IPAddress)
public async static Task<bool> ChangeNetworkToDHCP(string IPAddress)
{
string[,] TEST_JSON = { { "propDHCP", "true" } }; // Update GLOBAL--NetworkConfig with fixed IP and turn off DHCP
await HTTP_Update("GLOBAL--NetworkConfig", IPAddress, TEST_JSON);
// TODO - Check if this worked, if not return false
string[,] TEST_JSON = { { "propDHCP", "true" } };
await HTTP_Update("GLOBAL--NetworkConfig", IPAddress, TEST_JSON); // Don't care about response because it will fail as it has changed IP.
await Task.Delay(5000); // Wait for 5 seconds to allow the camera to restart
IList<string> FoundCams = await Network.SearchForCams();
if (FoundCams.Contains("192.168.1.211"))
{
MainForm.Instance.AddToActionsList($"Could not set camera to DHCP please check camera.{Level.ERROR}");
return false;
}
MainForm.Instance.AddToActionsList($"Camera successfully set to DHCP.{Level.Success}");
return true;
}
public static async Task UploadWonwooSet(string ipAddress, bool isIR)
{
string fileToUpload = null;
using OpenFileDialog openFileDialog1 = new()
{
InitialDirectory = GoogleAPI.GoogleDrivePath,
Filter = "CSV files (*.csv)|*.csv",
FilterIndex = 0
};
if (openFileDialog1.ShowDialog() == DialogResult.OK)
fileToUpload = openFileDialog1.FileName;
else
{
MainForm.Instance.AddToActionsList("File selection cancelled.", Level.WARNING);
return;
}
//Filename validation
string filename = Path.GetFileName(fileToUpload).ToUpper();
if ((isIR && !filename.Contains("IR")) || (!isIR && !filename.Contains("OV")))
{
MainForm.Instance.AddToActionsList($"Incorrect file selected. Expected {(isIR ? "IR" : "OV")} file", Level.WARNING);
return;
}
string[] lines = File.ReadAllLines(fileToUpload);
for (int i = 1; i < lines.Length; i++)
{
string[] parts = lines[i].Split(',').Select(p => p.Trim()).ToArray();
if (parts.Length < 3)
{
MainForm.Instance.AddToActionsList($"Invalid row format at line {i + 1}", Level.WARNING);
continue;
}
string name = parts[0];
string command = parts[1];
string expectedResponse = parts[2];
// VISCA format check
if (!RegexCache.VISCAAPIRegex().IsMatch(command))
{
MainForm.Instance.AddToActionsList($"{name}: Invalid VISCA command ({command})", Level.WARNING);
continue; // do not send it if bad
}
string result = await APIHTTPVISCA(ipAddress, command, isIR);
if (result.Contains(expectedResponse))
MainForm.Instance.AddToActionsList($"{name}: Success ({(isIR ? "IR" : "Colour")})", Level.Success);
else
MainForm.Instance.AddToActionsList($"{name}: Unexpected response ({result})", Level.ERROR);
await Task.Delay(150);
}
MainForm.Instance.AddToActionsList($"Upload complete ({(isIR ? "IR" : "Colour")}).", Level.Success);
}
public static async void UploadBlob(List<Camera> soakCameraList)
{
const string networkFolderPath = @"G:\Shared drives\MAV Production\MAV_146_AiQ_Mk2\Flexi";
string fileToUpload = null;
if (await MainForm.Instance.DisplayQuestion("Do you want the latest Flexi version from the MAV Production folder?"))
{
fileToUpload = Directory.GetFiles(networkFolderPath, "*.blob").OrderByDescending(File.GetLastWriteTime).FirstOrDefault();
if (fileToUpload == null)
{
MainForm.Instance.AddToActionsList("No .blob file found in the directory.", Level.WARNING);
return;
}
}
else
{
using OpenFileDialog openFileDialog1 = new()
{
InitialDirectory = networkFolderPath,
Filter = "Blob files (*.blob)|*.blob",
FilterIndex = 0
};
if (openFileDialog1.ShowDialog() == DialogResult.OK)
fileToUpload = openFileDialog1.FileName;
else
{
MainForm.Instance.AddToActionsList("File selection cancelled.", Level.WARNING);
return;
}
}
string fileName = Path.GetFileName(fileToUpload);
MainForm.Instance.AddToActionsList($"Selected file to upload: {fileToUpload}", Level.LOG);
foreach (Camera? cam in soakCameraList.Where(c => c.IsChecked))
{
string apiUrl = $"http://{cam.IP}/upload/software-update/2";
Network.Initialize("developer", cam.DevPass);
await Task.Delay(1000); // Gives extra time to allow for Network to initialize
MainForm.Instance.AddToActionsList($"Uploading to {cam.IP}...", Level.LOG);
string result = await SendBlobFileUpload(apiUrl, fileToUpload, fileName);
// Retry once on transient errors
if (result.Contains("Error while copying content to a stream") || result.Contains("Timeout"))
{
MainForm.Instance.AddToActionsList($"Retrying upload to {cam.IP}...", Level.WARNING);
await Task.Delay(1000);
result = await SendBlobFileUpload(apiUrl, fileToUpload, fileName);
}
MainForm.Instance.AddToActionsList($"Upload result for {cam.IP}: {result}");
await Task.Delay(500);
}
}
}
@@ -368,10 +473,8 @@ namespace AiQ_GUI
public string version { get; set; } = string.Empty;
public string revision { get; set; } = string.Empty;
public string buildtime { get; set; } = string.Empty;
public string appname { get; set; } = string.Empty;
public string MAC { get; set; } = string.Empty;
public int timeStamp { get; set; }
public string UUID { get; set; } = string.Empty;
public string proquint { get; set; } = string.Empty;
[JsonProperty("Serial No.")]
@@ -427,6 +530,12 @@ namespace AiQ_GUI
public int colourY { get; set; }
}
public class SysStatus
{
public int gpsState { get; set; } = 0;
public string gpsPresent { get; set; } = string.Empty;
}
public class NetworkConfig
{
public Property propDHCP { get; set; } = new Property();
@@ -435,13 +544,5 @@ namespace AiQ_GUI
public class Property
{
public string Value { get; set; } = string.Empty;
public string datatype { get; set; }
}
public class VaxtorConfig
{
public string id { get; set; }
public long configHash { get; set; }
public Property propMinCharHeight { get; set; }
public Property propMinGlobalConfidence { get; set; }
}
}

View File

@@ -10,11 +10,11 @@ namespace AiQ_GUI
internal class ImageProcessing
{
// API to get snapshot then downsize and downscale image to save size.
public static async Task<Image?> GetProcessedImage(string suffix, string IPAddress, string DevPass, string? savePath = null, PictureBox? PcBx = null, bool SaveDisplay = false)
public static async Task<Image?> GetProcessedImage(string Module, string IPAddress, string DevPass, string? savePath = null, PictureBox? PcBx = null, bool SaveDisplay = false)
{
try
{
string requestUrl = $"http://{IPAddress}/{suffix}";
string requestUrl = $"http://{IPAddress}/{Module}-snapshot";
HttpClientHandler handler = new()
{
@@ -26,7 +26,7 @@ namespace AiQ_GUI
HttpResponseMessage response = await httpClient.GetAsync(requestUrl);
if (!response.IsSuccessStatusCode)
{
MainForm.Instance.AddToActionsList($"No success from {requestUrl} replied {response.StatusCode}");
MainForm.Instance.AddToActionsList($"No success from {requestUrl} replied {response.StatusCode}{Level.ERROR}");
return null;
}
@@ -34,7 +34,7 @@ namespace AiQ_GUI
if (imageBytes.Length == 0) // Check if the imageBytes is empty
{
MainForm.Instance.AddToActionsList($"No image data received from {requestUrl}");
MainForm.Instance.AddToActionsList($"No image data received from {requestUrl}{Level.ERROR}");
return null;
}
@@ -43,7 +43,7 @@ namespace AiQ_GUI
CvInvoke.Imdecode(imageBytes, ImreadModes.AnyColor, mat);
if (mat.IsEmpty)
{
MainForm.Instance.AddToActionsList("Failed to decode image with Emgu CV.");
MainForm.Instance.AddToActionsList($"Failed to decode image with Emgu CV.{Level.ERROR}");
return null;
}
@@ -70,12 +70,12 @@ namespace AiQ_GUI
}
catch (HttpRequestException ex)
{
MainForm.Instance.AddToActionsList($"HTTP error: {ex.Message}");
MainForm.Instance.AddToActionsList($"HTTP error: {ex.Message}{Level.ERROR}");
return null;
}
catch (Exception ex)
{
MainForm.Instance.AddToActionsList($"Error processing image: {ex.Message}");
MainForm.Instance.AddToActionsList($"Error processing image: {ex.Message} {Level.ERROR}");
return null;
}
}
@@ -85,7 +85,7 @@ namespace AiQ_GUI
public static async Task ImageCheck(PictureBox PicBxOV, PictureBox PicBxF2, PictureBox PicBxF16, Label LblF2, Label LblF16, Camera CamOnTest)
{
// Take OV snapshot
Task<Image?> Colour_Response = GetProcessedImage("Colour-snapshot", CamOnTest.IP, CamOnTest.DevPass, LDS.MAVPath + LDS.OVsavePath, PicBxOV, true);
Task<Image?> Colour_Response = GetProcessedImage("Colour", CamOnTest.IP, CamOnTest.DevPass, LDS.MAVPath + LDS.OVsavePath, PicBxOV, true);
// Change to wide iris F2.0
await FlexiAPI.APIHTTPVISCA(CamOnTest.IP, "8101044B00000100FF", true);
@@ -93,7 +93,7 @@ namespace AiQ_GUI
await Task.Delay(200); // Wait for iris to settle before taking IR image
// Take IR bright light image
Image? F2_Response = await GetProcessedImage("Infrared-snapshot", CamOnTest.IP, CamOnTest.DevPass, LDS.MAVPath + LDS.IROpensavePath, PicBxF2, true);
Image? F2_Response = await GetProcessedImage("Infrared", CamOnTest.IP, CamOnTest.DevPass, LDS.MAVPath + LDS.IROpensavePath, PicBxF2, true);
if (F2_Response == null)
{
MainForm.Instance.AddToActionsList("IR F2.0 image response is blank.");
@@ -106,7 +106,7 @@ namespace AiQ_GUI
await Task.Delay(200); // Wait for iris to settle before taking IR image
// Take IR low light image
Image? F16_Response = await GetProcessedImage("Infrared-snapshot", CamOnTest.IP, CamOnTest.DevPass, LDS.MAVPath + LDS.IRTightsavePath, PicBxF16, true);
Image? F16_Response = await GetProcessedImage("Infrared", CamOnTest.IP, CamOnTest.DevPass, LDS.MAVPath + LDS.IRTightsavePath, PicBxF16, true);
if (F16_Response == null)
{
MainForm.Instance.AddToActionsList("IR F16.0 image response is blank.");
@@ -123,7 +123,7 @@ namespace AiQ_GUI
}
catch (Exception ex)
{
MainForm.Instance.AddToActionsList($"Error awaiting Colour snapshot: {ex.Message}");
MainForm.Instance.AddToActionsList($"Error awaiting Colour snapshot: {ex.Message} {Level.ERROR}");
return;
}
@@ -146,7 +146,7 @@ namespace AiQ_GUI
}
catch (Exception ex)
{
MainForm.Instance.AddToActionsList($"Error calculating luminance: {ex.Message}");
MainForm.Instance.AddToActionsList($"Error calculating luminance: {ex.Message} {Level.ERROR}");
return;
}
}

View File

@@ -45,7 +45,7 @@
}
catch (Exception ex)
{
MainForm.Instance.AddToActionsList($"Error checking LEDs: {ex.Message}");
MainForm.Instance.AddToActionsList($"Error checking LEDs: {ex.Message} {Level.ERROR}");
}
}
}

View File

@@ -11,27 +11,29 @@ namespace AiQ_GUI
const string SAFsalt = "F7W?wbD#'[+:v44]tA<:_iK4hQ}+$R{U";
const string Streamsalt = "*;5WPsR5i/$8s1I(M)K5=z3fms{_8x4U";
const string Auditsalt = "4t5e[E06:dXWf:C09Z[h)}V*n>}t0POP";
const string XMLsalt = "ap(r]oN?t V@WvI=IX=[+$n(<5J,&Ygd";
const string PasswordSalt = "eP@4^4T2@e@^h12oqf!590";
// Generates the license response based on the challenge and type of license
public static string GenerateLicCode(string challenge, string Type)
{
string salt; // Different salts for differnet licenses
string? salt = Type switch
{
"Store & Forward" => SAFsalt,
"Streaming" => Streamsalt,
"Audit" => Auditsalt,
"XML Server" => XMLsalt,
_ => null
};
if (Type == "Store & Forward")
salt = SAFsalt;
else if (Type == "Streaming")
salt = Streamsalt;
else if (Type == "Audit")
salt = Auditsalt;
else
return "Unrecognised challenge type: " + Type;
if (salt == null)
return $"Unrecognised challenge type:{Level.ERROR}" + Type;
if (string.IsNullOrEmpty(challenge) || challenge.Length != 6) // Check challenge format
return "Invalid challenge format. Challenge must be 6 characters.";
return $"Invalid challenge format. Challenge must be 6 characters.{Level.ERROR}";
if (string.IsNullOrEmpty(salt) || salt.Length != 32) // Check salt format
return "Invalid salt format. Salt must be 32 characters.";
return $"Invalid salt format. Salt must be 32 characters.{Level.ERROR}";
// Hash computation using SHA256 algorithm
byte[] inputBytes = Encoding.UTF8.GetBytes(challenge + " " + salt); // SHA hash format challenge and salt with space between
@@ -60,7 +62,7 @@ namespace AiQ_GUI
}
catch (Exception ex)
{
return "Error: Could not generate password " + ex.Message;
return $"Error: Could not generate password{Level.ERROR}" + ex.Message;
}
}
@@ -85,7 +87,7 @@ namespace AiQ_GUI
}
catch (Exception ex)
{
MainForm.Instance.AddToActionsList("Exception in FetchDevPassword: " + ex.Message);
MainForm.Instance.AddToActionsList($"Exception in FetchDevPassword: {Level.ERROR}" + ex.Message);
return null;
}
}

View File

@@ -32,7 +32,7 @@ namespace AiQ_GUI
}
catch
{
MainForm.Instance.AddToActionsList("Is the router on the network? Has the MAV Config file been applied?");
MainForm.Instance.AddToActionsList($"Is the router on the network? Has the MAV Config file been applied?{Level.WARNING}");
}
return null;
@@ -48,31 +48,31 @@ namespace AiQ_GUI
if (Strength < 25.0)
{
MainForm.Instance.AddToActionsList($"Router signal strength is {Strength} which is below 25%. Please check the router connection.");
MainForm.Instance.AddToActionsList($"Router signal strength is {Strength} which is below 25%. Please check the router connection.{Level.WARNING}");
PassTest = false;
}
if (!Router.SimStatus.Contains("SIM Ready"))
{
MainForm.Instance.AddToActionsList($"SIM card is not ready. {Router.SimStatus} Please check the SIM card status.");
MainForm.Instance.AddToActionsList($"SIM card is not ready. {Router.SimStatus} Please check the SIM card status.{Level.WARNING}");
PassTest = false;
}
if (!Router.Port3Status.Contains("port:3 link:up speed:100baseT full-duplex"))
{
MainForm.Instance.AddToActionsList($"Port 3 is not connected properly. {Router.Port3Status} Please check the connection.");
MainForm.Instance.AddToActionsList($"Port 3 is not connected properly. {Router.Port3Status} Please check the connection. {Level.WARNING}");
PassTest = false;
}
if (!Router.Port4Status.Contains("port:4 link:up speed:100baseT full-duplex"))
{
MainForm.Instance.AddToActionsList($"Port 4 is not connected properly. {Router.Port4Status} Please check the connection.");
MainForm.Instance.AddToActionsList($"Port 4 is not connected properly. {Router.Port4Status} Please check the connection. {Level.WARNING}");
PassTest = false;
}
if (!Router.GoodPing)
{
MainForm.Instance.AddToActionsList("Router could not ping 8.8.8.8. Please check the online connection.");
MainForm.Instance.AddToActionsList($"Router could not ping 8.8.8.8. Please check the online connection.{Level.WARNING}");
PassTest = false;
}

View File

@@ -1,4 +1,5 @@
using Renci.SshNet;
using Renci.SshNet.Common;
namespace AiQ_GUI
{
@@ -6,6 +7,29 @@ namespace AiQ_GUI
{
public const string SSHUsername = "mav";
public const string SSHPassword = "mavPA$$";
public const string SSHPasswordNEW = "#!mavsoftMESA19"; // New password for SSH after last one got leaked
private static SshClient SshConnect(string IPAddress)
{
SshClient client = new(IPAddress, SSHUsername, SSHPasswordNEW);
try
{
client.Connect();
return client;
}
catch (SshAuthenticationException)
{
client = new(IPAddress, SSHUsername, SSHPassword);
client.Connect();
return client;
}
catch (Exception Ex)
{
MainForm.Instance.AddToActionsList($"SSH connection failed: {Ex.Message}. Check password or network. {Level.WARNING}");
}
return null;
}
// Connects to camera over SSH and collects the Vaxtor packages, filesystem name, filesystem size, and tailscale status.
public static SSHData CollectSSHData(string IPAddress)
@@ -14,8 +38,7 @@ namespace AiQ_GUI
try
{
using SshClient client = new(IPAddress, SSHUsername, SSHPassword);
client.Connect();
SshClient client = SshConnect(IPAddress);
try
{
@@ -34,8 +57,7 @@ namespace AiQ_GUI
catch (Exception ex)
{
MainForm.Instance.AddToActionsList($"Failed to get filesystem info: {ex.Message}");
Data.FilesystemName = "Unknown";
Data.FilesystemSize = "Unknown";
Data.FilesystemName = Data.FilesystemSize = "Unknown";
}
try
@@ -49,10 +71,11 @@ namespace AiQ_GUI
}
client.Disconnect();
client.Dispose();
}
catch (Exception ex)
{
MainForm.Instance.AddToActionsList($"SSH connection failed: {ex.Message}. Check password or network.");
MainForm.Instance.AddToActionsList($"SSH connection failed: {ex.Message}. Check password or network. {Level.WARNING}");
}
string LogMssg = string.Join(" | ", typeof(SSHData).GetProperties().Select(p => $"{p.Name}: {p.GetValue(Data)}"));
@@ -131,8 +154,7 @@ namespace AiQ_GUI
{
try
{
using SshClient client = new SshClient(IPAddress, SSHUsername, SSHPassword);
client.Connect();
SshClient client = SshConnect(IPAddress);
try
{
@@ -144,10 +166,11 @@ namespace AiQ_GUI
}
client.Disconnect();
client.Dispose();
}
catch (Exception ex)
{
MainForm.Instance.AddToActionsList($"SSH connection failed: {ex.Message}. Check password or network.");
MainForm.Instance.AddToActionsList($"SSH connection failed: {ex.Message}. Check password or network. {Level.WARNING}");
}
return (string.Empty, string.Empty);
@@ -191,10 +214,10 @@ namespace AiQ_GUI
{
try
{
using SshClient client = new(IPAddress, SSHUsername, SSHPassword);
client.Connect();
SshClient client = SshConnect(IPAddress);
(string FilesystemName, string FilesystemSize) = GetRootFilesystemInfo(client);
client.Disconnect();
client.Dispose();
return (FilesystemName, FilesystemSize);
}
catch (Exception ex)
@@ -207,19 +230,19 @@ namespace AiQ_GUI
// Checks the filesystem size and expands it if necessary, displays on the label how big the SD card is.
public static async Task<SSHData> CheckFSSize(string IPAddress, Label LblFSSize, SSHData sshData)
{
const double MinGoodSize = 100.0; // 100GB
const double MaxGoodSize = 150.0; // 150GB
const double GoodSize = 128.0; // 128GB
const double Deviation = 20.0; // ±20GB
double currentSize = NormaliseFSSize(sshData.FilesystemSize);
LblFSSize.Text = $"Filesystem Size = {currentSize}GB";
if (currentSize >= MinGoodSize && currentSize <= MaxGoodSize)
if (Math.Abs(GoodSize - currentSize) < Deviation)
{
LblFSSize.ForeColor = Color.LightGreen;
return sshData;
}
if (currentSize < MinGoodSize)
if (currentSize < GoodSize - Deviation)
{
try
{
@@ -230,7 +253,7 @@ namespace AiQ_GUI
double newSize = NormaliseFSSize(sshData.FilesystemSize);
LblFSSize.Text = $"Filesystem Size = {newSize}GB";
if (newSize >= MinGoodSize && newSize <= MaxGoodSize)
if (Math.Abs(GoodSize - newSize) < Deviation)
{
LblFSSize.ForeColor = Color.LightGreen;
return sshData;
@@ -261,10 +284,9 @@ namespace AiQ_GUI
// Extract value & unit
System.Text.RegularExpressions.Match match = RegexCache.FileSizeRegex().Match(rootSize.Trim());
if (!match.Success)
return 0;
if (!double.TryParse(match.Groups["value"].Value, out double value))
// Return 0 if no match or invalid number
if (!match.Success || !double.TryParse(match.Groups["value"].Value, out double value))
return 0;
string unit = match.Groups["unit"].Value.ToUpperInvariant();
@@ -288,25 +310,24 @@ namespace AiQ_GUI
return 0;
}
// Expands the filesystem to max
// Expands the filesystem to max
public async static Task<bool> ExpandFS(string device, string IPAddress)
{
try
{
using SshClient ssh = new SshClient(IPAddress, SSHUsername, SSHPassword);
ssh.Connect();
SshClient client = SshConnect(IPAddress);
SshCommand checkDevice = ssh.RunCommand($"[ -b {device} ] && echo OK || echo NOT_FOUND");
SshCommand checkDevice = client.RunCommand($"[ -b {device} ] && echo OK || echo NOT_FOUND");
if (!string.IsNullOrWhiteSpace(checkDevice.Error))
throw new Exception(checkDevice.Error);
if (checkDevice.Result.Trim() != "OK") // Device not found
{
MainForm.Instance.AddToActionsList($"Block device {device} not found.");
MainForm.Instance.AddToActionsList($"Block device {device} not found.", Level.WARNING);
return false;
}
SshCommand umountCmd = ssh.RunCommand($"sudo umount {device}");
SshCommand umountCmd = client.RunCommand($"sudo umount {device}");
if (!string.IsNullOrWhiteSpace(umountCmd.Error) && !umountCmd.Error.Contains("not mounted"))
{
MainForm.Instance.AddToActionsList($"Unmount error: {umountCmd.Error}");
@@ -315,15 +336,16 @@ namespace AiQ_GUI
await Task.Delay(1000); // Wait for mount to settle
SshCommand fsckCmd = ssh.RunCommand($"sudo e2fsck -f -y -v -C 0 {device}");
SshCommand fsckCmd = client.RunCommand($"sudo e2fsck -f -y -v -C 0 {device}");
if (!string.IsNullOrWhiteSpace(fsckCmd.Error))
{
MainForm.Instance.AddToActionsList($"e2fsck error: {fsckCmd.Error}");
return false;
}
SshCommand resizeFs = ssh.RunCommand($"sudo resize2fs {device}");
ssh.Disconnect();
SshCommand resizeFs = client.RunCommand($"sudo resize2fs {device}");
client.Disconnect();
client.Dispose();
if (!string.IsNullOrWhiteSpace(resizeFs.Error))
{
@@ -348,22 +370,50 @@ namespace AiQ_GUI
{
try
{
using SshClient ssh = new SshClient(IPAddress, SSHUsername, SSHPassword);
ssh.Connect();
SshClient client = SshConnect(IPAddress);
SshCommand checkDevice = ssh.RunCommand("sync");
SshCommand checkDevice = client.RunCommand("sync");
if (!string.IsNullOrWhiteSpace(checkDevice.Error))
throw new Exception(checkDevice.Error);
if (checkDevice.Result.Trim().Length > 1) // Device not found
{
MainForm.Instance.AddToActionsList($"Cannot sync files to disk. Replied: {checkDevice.Result}. DO NOT TURN OFF, GET SUPERVISOR");
MainForm.Instance.AddToActionsList($"Cannot sync files to disk. Replied: {checkDevice.Result}. DO NOT TURN OFF, GET SUPERVISOR", Level.ERROR);
return;
}
client.Disconnect();
client.Dispose();
}
catch (Exception ex)
{
MainForm.Instance.AddToActionsList($"Cannot sync becuase: {ex.Message}. DO NOT TURN OFF, GET SUPERVISOR");
MainForm.Instance.AddToActionsList($"Cannot sync becuase: {ex.Message}. DO NOT TURN OFF, GET SUPERVISOR", Level.ERROR);
}
}
public static void SetTo115200(string IPAddress)
{
try
{
string Cmd = "sudo sh -c \"stty -F /dev/ttyInfrared 9600 cs8 -cstopb -parenb -ixon -ixoff -crtscts -icanon -echo -opost; echo '81 01 04 01 03 FF' | xxd -r -p > /dev/ttyInfrared\"";
SshClient client = SshConnect(IPAddress);
SshCommand checkDevice = client.RunCommand(Cmd);
if (!string.IsNullOrWhiteSpace(checkDevice.Error))
throw new Exception(checkDevice.Error);
Cmd = Cmd.Replace("Infrared", "Colour");
checkDevice = client.RunCommand(Cmd);
if (!string.IsNullOrWhiteSpace(checkDevice.Error))
throw new Exception(checkDevice.Error);
client.Disconnect();
client.Dispose();
}
catch (Exception ex)
{
MainForm.Instance.AddToActionsList($"Cannot set cameras to 115200 becuase: {ex.Message}", Level.ERROR);
}
}
}
@@ -375,4 +425,4 @@ namespace AiQ_GUI
public string FilesystemSize { get; set; } = string.Empty;
public bool tailscale { get; set; }
}
}
}

View File

@@ -1,7 +1,7 @@
{
"cameraName": "AiQ-ANPR-Camera",
"version": "1.6.4",
"revision": "bf16134",
"version": "1.7.1",
"revision": "4b63df5",
"serialNumber": "K1005001",
"modelNumber": "AB12CD",
"MAC": "3C:6D:66:0A:BA:18",

View File

@@ -293,7 +293,7 @@ namespace AiQ_GUI
{
await HTTPReplySetup(context, "Update successful.", 200, "text/plain");
}
else if (fo.Id == "Internal Config")
else if (fo.Id == "GLOBAL--FlexiApplication")
{
string SerialNumber = "";
string ModelNumber = "";

View File

@@ -1,6 +1,6 @@
{
"version": "1.6.4",
"revision": "77e042f",
"version": "1.7.1",
"revision": "4b63df5",
"buildtime": "2025-07-24T10:35:36.445241784Z",
"appname": "FlexiAI",
"MAC": "3C:6D:66:0A:BA:18",

View File

@@ -15,9 +15,7 @@ namespace AiQ_GUI
// If there is no dot in the version string, return the version as is
if (dotLocation < 0)
{
return GUIVersion;
}
// Check if the next character after the dot is "0"
if (dotLocation + 1 < GUIVersion.Length && GUIVersion[dotLocation + 1] == '0')
@@ -48,7 +46,7 @@ namespace AiQ_GUI
{
if (ComapreVersions()) // Checks if the current version is older than the latest version
{
string GUIPath = $"{GoogleAPI.DrivePath}AiQ\\GUI's\\AiQ_Final_Test\\AiQ_GUI.application";
const string GUIPath = $"{GoogleAPI.DrivePath}AiQ\\GUI's\\AiQ_Final_Test\\setup.exe";
// Check if path is real
if (!File.Exists(GUIPath))

View File

@@ -1,12 +1,12 @@
using Google.Apis.Auth.OAuth2;
using Google.Apis.Gmail.v1;
//using Google.Apis.Gmail.v1;
using Google.Apis.Services;
using Google.Apis.Sheets.v4;
using Google.Apis.Sheets.v4.Data;
using Google.Apis.Util.Store;
using System.Net.Mail;
using System.Net.Mime;
using System.Reflection;
//using System.Net.Mail;
//using System.Net.Mime;
//using System.Reflection;
namespace AiQ_GUI
{
@@ -18,13 +18,14 @@ namespace AiQ_GUI
static readonly string credPath = Environment.GetFolderPath(Environment.SpecialFolder.Personal);
public const string spreadsheetId_ModelInfo = "1bCcCr4OYqfjmydt6UqtmN4FQETezXmZRSStJdCCcqZM";
public const string DrivePath = @"G:\Shared drives\MAV Production GUI's\"; // Path to google shared drive
public const string GoogleDrivePath = @"G:\Shared drives\MAV Production GUI's\AiQ\GUI's\";
// Startup and make necessary connections to Google servers and make sure user is logged in
public static bool Setup()
{
try
{
string streamPath = $"{DrivePath}R50IQ\\client_secret.json";
const string streamPath = $"{DrivePath}R50IQ\\client_secret.json";
string[] Scopes = [SheetsService.Scope.Spreadsheets];
FileStream stream = new(streamPath, FileMode.Open, FileAccess.Read);
credential = GoogleWebAuthorizationBroker.AuthorizeAsync(GoogleClientSecrets.FromStream(stream).Secrets, Scopes, "user", CancellationToken.None, new FileDataStore(credPath, true)).Result;
@@ -106,12 +107,12 @@ namespace AiQ_GUI
}
else
{
return "Last serial number not found";
return $"Last serial number not found{Level.ERROR}";
}
}
catch (Exception ex)
{
return $"ERROR: {ex.Message}";
return $"ERROR: {ex.Message}{Level.ERROR}";
}
}
@@ -147,12 +148,12 @@ namespace AiQ_GUI
}
else
{
return "Serial number not found";
return $"Serial number not found{Level.ERROR}";
}
}
catch (Exception ex)
{
return $"ERROR: {ex.Message}";
return $"ERROR: {ex.Message}{Level.ERROR}";
}
}
@@ -195,7 +196,7 @@ namespace AiQ_GUI
}
catch (Exception ex)
{
return "Failed to update spreadsheet data, please check manually" + ex.Message;
return $"Failed to update spreadsheet data, please check manually{Level.ERROR}" + ex.Message;
}
}
@@ -224,14 +225,14 @@ namespace AiQ_GUI
}
catch (Exception ex)
{
return "Failed to update spreadsheet data, please check manually" + ex.Message;
return $"Failed to update spreadsheet data, please check manually{Level.WARNING}" + ex.Message;
}
}
// Checks RMA control sheet for a model and serial that match current camera
public static int CheckRMANum(string serial, string model)
{
string spreadsheetId_RMAControl = "1tZhkYrqBQ3BcL7ZS4q3ghzCgHSJ8f5LVSj7nh6fIRC8";
const string spreadsheetId_RMAControl = "1tZhkYrqBQ3BcL7ZS4q3ghzCgHSJ8f5LVSj7nh6fIRC8";
try
{
// Get all info in H and I columns
@@ -284,75 +285,76 @@ namespace AiQ_GUI
return valuesRMA.Count + 2; // Gets the amount of rows, offsets for start from 1 error and adds one to be next row
}
public static void EmailApproval(string ApprovalRow, string User)
{
FileStream GmailStream = new($"{DrivePath}R50IQ\\creds.json", FileMode.Open, FileAccess.Read);
string[] ScopesGmail = [GmailService.Scope.GmailSend];
// DEPRECATED
//public static void EmailApproval(string ApprovalRow, string User)
//{
// FileStream GmailStream = new($"{DrivePath}R50IQ\\creds.json", FileMode.Open, FileAccess.Read);
// string[] ScopesGmail = [GmailService.Scope.GmailSend];
using (GmailStream)
{
string credPathGmail = Path.Combine(credPath, ".credentials/gmail-dotnet-quickstart.json");
credential = GoogleWebAuthorizationBroker.AuthorizeAsync(GoogleClientSecrets.FromStream(GmailStream).Secrets, ScopesGmail, "user", CancellationToken.None, new FileDataStore(credPathGmail, true)).Result;
GmailStream.Close();
}
// using (GmailStream)
// {
// string credPathGmail = Path.Combine(credPath, ".credentials/gmail-dotnet-quickstart.json");
// credential = GoogleWebAuthorizationBroker.AuthorizeAsync(GoogleClientSecrets.FromStream(GmailStream).Secrets, ScopesGmail, "user", CancellationToken.None, new FileDataStore(credPathGmail, true)).Result;
// GmailStream.Close();
// }
// Build the MIME message with attachment
using MailMessage mail = new MailMessage();
mail.From = new MailAddress("me");
mail.To.Add("richard.porter@mav-systems.com");
mail.To.Add("bradley.relyea@mav-systems.com");
mail.To.Add("bradley.born@mav-systems.com");
mail.Subject = "Approval required";
mail.Body = $"Dear Rich,<br><br>Camera needs approval<br>" +
$"https://docs.google.com/spreadsheets/d/1bCcCr4OYqfjmydt6UqtmN4FQETezXmZRSStJdCCcqZM/edit#gid=1931079354&range=A{ApprovalRow}" +
$"<br><br><br>Thanks,<br><br>{User}";
mail.IsBodyHtml = true;
// // Build the MIME message with attachment
// using MailMessage mail = new();
// mail.From = new MailAddress("me");
// mail.To.Add("richard.porter@mav-systems.com");
// mail.To.Add("bradley.relyea@mav-systems.com");
// mail.To.Add("bradley.born@mav-systems.com");
// mail.Subject = "Approval required";
// mail.Body = $"Dear Rich,<br><br>Camera needs approval<br>" +
// $"https://docs.google.com/spreadsheets/d/1bCcCr4OYqfjmydt6UqtmN4FQETezXmZRSStJdCCcqZM/edit#gid=1931079354&range=A{ApprovalRow}" +
// $"<br><br><br>Thanks,<br><br>{User}";
// mail.IsBodyHtml = true;
// Attach the log file if it exists
string logFilePath = LDS.MAVPath + Logging.LogFileName;
if (File.Exists(logFilePath))
{
Attachment logAttachment = new(logFilePath, MediaTypeNames.Text.Plain);
logAttachment.Name = Logging.LogFileName;
mail.Attachments.Add(logAttachment);
}
// // Attach the log file if it exists
// string logFilePath = LDS.MAVPath + Logging.LogFileName;
// if (File.Exists(logFilePath))
// {
// Attachment logAttachment = new(logFilePath, MediaTypeNames.Text.Plain);
// logAttachment.Name = Logging.LogFileName;
// mail.Attachments.Add(logAttachment);
// }
// Save the MIME message to a stream
using MemoryStream ms = new();
SmtpClient smtpClient = new(); // Only used to access the internal Write method
Type mailWriterType = typeof(SmtpClient).Assembly.GetType("System.Net.Mail.MailWriter");
object? mailWriter = Activator.CreateInstance(
mailWriterType,
BindingFlags.Instance | BindingFlags.NonPublic,
null,
[ms, true],
null);
// // Save the MIME message to a stream
// using MemoryStream ms = new();
// SmtpClient smtpClient = new(); // Only used to access the internal Write method
// Type mailWriterType = typeof(SmtpClient).Assembly.GetType("System.Net.Mail.MailWriter");
// object? mailWriter = Activator.CreateInstance(
// mailWriterType,
// BindingFlags.Instance | BindingFlags.NonPublic,
// null,
// [ms, true],
// null);
typeof(MailMessage).InvokeMember(
"Send",
BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.InvokeMethod,
null,
mail,
[mailWriter, true, true]);
// typeof(MailMessage).InvokeMember(
// "Send",
// BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.InvokeMethod,
// null,
// mail,
// [mailWriter, true, true]);
ms.Position = 0;
byte[] rawBytes = ms.ToArray();
// ms.Position = 0;
// byte[] rawBytes = ms.ToArray();
GmailService service = new(new BaseClientService.Initializer()
{
HttpClientInitializer = credential,
ApplicationName = ApplicationName,
});
// GmailService service = new(new BaseClientService.Initializer()
// {
// HttpClientInitializer = credential,
// ApplicationName = ApplicationName,
// });
Google.Apis.Gmail.v1.Data.Message newMsg = new()
{
Raw = Convert.ToBase64String(rawBytes)
.Replace("+", "-")
.Replace("/", "_")
.Replace("=", "")
};
// Google.Apis.Gmail.v1.Data.Message newMsg = new()
// {
// Raw = Convert.ToBase64String(rawBytes)
// .Replace("+", "-")
// .Replace("/", "_")
// .Replace("=", "")
// };
service.Users.Messages.Send(newMsg, "me").Execute();
}
// service.Users.Messages.Send(newMsg, "me").Execute();
//}
}
}

View File

@@ -54,21 +54,36 @@ namespace AiQ_GUI
public static void PrintGBLbl()
{
// New Label with UKCA logo
const string MadeInGB = "^XA~TA000~JSN^LT0^MNW^MTT^PON^PMN^LH0,0^JMA^PR2,2~SD15^JUS^LRN^CI0^XZ\n" +
"^XA\n" +
"^MMT\n" +
"^PW384\n" +
"^LL0096\n" +
"^LS0\n" +
"^FO192,0^GFA,01152,01152,00012,:Z64:\n" +
"eJzt0DEKgDAMBdCUDo49Qm9ij2aP1qN4BEeHYvziL0TURRcHA4VXaEryRf76agWciVZcaQ8nuoMHOsBKxypO2ZqK+Jke0NK+XNAy0hUuOx3exkzjbZSzPdw3j0cnzY999efdDHZOO/9hL7tvyuIrHSfEyGbk5vQiT5uzzX+bwDX/9a5WFTRBPA==:FF83\n" +
"^FO320,0^GFA,00768,00768,00008,:Z64:\n" +
"eJxjYBjOgAWIHYCYA4gNGOwYBEBs9v8HFEBycnY2IDkmOwabBiDNaP//L1jTBAYxMB3AIAOmDaC0AIMNmJawLwDTMvUTwLQQswCYlmRSANMcLVA6A0obQOkKVHlOqHouRgjNw/AAIs+QALEHSuswfADTLow/QBRjC2MNiGZi//8L7Ec2xo8MUD82gJ3JANaoAHY65UD+////D0ighxYAAPB/L1Q=:B1A7\n" +
"^FO0,0^GFA,02688,02688,00028,:Z64:\n" +
"eJzt071OwzAQB/BEHjL6ETzwAowMSH4Y3gMXdejII/RRcJWBsY9QVx0YsdTFEsbH3fmjSaGMDKhXZ/oldc73T9dd61r/sLTXcACnu8ENXsBCAHRGLhRbyLaQTqKFbDZbUmxWsaVsLhtINvwpemjJ5osNv9jI5o1TEW1kC8X2bAGvKLweQ4cWi3m2z2zqwJaqGTLAKwkvs0GxqIuZRzJIZGsyBUmRGXCPRvhhahJAsr27NLFdsRWZRtPNdkBGLZGprYt6WffbsvUAezL5vMdDqe/5ynYL4HlGEm2s/Y1sdwbCw8nMODVs8IbtAHhA5Tw3bPeKZnayPAfLphVAtjd6SShzN2wrujfbcKxzt5ptSXuwObQwsz1t4y5kEOB40Qx8XDLMJ1wyzCfNPOJd1FASoUs115hPMrC0qtmWTzZeaENEcy2fbJ5WNd/yObc0Md8skEmy0PJJtvRaBN3h2ZHlXDPPDNBSNTUz9YIG1WTZr9oitcxD7W9g02zrmXEPZIpsN7PQzKaS66k5Mhx6Krmu9hTprMnApZLrn208N8X/id/AueHnK32zzfm7DGQ9m/1m1HsfRUg116fe+cz6wKZnZvERNBH6WO1a1/qD+gLCtYx7:603A\n" +
"^FO256,0^GFA,01152,01152,00012,:Z64:\n" +
"eJzt0DEKwzAMBVAJDxlzBB/F18pQcI7mo/gIGlVwqzpBrkSbDoGO+V4egXy+DXDlv8EKtMCyO1QkRjZPZJ6LOa7mDOZmfrBZyLk4rz++1+9/kZDuzcxPtQhx0suIVI7FPGtRlsJBi6LAuOPpRKFpDM1CcYzrG5Lw21mNu5tuBrj1M9zUoU/kkxbt9N46D83O21tpD3hn5+R85SMvvQiYIA==:00E3\n" +
"^PQ1,0,1,Y^XZ";
const string MadeInGB = "^CT~~CD,~CC^~CT~\n" +
"^XA\n" +
"~TA000\n" +
"~JSN\n" +
"^LT0\n" +
"^MNW\n" +
"^MTT\n" +
"^PON\n" +
"^PMN\n" +
"^LH0,0\n" +
"^JMA\n" +
"^PR2,2\n" +
"~SD15\n" +
"^JUS\n" +
"^LRN\n" +
"^CI27\n" +
"^PA0,1,1,0\n" +
"^XZ\n" +
"^XA\n" +
"^MMT\n" +
"^PW384\n" +
"^LL96\n" +
"^LS0\n" +
"^FO212,34^GFA,225,280,8,:Z64:eJxVkDEKwkAQRZ+GRbs0oo2CtYcwV0hno+AR0oiVkKOk9jTbWAa2TLllKte/uJi1WIbHnz/zZ5l3cABMAyfVaqQILVwsphcHTxlgdoNNDcWoFsnGwlly6eCtutBrIktTCztZvOpRFrWwFl8TD0vYil/1l4MMK/G9mTjX3f7f79J8m+377U95Yr4HKW835ScMuie7r/JaGAek++N/PFs+dGoy4g==:0511\n" +
"^FO336,26^GFA,217,384,8,:Z64:eJxrYEAAFiBWAGIOME8ACIFsvncdIDEWFhERByDNZLdqXwOQZhRkFAQrW8DABaYDGETAtAGDFVQ7hM+htQNMi9QLgGkuZQUwzQi1lKMDwmdwgPINoHwBVHmYei4mBbhjwfIMD6DKIQZoMHyASDBCDOjhfgWmWUMDQBQTE8MGJC+D/boAia/AQBng////P8hB8kD6ABKfHwcfACScKCU=:8B00\n" +
"^FO11,27^GFA,817,1125,25,:Z64:eJxtkkFu00AYhf/pgBwJKcOCbjM3KDewc4QusFhyBZYsAp7UCyIhkSuwZsEZJgKJJSdAmaoLsmqmQhUjYfnn/WMnrVRsRbYz8+nNe/+jlVxuQhP5rWi1ognh9ZNiubwlQ5ZsRw2TLbwh0nkhVC7f7Jip0sGc0JGoqCHLXggd7R0RsBlE4N7ZNpnVUcM31HhbxAVBo7M0PxCR3d9Q6pQIGr1xR+KG6befqUzo/p4GZ4JS/5lsy+uLyUjMmh/qJswoNR00tnc+ymrdXkUQ3ENjvyZSdV0LYY26jDN6I4T+viUi59qRCGkgbHvFDitUCPHIFLdpplL2sWH5n4wQZN5zOvi4T5ApuJvpwYdnOVWqBmLNfcW77APBId1YDYT5thjTLRAciMAj4crjPCr4kE2ZwAAt5wlqn7NqhTij6Ud39mXnrq/d9CJMRQOnYU8PLmTPicPDBdUVHP9LwMaOw2T8nOQ7E7Dh2Z++oPpcndf1/BmdqvljELDhZIKOexVllrlXIMQr+iF2VGzwOPSqRZOEALlMIDj3Cj5gg9CSl/7tq02sb+dYKBKIE9ggZKVDMsuoA6GP0iuZRnwKQvtFsYkqqj94RxMxL97BefkkdGaTdFB7b9tMwMbrrDESv/zQK8JhYvbRPxcNfRWGXlE+fvbRYH/UmzD0SnO/TkcfQkTb7vcyj65AuhZYJwSEbPF1bEkUH1GyQkr4zr1CS+xALMpM4FtvMrGTCZYfUldBYwfi0CvfCLGNCyuE+dkderXk7GPMyrp3h15BJfsYnFtajL3q15x9uM5cwkfpSmggq38uZhFX:58F1\n" +
"^FO278,28^GFA,273,360,8,:Z64:eJx1j7ENwjAQRX8UoRQUKel8LcoMyG6yB2ukCCSlR0EMgSgYgBEyAUrpwspxZwcJIWFLPj3/++fvC/YNGqBHb+RAgLVyoMPBFHPmeso63TM7ZF3alV1I3NO88pR1mr508dPaLw+s86Qu15jmDxxMJdVxsKUYdjynPFseLW74v7yHH4ENx2J4AjUvJYufOFYcdd4iV8CpbelxBM5yJVvygWTrVy1qzQOj9cOdctYdc9JJ6y8v3rsXa/6Y/PJe8pMypTyJ30wJYOg=:5E2C\n" +
"^PQ1,0,1,Y\n" +
"^XZ";
if (!PrintToZebra(MadeInGB))
MainForm.Instance.AddToActionsList("Error printing GB label");

View File

@@ -77,32 +77,32 @@ namespace AiQ_GUI
{
foreach (int ShuffleOrder in Shuffle())
{
switch (ShuffleOrder)
{
case 0:
case 0:
if (!await MainForm.Instance.DisplayQuestion("Is the sleeve aligned correctly?"))
{
await MainForm.Instance.TestFailed(Btn, "Visual Test Fail - Sleeve not aligned");
await MainForm.Instance.TestFailed(Btn, $"Visual Test Fail - Sleeve not aligned{Level.ERROR}");
}
}
break;
case 1:
if (!await MainForm.Instance.DisplayQuestion("Are all the screws fitted in the front?"))
{
await MainForm.Instance.TestFailed(Btn, "Visual Test Fail - Not all front screws fitted");
await MainForm.Instance.TestFailed(Btn, $"Visual Test Fail - Not all front screws fitted{Level.ERROR}");
}
break;
case 2:
if (!await MainForm.Instance.DisplayQuestion("Are all the screws fitted in the rear?"))
{
await MainForm.Instance.TestFailed(Btn, "Visual Test Fail - Not all rear screws fitted");
await MainForm.Instance.TestFailed(Btn, $"Visual Test Fail - Not all rear screws fitted{Level.ERROR}");
}
break;
case 3:
if (await MainForm.Instance.DisplayQuestion("Shake unit, does it rattle?"))
{
await MainForm.Instance.TestFailed(Btn, "Visual Test Fail - Unit rattles");
{
await MainForm.Instance.TestFailed(Btn, $"Visual Test Fail - Unit rattles{Level.ERROR}");
}
break;
default:
@@ -123,7 +123,7 @@ namespace AiQ_GUI
}
catch (Exception ex)
{
MainForm.Instance.AddToActionsList($"Error fetching versions for {IPAddress}: {ex.Message}");
MainForm.Instance.AddToActionsList($"Error fetching versions for {IPAddress}: {ex.Message}{Level.ERROR}");
return null;
}
@@ -184,9 +184,9 @@ namespace AiQ_GUI
public string IP { get; set; } = string.Empty;
public int RMANum { get; set; } = 0;
public string FlexiVersion { get; set; } = string.Empty; // Flexi version
public string Processor { get; set; } = string.Empty; // Nano, Xavier, Orin or Pi?
public string ModuleManufacturer { get; set; } = string.Empty; // KT&C or Wonwoo
public bool IsChecked { get; set; } // Is it checked for soak test?
public CheckBox CheckBox { get; set; } = new CheckBox();
public string TestReportLoc { get; set; } = string.Empty; // Location of test report file
}
// Static class for global flags
@@ -220,7 +220,7 @@ namespace AiQ_GUI
[GeneratedRegex(@"^[A-Z]{2}\d{2}[A-Z]{2}$", RegexOptions.Compiled)]
internal static partial Regex ModelRegex();
[GeneratedRegex(@"^(?:[FSA])?\d{6}$", RegexOptions.Compiled)]
[GeneratedRegex(@"^(?:[FSAX])?\d{6}$", RegexOptions.Compiled)]
internal static partial Regex LicCodeRegex();
[GeneratedRegex(@"^(?<value>[\d\.]+)(?<unit>[KMGTP]?)(?<suffix>B?)$", RegexOptions.Compiled | RegexOptions.IgnoreCase)]

22
LDS.cs
View File

@@ -10,22 +10,17 @@ namespace AiQ_GUI
public const string IROpensavePath = "IR_Open_image.jpg"; // Path to save the downloaded image
public const string IRTightsavePath = "IR_Tight_image.jpg"; // Path to save the downloaded image
public const string LDSFileName = "LDS.json"; // Local Data Store file name
const string DefaultJSON = "{\r\n \"User\": \"\",\r\n \"LastModel\": \"\",\r\n \"PSUIP\": \"\",\r\n \"EzIP\": \"\",\r\n \"ZebraIP\": \"\",\r\n \"TestTubeIP\": \"\",\r\n \"Shutter\": 0,\r\n \"Iris\": 0,\r\n \"Gain\": 0\r\n}";
const string DefaultJSON = "{\r\n \"User\": \"\",\r\n \"LastModel\": \"\",\r\n \"PSUIP\": \"\",\r\n \"EzIP\": \"\",\r\n \"ZebraIP\": \"\",\r\n \"TestTubeIP\": \"\",\r\n \"Shutter\": 0,\r\n \"Iris\": 0,\r\n \"Gain\": 0\r\n \"GUIPosX\": 0\r\n \"GUIPosY\": 0\r\n}";
public static LocalDataStore GetLDS()
{
try
{
if (!Directory.Exists(MAVPath)) // Check the AiQ folder exists in ProgramData and if it doesn't then create it
{
Directory.CreateDirectory(MAVPath);
}
if (!File.Exists(MAVPath + LDSFileName)) // Check the LDS file exists and if it doesn't then create it
{
// Save a blank version of the JSON
File.WriteAllText(MAVPath + LDSFileName, DefaultJSON);
}
File.WriteAllText(MAVPath + LDSFileName, DefaultJSON); // Save a blank version of the JSON
StreamReader file = new(MAVPath + LDSFileName);
string Content = file.ReadToEnd();
@@ -35,9 +30,8 @@ namespace AiQ_GUI
}
catch // If file can't deserialise
{
MainForm.Instance.AddToActionsList("Error loading Local Data Store");
MainForm.Instance.AddToActionsList($"Error loading Local Data Store{Level.WARNING}");
return null; // Return null to indicate failure
}
}
@@ -51,7 +45,7 @@ namespace AiQ_GUI
}
catch (Exception ex)
{
MainForm.Instance.AddToActionsList($"Error saving Local Data Store: {ex.Message}");
MainForm.Instance.AddToActionsList($"Error saving Local Data Store: {ex.Message}{Level.WARNING}");
}
}
}
@@ -64,8 +58,10 @@ namespace AiQ_GUI
public string EzIP { get; set; } = string.Empty;
public string ZebraIP { get; set; } = string.Empty;
public string TestTubeIP { get; set; } = string.Empty;
public int Shutter { get; set; }
public int Iris { get; set; }
public int Gain { get; set; }
public int Shutter { get; set; } = 0;
public int Iris { get; set; } = 0;
public int Gain { get; set; } = 0;
public int GUIPosX { get; set; } = 0;
public int GUIPosY { get; set; } = 0;
}
}

View File

@@ -1,4 +1,5 @@
using System.Diagnostics;
using System.Security.Principal;
namespace AiQ_GUI
{
@@ -25,6 +26,10 @@ namespace AiQ_GUI
{
try
{
WindowsPrincipal wp = new(WindowsIdentity.GetCurrent()); // Log in a seperate file when admin
if (wp.IsInRole(WindowsBuiltInRole.Administrator))
FileName = FileName.Replace(".log", "_ADMIN.log");
string logFilePath = LDS.MAVPath + FileName;
FileInfo fi = new(logFilePath);
@@ -43,7 +48,7 @@ namespace AiQ_GUI
}
catch (Exception ex)
{
Debug.WriteLine($"Error logging message: {ex.Message}");
MessageBox.Show($"Error logging message: {ex.Message}");
}
}
}

View File

@@ -4,7 +4,7 @@ namespace AiQ_GUI
{
class Access
{
const string connString = @"Provider=Microsoft.ACE.OLEDB.12.0;Data Source=G:\Shared drives\MAV Production GUI's\AiQ\GUI's\AiQ_Final_Test.accdb;Persist Security Info=False;OLE DB Services=-1;";
public const string connString = @"Provider=Microsoft.ACE.OLEDB.12.0;Data Source=G:\Shared drives\MAV Production GUI's\AiQ\GUI's\AiQ_Final_Test.accdb;Persist Security Info=False;OLE DB Services=-1;";
// Reads camera model numbers and descriptions from the database, sorts them alphabetically by model number (except "AB12CD", which appears last), and formats each entry as "ModelNumber - Description".
public static string[] ReadCamTypes()
@@ -38,6 +38,7 @@ namespace AiQ_GUI
IOrderedEnumerable<Tuple<string, string>> sorted = modelTuples.OrderBy(t => t.Item1.Equals("AB12CD", StringComparison.OrdinalIgnoreCase) ? 1 : 0)
.ThenBy(t => t.Item1, StringComparer.OrdinalIgnoreCase);
conn.Close();
// Format the sorted tuples as "ModelNumber - Description" strings and return as array
return sorted.Select(t => $"{t.Item1} - {t.Item2}").ToArray();
}
@@ -67,6 +68,7 @@ namespace AiQ_GUI
UniversalData.LatestVersion = Convert.ToString(reader["AiQGUIVersion"]);
UniversalData.PowerConsumption = Convert.ToInt16(reader["PowerConsumption"]);
UniversalData.LicencingServerURL = Convert.ToString(reader["LicencingServerURL"]);
conn.Close();
}
// Knowing the model number on test, this function reads the database and populates the Camera class with the values.
@@ -97,12 +99,72 @@ namespace AiQ_GUI
CameraAccessInfo.LED_V = Convert.ToDouble(reader["LEDVoltage"]);
CameraAccessInfo.LED_I = Convert.ToInt32(reader["LEDCurrent"]);
CameraAccessInfo.SpreadsheetID = Convert.ToString(reader["SSID"]);
conn.Close();
}
public static void Stats(string TypeOfTest, string modelNumber)
{
Stats([TypeOfTest], modelNumber);
}
public static void Stats(string[] TypeOfTest, string modelNumber)
{
using OleDbConnection conn = new(connString); // Opens connection to Access database
try
{
conn.Open(); // Opens DB
foreach (string type in TypeOfTest)
{
string query = $"UPDATE AiQ SET [{type}] = [{type}] + 1 WHERE [ModelNumber] = ?"; // Add one for every test ran of this type for this model number
using OleDbCommand cmd = new(query, conn); // Create command
cmd.Parameters.AddWithValue("?", modelNumber); // Add model number to prevent injection
int rowsAffected = cmd.ExecuteNonQuery();
// Execute the command and get the number of rows affected
if (rowsAffected == 0) // If one or more rows were updated
MainForm.Instance.AddToActionsList($"No rows affected for {modelNumber}{Level.ERROR}");
}
conn.Close();
}
catch
{
MainForm.Instance.AddToActionsList($"Could not access Access in Google Drive. Is it running?{Level.WARNING}");
return;
}
}
public static void StatsDiags(string redDiagLabels, string RhTxBxActionsText, string ModelNumber)
{
using OleDbConnection conn = new(connString);
conn.Open();
// Null checks
string redVal = string.IsNullOrWhiteSpace(redDiagLabels) ? "-" : redDiagLabels;
string actVal = string.IsNullOrWhiteSpace(RhTxBxActionsText) ? "-" : RhTxBxActionsText;
string model = string.IsNullOrWhiteSpace(ModelNumber) ? "-" : ModelNumber;
const string sql = @"INSERT INTO DiagsStats ([Date], [Model], [Red Diags Labels], [RhTxBxActions Contents]) VALUES (?, ?, ?, ?)";
using OleDbCommand cmd = new(sql, conn);
cmd.Parameters.Add(new OleDbParameter
{
OleDbType = OleDbType.Date,
Value = DateTime.Now
});
cmd.Parameters.AddWithValue("?", model);
cmd.Parameters.AddWithValue("?", redVal);
cmd.Parameters.AddWithValue("?", actVal);
int rows = cmd.ExecuteNonQuery();
conn.Close();
if (rows == 0)
MainForm.Instance.AddToActionsList($"No rows inserted into DiagsStats (unexpected).{Level.ERROR}");
}
}
// Expected universal data for the GUI, read from the database
public class UniversalData
{

View File

@@ -17,7 +17,7 @@ namespace AiQ_GUI
}
else
{
MainForm.Instance.AddToActionsList("Could not find spreadsheet :(");
MainForm.Instance.AddToActionsList($"Could not find spreadsheet :( {Level.ERROR}");
}
}
@@ -31,7 +31,7 @@ namespace AiQ_GUI
}
else
{
MainForm.Instance.AddToActionsList("Could not find spreadsheet :(");
MainForm.Instance.AddToActionsList($"Could not find spreadsheet :( {Level.ERROR}");
}
return null;
}
@@ -40,7 +40,7 @@ namespace AiQ_GUI
{
if (!File.Exists(FilePath))
{
MainForm.Instance.AddToActionsList("Could not find spreadsheet :(");
MainForm.Instance.AddToActionsList($"Could not find spreadsheet :( {Level.ERROR}");
return -1;
}
@@ -62,7 +62,7 @@ namespace AiQ_GUI
{
if (!File.Exists(FilePath))
{
MainForm.Instance.AddToActionsList("Could not find spreadsheet :(");
MainForm.Instance.AddToActionsList($"Could not find spreadsheet :( {Level.ERROR}");
return "Spreadsheet not found";
}
@@ -114,7 +114,7 @@ namespace AiQ_GUI
{
if (!File.Exists(filePath))
{
MainForm.Instance.AddToActionsList("Could not find spreadsheet :(");
MainForm.Instance.AddToActionsList($"Could not find spreadsheet :) {Level.ERROR}");
return "Spreadsheet not found";
}
@@ -150,7 +150,7 @@ namespace AiQ_GUI
}
catch (Exception ex)
{
MainForm.Instance.AddToActionsList("Error updating spreadsheet: " + ex.Message);
MainForm.Instance.AddToActionsList($"Error updating spreadsheet:{Level.ERROR} " + ex.Message);
return $"ERROR: {ex.Message}";
}
}
@@ -161,7 +161,7 @@ namespace AiQ_GUI
{
if (!File.Exists(FilePath))
{
MainForm.Instance.AddToActionsList("Could not find spreadsheet :(");
MainForm.Instance.AddToActionsList($"Could not find spreadsheet :( {Level.ERROR}");
return "Spreadsheet not found";
}
@@ -202,8 +202,8 @@ namespace AiQ_GUI
}
catch (Exception ex)
{
MainForm.Instance.AddToActionsList("Error updating spreadsheet: " + ex.Message);
return "Failed to update spreadsheet data, please check manually: " + ex.Message;
MainForm.Instance.AddToActionsList($"Error updating spreadsheet: {Level.WARNING}" + ex.Message);
return $"Failed to update spreadsheet data, please check manually: {Level.WARNING}" + ex.Message;
}
}
@@ -213,8 +213,9 @@ namespace AiQ_GUI
{
if (!File.Exists(FilePath))
{
MainForm.Instance.AddToActionsList("Could not find spreadsheet :(");
return "Spreadsheet not found";
MainForm.Instance.AddToActionsList($"Could not find spreadsheet :( {Level.ERROR}");
return $"Spreadsheet not found{ Level.ERROR}"
;
}
using XLWorkbook workbook = new(FilePath);
@@ -239,8 +240,8 @@ namespace AiQ_GUI
}
catch (Exception ex)
{
MainForm.Instance.AddToActionsList("Error updating Vaxtor spreadsheet: " + ex.Message);
return "Failed to update spreadsheet data, please check manually: " + ex.Message;
MainForm.Instance.AddToActionsList($"Error updating Vaxtor spreadsheet: {Level.WARNING}" + ex.Message);
return $"Failed to update spreadsheet data, please check manually: {Level.WARNING}" + ex.Message;
}
}
@@ -250,7 +251,7 @@ namespace AiQ_GUI
{
if (!File.Exists(filePath))
{
MainForm.Instance.AddToActionsList("Could not find RMA Control spreadsheet :(");
MainForm.Instance.AddToActionsList($"Could not find RMA Control spreadsheet :({Level.ERROR}");
return 0;
}
@@ -280,7 +281,7 @@ namespace AiQ_GUI
}
catch (Exception ex)
{
MainForm.Instance.AddToActionsList("Error reading RMA Control spreadsheet: " + ex.Message);
MainForm.Instance.AddToActionsList($"Error reading RMA Control spreadsheet: {Level.ERROR}" + ex.Message);
}
return 0; // Default if not found
@@ -292,7 +293,7 @@ namespace AiQ_GUI
{
if (!File.Exists(filePath))
{
MainForm.Instance.AddToActionsList("Could not find spreadsheet :(");
MainForm.Instance.AddToActionsList($"Could not find spreadsheet :( {Level.ERROR}");
return 0;
}
@@ -316,7 +317,7 @@ namespace AiQ_GUI
}
catch (Exception ex)
{
MainForm.Instance.AddToActionsList("Error checking serial number row: " + ex.Message);
MainForm.Instance.AddToActionsList($"Error checking serial number row: {Level.ERROR}" + ex.Message);
}
return 0;
@@ -328,7 +329,7 @@ namespace AiQ_GUI
{
if (!File.Exists(filePath))
{
MainForm.Instance.AddToActionsList("Could not find spreadsheet :(");
MainForm.Instance.AddToActionsList($"Could not find spreadsheet :({Level.ERROR}");
return -1;
}
@@ -343,7 +344,7 @@ namespace AiQ_GUI
}
catch (Exception ex)
{
MainForm.Instance.AddToActionsList("Error checking next free row: " + ex.Message);
MainForm.Instance.AddToActionsList($"Error checking next free row: {Level.ERROR}" + ex.Message);
return -1;
}
}
@@ -417,7 +418,6 @@ namespace AiQ_GUI
// int nextFree = Excel.CheckNextFree(filePath);
// Debug.WriteLine("Next free row in Vaxtor sheet (Column C): " + nextFree);
//}
}
}
}

View File

@@ -1,17 +1,12 @@
using System;
using ClosedXML.Excel;
using System.Data;
using System.Data.OleDb;
using ClosedXML.Excel;
namespace AiQ_GUI.Microsoft
namespace AiQ_GUI
{
internal class StatsExcel
{
private const string connString =
@"Provider=Microsoft.ACE.OLEDB.12.0;Data Source=G:\Shared drives\MAV Production GUI's\AiQ\GUI's\AiQ_Final_Test.accdb;Persist Security Info=False;OLE DB Services=-1;";
private readonly string exportPath =
@"G:\Shared drives\MAV Production GUI's\AiQ\GUI's\AiQ_Test_Stats.xlsx";
private const string exportPath = @"G:\Shared drives\MAV Production GUI's\AiQ\GUI's\AiQ_Test_Stats.xlsx";
public void ExportDatabaseToExcel()
{
@@ -19,32 +14,30 @@ namespace AiQ_GUI.Microsoft
{
MainForm.Instance.AddToActionsList("=== ExportDatabaseToExcel START ===");
using (OleDbConnection conn = new(connString))
using OleDbConnection conn = new(Access.connString);
conn.Open();
MainForm.Instance.AddToActionsList("Connected to Access database.");
DateTime now = DateTime.Now;
// Read LastStatsRun from UniversalData (if any)
DateTime? lastStatsRun = null;
try
{
conn.Open();
MainForm.Instance.AddToActionsList("Connected to Access database.");
DateTime now = DateTime.Now;
// Read LastStatsRun from UniversalData (if any)
DateTime? lastStatsRun = null;
try
using (OleDbCommand cmdPeriod = new("SELECT LastStatsRun FROM UniversalData", conn))
{
using (var cmdPeriod = new OleDbCommand("SELECT LastStatsRun FROM UniversalData", conn))
{
object res = cmdPeriod.ExecuteScalar();
if (res != null && res != DBNull.Value)
lastStatsRun = (DateTime)res;
}
MainForm.Instance.AddToActionsList($"Fetched LastStatsRun: {(lastStatsRun.HasValue ? lastStatsRun.Value.ToString("yyyy-MM-dd HH:mm:ss") : "none")}");
}
catch (Exception periodEx)
{
MainForm.Instance.AddToActionsList($"Warning: could not read LastStatsRun. Details: {periodEx.Message}");
object res = cmdPeriod.ExecuteScalar();
if (res != null && res != DBNull.Value)
lastStatsRun = (DateTime)res;
}
MainForm.Instance.AddToActionsList($"Fetched LastStatsRun: {(lastStatsRun.HasValue ? lastStatsRun.Value.ToString("yyyy-MM-dd HH:mm:ss") : "none")}");
}
catch (Exception periodEx)
{
MainForm.Instance.AddToActionsList($"Warning: could not read LastStatsRun. Details: {periodEx.Message}");
}
// ==================== MAIN STATS EXPORT ====================
string selectColumns = @"
// ==================== MAIN STATS EXPORT ====================
const string selectColumns = @"
ModelNumber,
[Total Tests Run],
[Pre Tests Passed],
@@ -68,71 +61,71 @@ namespace AiQ_GUI.Microsoft
[Visual Test Fail - Not All Rear Screws Fitted],
[Visual Test Fail - Unit rattles]";
string query = $@"
SELECT
{selectColumns}
FROM AiQ";
string query = $@"
SELECT
{selectColumns}
FROM AiQ";
OleDbDataAdapter adapter = new(query, conn);
DataTable dataTable = new();
adapter.Fill(dataTable);
OleDbDataAdapter adapter = new(query, conn);
DataTable dataTable = new();
adapter.Fill(dataTable);
if (dataTable.Rows.Count == 0)
{
MainForm.Instance.AddToActionsList("No data found in AiQ table.");
return;
}
if (dataTable.Rows.Count == 0)
{
MainForm.Instance.AddToActionsList("No data found in AiQ table.");
return;
}
string monthName = now.ToString("MMMM");
string sheetName = $"{monthName} Stats";
string monthName = now.ToString("MMMM");
string sheetName = $"{monthName} Stats";
MainForm.Instance.AddToActionsList($"Adding sheet: {sheetName}");
MainForm.Instance.AddToActionsList($"Adding sheet: {sheetName}");
XLWorkbook workbook;
if (System.IO.File.Exists(exportPath))
{
workbook = new XLWorkbook(exportPath);
MainForm.Instance.AddToActionsList("Opened existing workbook.");
}
else
{
workbook = new XLWorkbook();
MainForm.Instance.AddToActionsList("Created new workbook (file not found).");
}
XLWorkbook workbook;
if (File.Exists(exportPath))
{
workbook = new XLWorkbook(exportPath);
MainForm.Instance.AddToActionsList("Opened existing workbook.");
}
else
{
workbook = new XLWorkbook();
MainForm.Instance.AddToActionsList("Created new workbook (file not found).");
}
if (workbook.Worksheets.Contains(sheetName))
{
workbook.Worksheet(sheetName).Delete();
MainForm.Instance.AddToActionsList($"Deleted old sheet: {sheetName}");
}
if (workbook.Worksheets.Contains(sheetName))
{
workbook.Worksheet(sheetName).Delete();
MainForm.Instance.AddToActionsList($"Deleted old sheet: {sheetName}");
}
var ws = workbook.Worksheets.Add(sheetName);
ws.Cell(1, 1).InsertTable(dataTable, "AiQ_Stats", true);
ws.Columns().AdjustToContents();
IXLWorksheet ws = workbook.Worksheets.Add(sheetName);
ws.Cell(1, 1).InsertTable(dataTable, "AiQ_Stats", true);
ws.Columns().AdjustToContents();
// Write Data Period label and computed period string to the right of the table
int infoCol = dataTable.Columns.Count + 2;
ws.Cell(1, infoCol).Value = "Data Period";
// Write Data Period label and computed period string to the right of the table
int infoCol = dataTable.Columns.Count + 2;
ws.Cell(1, infoCol).Value = "Data Period";
string periodText;
if (lastStatsRun.HasValue)
{
periodText = $"{lastStatsRun.Value:yyyy-MM-dd HH:mm:ss} - {now:yyyy-MM-dd HH:mm:ss}";
}
else
{
periodText = $"Up to {now:yyyy-MM-dd HH:mm:ss}";
}
string periodText;
if (lastStatsRun.HasValue)
{
periodText = $"{lastStatsRun.Value:yyyy-MM-dd HH:mm:ss} - {now:yyyy-MM-dd HH:mm:ss}";
}
else
{
periodText = $"Up to {now:yyyy-MM-dd HH:mm:ss}";
}
ws.Cell(2, infoCol).Value = periodText;
ws.Cell(2, infoCol).Value = periodText;
// ==================== DIAGS STATS EXPORT (CURRENT MONTH ONLY) ====================
MainForm.Instance.AddToActionsList("Exporting DiagsStats for current month...");
// ==================== DIAGS STATS EXPORT (CURRENT MONTH ONLY) ====================
MainForm.Instance.AddToActionsList("Exporting DiagsStats for current month...");
DateTime monthStart = new DateTime(now.Year, now.Month, 1);
DateTime nextMonth = monthStart.AddMonths(1);
DateTime monthStart = new(now.Year, now.Month, 1);
DateTime nextMonth = monthStart.AddMonths(1);
string diagsQuery = @"
const string diagsQuery = @"
SELECT
[Date],
[Model],
@@ -143,129 +136,125 @@ namespace AiQ_GUI.Microsoft
FROM DiagsStats
WHERE [Date] >= ? AND [Date] < ?";
using (var diagsCmd = new OleDbCommand(diagsQuery, conn))
using (OleDbCommand diagsCmd = new(diagsQuery, conn))
{
diagsCmd.Parameters.Add(new OleDbParameter { OleDbType = OleDbType.Date, Value = monthStart });
diagsCmd.Parameters.Add(new OleDbParameter { OleDbType = OleDbType.Date, Value = nextMonth });
using OleDbDataAdapter diagsAdapter = new(diagsCmd);
DataTable diagsTable = new();
diagsAdapter.Fill(diagsTable);
if (diagsTable.Rows.Count > 0)
{
diagsCmd.Parameters.Add(new OleDbParameter { OleDbType = OleDbType.Date, Value = monthStart });
diagsCmd.Parameters.Add(new OleDbParameter { OleDbType = OleDbType.Date, Value = nextMonth });
int startRow = (ws.LastRowUsed()?.RowNumber() ?? 0) + 3;
using (var diagsAdapter = new OleDbDataAdapter(diagsCmd))
{
DataTable diagsTable = new();
diagsAdapter.Fill(diagsTable);
ws.Cell(startRow, 1).Value = "Diags Stats (Current Month)";
ws.Cell(startRow, 1).Style.Font.Bold = true;
ws.Cell(startRow, 1).Style.Font.FontSize = 12;
if (diagsTable.Rows.Count > 0)
{
int startRow = (ws.LastRowUsed()?.RowNumber() ?? 0) + 3;
ws.Cell(startRow + 1, 1).InsertTable(diagsTable, "Diags_Stats", true);
ws.Columns().AdjustToContents();
ws.Cell(startRow, 1).Value = "Diags Stats (Current Month)";
ws.Cell(startRow, 1).Style.Font.Bold = true;
ws.Cell(startRow, 1).Style.Font.FontSize = 12;
ws.Cell(startRow + 1, 1).InsertTable(diagsTable, "Diags_Stats", true);
ws.Columns().AdjustToContents();
MainForm.Instance.AddToActionsList($"Added DiagsStats ({diagsTable.Rows.Count} rows) starting at row {startRow}.");
}
}
MainForm.Instance.AddToActionsList($"Added DiagsStats ({diagsTable.Rows.Count} rows) starting at row {startRow}.");
}
}
// ==================== SAVE MAIN SHEET ====================
workbook.SaveAs(exportPath);
MainForm.Instance.AddToActionsList($"Added {sheetName} to workbook: {exportPath}");
// ==================== SAVE MAIN SHEET ====================
workbook.SaveAs(exportPath);
MainForm.Instance.AddToActionsList($"Added {sheetName} to workbook: {exportPath}");
workbook.Dispose();
workbook.Dispose();
// ==================== BACKUP TABLE (ALWAYS, BEFORE RESET) ====================
string ts = now.ToString("yyyyMMdd_HHmmss");
string backupTableName = $"Ztats_{ts}";
// ==================== BACKUP TABLE (ALWAYS, BEFORE RESET) ====================
string ts = now.ToString("yyyyMMdd_HHmmss");
string backupTableName = $"Ztats_{ts}";
if (backupTableName.Length > 64)
backupTableName = backupTableName.Substring(0, 64);
if (backupTableName.Length > 64)
backupTableName = backupTableName.Substring(0, 64);
string backupSql = $@"
SELECT
{selectColumns}
INTO [{backupTableName}]
FROM AiQ";
try
{
using (OleDbCommand cmdBackup = new(backupSql, conn))
{
cmdBackup.ExecuteNonQuery();
}
MainForm.Instance.AddToActionsList($"Created backup table: [{backupTableName}]");
}
catch (Exception backupEx)
{
MainForm.Instance.AddToActionsList($"ERROR creating backup table [{backupTableName}]. Aborting reset to protect data. Details: {backupEx.Message}");
MainForm.Instance.AddToActionsList("=== ExportDatabaseToExcel END (backup failed, no reset) ===");
return;
}
string backupSql = $@"
SELECT
{selectColumns}
INTO [{backupTableName}]
FROM AiQ";
using (OleDbTransaction tx = conn.BeginTransaction())
{
try
{
using (var cmdBackup = new OleDbCommand(backupSql, conn))
const string resetSql = @"
UPDATE AiQ SET
[Total Tests Run] = 0,
[Pre Tests Passed] = 0,
[Pre Tests Failed] = 0,
[Final Tests Passed] = 0,
[Final Tests Failed] = 0,
[RMA Total Tests Run] = 0,
[RMA Pre Tests Passed] = 0,
[RMA Pre Tests Failed] = 0,
[RMA Final Tests Passed] = 0,
[RMA Final Tests Failed] = 0,
[Diagnostic Failure] = 0,
[Failed To Set Model Or Serial Number] = 0,
[Camera Not In Test Tube] = 0,
[Could Not Zoom Modules To 8000] = 0,
[Could Not Zoom Modules To Full Wide] = 0,
[Please Get RMA Number From Operations Team Before Continuing] = 0,
[Please Get A Valid Vaxtor Product Key Before Continuing] = 0,
[Visual Test Fail - Sleeve Not Aligned] = 0,
[Visual Test Fail - Not All Front Screws Fitted] = 0,
[Visual Test Fail - Not All Rear Screws Fitted] = 0,
[Visual Test Fail - Unit rattles] = 0;";
using (OleDbCommand cmdReset = new(resetSql, conn, tx))
{
cmdBackup.ExecuteNonQuery();
int affected = cmdReset.ExecuteNonQuery();
MainForm.Instance.AddToActionsList($"Zeroed counters on AiQ rows: {affected} row(s).");
}
MainForm.Instance.AddToActionsList($"Created backup table: [{backupTableName}]");
int updatedRows;
using (OleDbCommand cmdUpd = new("UPDATE UniversalData SET LastStatsRun = ?", conn, tx))
{
cmdUpd.Parameters.Add(new OleDbParameter { OleDbType = OleDbType.Date, Value = now });
updatedRows = cmdUpd.ExecuteNonQuery();
}
if (updatedRows == 0)
{
using OleDbCommand cmdIns = new("INSERT INTO UniversalData (LastStatsRun) VALUES (?)", conn, tx);
cmdIns.Parameters.Add(new OleDbParameter { OleDbType = OleDbType.Date, Value = now });
cmdIns.ExecuteNonQuery();
MainForm.Instance.AddToActionsList("Inserted LastStatsRun row.");
}
else
{
MainForm.Instance.AddToActionsList("Updated LastStatsRun successfully.");
}
tx.Commit();
MainForm.Instance.AddToActionsList("Reset committed.");
}
catch (Exception backupEx)
catch (Exception resetEx)
{
MainForm.Instance.AddToActionsList($"ERROR creating backup table [{backupTableName}]. Aborting reset to protect data. Details: {backupEx.Message}");
MainForm.Instance.AddToActionsList("=== ExportDatabaseToExcel END (backup failed, no reset) ===");
return;
tx.Rollback();
MainForm.Instance.AddToActionsList($"ERROR during reset, rolled back. Details: {resetEx.Message}");
}
using (var tx = conn.BeginTransaction())
{
try
{
string resetSql = @"
UPDATE AiQ SET
[Total Tests Run] = 0,
[Pre Tests Passed] = 0,
[Pre Tests Failed] = 0,
[Final Tests Passed] = 0,
[Final Tests Failed] = 0,
[RMA Total Tests Run] = 0,
[RMA Pre Tests Passed] = 0,
[RMA Pre Tests Failed] = 0,
[RMA Final Tests Passed] = 0,
[RMA Final Tests Failed] = 0,
[Diagnostic Failure] = 0,
[Failed To Set Model Or Serial Number] = 0,
[Camera Not In Test Tube] = 0,
[Could Not Zoom Modules To 8000] = 0,
[Could Not Zoom Modules To Full Wide] = 0,
[Please Get RMA Number From Operations Team Before Continuing] = 0,
[Please Get A Valid Vaxtor Product Key Before Continuing] = 0,
[Visual Test Fail - Sleeve Not Aligned] = 0,
[Visual Test Fail - Not All Front Screws Fitted] = 0,
[Visual Test Fail - Not All Rear Screws Fitted] = 0,
[Visual Test Fail - Unit rattles] = 0;";
using (var cmdReset = new OleDbCommand(resetSql, conn, tx))
{
int affected = cmdReset.ExecuteNonQuery();
MainForm.Instance.AddToActionsList($"Zeroed counters on AiQ rows: {affected} row(s).");
}
int updatedRows;
using (var cmdUpd = new OleDbCommand("UPDATE UniversalData SET LastStatsRun = ?", conn, tx))
{
cmdUpd.Parameters.Add(new OleDbParameter { OleDbType = OleDbType.Date, Value = now });
updatedRows = cmdUpd.ExecuteNonQuery();
}
if (updatedRows == 0)
{
using (var cmdIns = new OleDbCommand("INSERT INTO UniversalData (LastStatsRun) VALUES (?)", conn, tx))
{
cmdIns.Parameters.Add(new OleDbParameter { OleDbType = OleDbType.Date, Value = now });
cmdIns.ExecuteNonQuery();
MainForm.Instance.AddToActionsList("Inserted LastStatsRun row.");
}
}
else
{
MainForm.Instance.AddToActionsList("Updated LastStatsRun successfully.");
}
tx.Commit();
MainForm.Instance.AddToActionsList("Reset committed.");
}
catch (Exception resetEx)
{
tx.Rollback();
MainForm.Instance.AddToActionsList($"ERROR during reset, rolled back. Details: {resetEx.Message}");
}
}
conn.Close();
}
MainForm.Instance.AddToActionsList("=== ExportDatabaseToExcel END ===");

View File

@@ -7,9 +7,9 @@ namespace AiQ_GUI
{
const string webhookUrl = "https://default71bd136a1c65418fb59e927135629c.ac.environment.api.powerplatform.com:443/powerautomate/automations/direct/workflows/b27c5192e83f4f48b20c1b115985b0b3/triggers/manual/paths/invoke/?api-version=1&sp=%2Ftriggers%2Fmanual%2Frun&sv=1.0&sig=1-eCbYXms6xInRKHwz3tgAcdQ9x7CSjl3Yzw2V_1MlA";
public static async Task SendMssg(string ApprovalRow, string User)
public static async Task SendMssg(string ApprovalRow, string User) // Sometimes You will need to reauthenticate the workflow
{
using HttpClient client = new HttpClient();
using HttpClient client = new();
string link = $"https://docs.google.com/spreadsheets/d/1bCcCr4OYqfjmydt6UqtmN4FQETezXmZRSStJdCCcqZM/edit#gid=1931079354&range=A{ApprovalRow}"; // Has to be parsed like this as teams doesnt hyperlink otherwise
@@ -21,7 +21,7 @@ namespace AiQ_GUI
};
string json = JsonConvert.SerializeObject(payload);
StringContent content = new StringContent(json, Encoding.UTF8, "application/json");
StringContent content = new(json, Encoding.UTF8, "application/json");
HttpResponseMessage response = await client.PostAsync(webhookUrl, content);
}

View File

@@ -1,6 +1,5 @@
using System.Diagnostics;
using System.Reflection;
using System.Runtime.InteropServices;
using System.Security.Principal;
namespace AiQ_GUI
@@ -16,14 +15,11 @@ namespace AiQ_GUI
.Where(p => targetProcesses.Any(tp => p.ProcessName.Contains(tp)))
.Select(clsProcess =>
{
using (clsProcess)
try
{
try
{
clsProcess.CloseMainWindow();
}
catch { }
clsProcess.CloseMainWindow();
}
catch { }
return Task.CompletedTask;
});
@@ -34,7 +30,7 @@ namespace AiQ_GUI
{
Logging.LogMessage($"Starting exe from {ExeLoc}");
ProcessStartInfo processInfo = new ProcessStartInfo(ExeLoc)
ProcessStartInfo processInfo = new(ExeLoc)
{
UseShellExecute = true,
Verb = "runas"
@@ -64,7 +60,7 @@ namespace AiQ_GUI
{
StartAsAdmin(ExeLoc);
}
else if (runAsAdmin)
else if (Properties.Settings.Default.FirstRun && runAsAdmin)
{
try
{
@@ -97,43 +93,4 @@ namespace AiQ_GUI
}
}
}
[ComImport, Guid("AF230D27-BABA-4E42-ACED-F524F22CFCE2")]
public interface INetFwRule
{
string Name { get; set; }
string Description { get; set; }
string ApplicationName { get; set; }
string ServiceName { get; set; }
int Protocol { get; set; }
string LocalPorts { get; set; }
string RemotePorts { get; set; }
string LocalAddresses { get; set; }
string RemoteAddresses { get; set; }
string IcmpTypesAndCodes { get; set; }
int Direction { get; set; }
object Interfaces { get; set; }
string InterfaceTypes { get; set; }
bool Enabled { get; set; }
string Grouping { get; set; }
int Profiles { get; set; }
bool EdgeTraversal { get; set; }
int Action { get; set; }
}
[ComImport, Guid("98325047-C671-4174-8D81-DEFCD3F03186")]
public interface INetFwPolicy2
{
int CurrentProfileTypes { get; }
void get_FirewallEnabled(int profileType, out bool enabled);
void put_FirewallEnabled(int profileType, bool enabled);
void get_ExcludedInterfaces(int profileType, out object interfaces);
void put_ExcludedInterfaces(int profileType, object interfaces);
int BlockAllInboundTraffic { get; set; }
int NotificationsDisabled { get; set; }
int UnicastResponsesToMulticastBroadcastDisabled { get; set; }
object Rules { get; }
object ServiceRestriction { get; }
// ...other members omitted for brevity
}
}

View File

@@ -10,7 +10,7 @@ namespace AiQ_GUI
public static HttpClient? SingleHTTPClient;
public static HttpClient Client => SingleHTTPClient ?? throw new InvalidOperationException("Client not initialized.");
public static void Initialize(string username, string password)
public async static void Initialize(string username, string password)
{
HttpClientHandler handler = new()
{
@@ -23,6 +23,19 @@ namespace AiQ_GUI
{
Timeout = TimeSpan.FromSeconds(20)
};
bool UP = false;
int i = 0;
while (!UP)
{
UP = await PingIP("10.10.10.137");
await Task.Delay(500);
if (i >= 5)
return; // Try 5 times max
i++;
}
}
// Handles get and post to the camera API
@@ -58,15 +71,15 @@ namespace AiQ_GUI
}
catch (TaskCanceledException ex)
{
return $"HTTP error calling {url}: {ex.Message}";
return $"HTTP error calling {url}: {ex.Message}{Level.ERROR}";
}
catch (HttpRequestException ex)
{
return $"HTTP error calling {url}: {ex.Message}";
return $"HTTP error calling {url}: {ex.Message}{Level.ERROR}";
}
catch (Exception ex)
{
return $"Unexpected error calling {url}: {ex.Message}";
return $"Unexpected error calling {url}: {ex.Message}{Level.ERROR}";
}
}
@@ -74,6 +87,7 @@ namespace AiQ_GUI
{
const int sendPort = 6666;
const int receivePort = 6667;
const int discoveryTimeoutMs = 1000;
IList<string> FoundCams = [];
byte[] discoveryPacket = [0x50, 0x4f, 0x4c, 0x4c, 0xaf, 0xb0, 0xb3, 0xb3, 0xb6, 0x01, 0xa8, 0xc0, 0x0b, 0x1a, 0x00, 0x00];
@@ -88,9 +102,9 @@ namespace AiQ_GUI
}
using UdpClient receiver = new(receivePort); // Listen for replies on fixed port
receiver.Client.ReceiveTimeout = 750;
receiver.Client.ReceiveTimeout = discoveryTimeoutMs;
DateTime timeout = DateTime.Now.AddMilliseconds(750);
DateTime timeout = DateTime.Now.AddMilliseconds(discoveryTimeoutMs);
try
{
while (DateTime.Now < timeout)
@@ -143,9 +157,9 @@ namespace AiQ_GUI
PingReply reply = await myPing.SendPingAsync(ipAddress, 1000); // Timeout is 1s
return reply.Status == IPStatus.Success;
}
catch (PingException ex) { MainForm.Instance.AddToActionsList($"PingException: Unable to ping {ipAddress}. Reason: {ex.Message}"); }
catch (SocketException ex) { MainForm.Instance.AddToActionsList($"SocketException: Network error while pinging {ipAddress}. Reason: {ex.Message}"); }
catch (Exception ex) { MainForm.Instance.AddToActionsList($"Unexpected error while pinging {ipAddress}: {ex.Message}"); }
catch (PingException ex) { MainForm.Instance.AddToActionsList($"PingException: Unable to ping {ipAddress}. Reason: {ex.Message}{Level.WARNING}"); }
catch (SocketException ex) { MainForm.Instance.AddToActionsList($"SocketException: Network error while pinging {ipAddress}. Reason: {ex.Message}{Level.ERROR}"); }
catch (Exception ex) { MainForm.Instance.AddToActionsList($"Unexpected error while pinging {ipAddress}: {ex.Message}{Level.ERROR}"); }
}
return false;

13
PDF.cs
View File

@@ -167,7 +167,6 @@ namespace AiQ_GUI
}
List<string> logLines = File.Exists(logFilePath) ? File.ReadAllLines(logFilePath).ToList() : new List<string>();
List<string> errorLines = logLines.Where(l => l.Contains("[ERROR]")).ToList();
List<string> warningLines = logLines.Where(l => l.Contains("[WARNING]")).ToList();
@@ -337,7 +336,17 @@ namespace AiQ_GUI
renderer.PdfDocument.Options.Layout = PdfWriterLayout.Compact;
// Construct save path
string fullPath = TestRecordDir + CamSoak.Model + $"\\SoakTestReport_{CamSoak.Model}_{CamSoak.Serial}_{pcTime:dd-MM-yyyy_HH-mm-ss}" + (CamSoak.RMANum != 0 ? $" RMA{CamSoak.RMANum}" : "") + ".pdf";
string SaveDir = TestRecordDir + CamSoak.Model;
if (!Directory.Exists(TestRecordDir))
{
SaveDir = Path.Combine(LDS.MAVPath, "SoakTestRecords"); // Gets local folder if the main TestRecordDir is not found (no google drive).
if (!Directory.Exists(SaveDir))
Directory.CreateDirectory(SaveDir);
}
string fullPath = Path.Combine(SaveDir, $"SoakTestReport_{CamSoak.Model}_{CamSoak.Serial}_{pcTime:dd-MM-yyyy_HH-mm-ss}" + (CamSoak.RMANum != 0 ? $" RMA{CamSoak.RMANum}" : "") + ".pdf");
//string fullPath = TestRecordDir + CamSoak.Model + $"\\SoakTestReport_{CamSoak.Model}_{CamSoak.Serial}_{pcTime:dd-MM-yyyy_HH-mm-ss}" + (CamSoak.RMANum != 0 ? $" RMA{CamSoak.RMANum}" : "") + ".pdf";
renderer.PdfDocument.Save(fullPath);
Logging.LogMessage("Soak Test PDF saved to " + fullPath);

View File

@@ -19,7 +19,7 @@ namespace AiQ_GUI
}
catch (WebDriverTimeoutException)
{
MainForm.Instance.AddToActionsList("Could not load web page " + url + "1. Check camera has no password set. " + Environment.NewLine + "2. If unable to fix speak to supervisor.");
MainForm.Instance.AddToActionsList($"Could not load web page {Level.ERROR}" + url + $"1. Check camera has no password set. {Level.ERROR}" + Environment.NewLine + $"2. If unable to fix speak to supervisor.{Level.ERROR}");
}
}
@@ -39,14 +39,49 @@ namespace AiQ_GUI
return elementID;
}
// Clicks the element with the specified ID using Chromedriver
public static void ClickElementByID(string elementID, ChromeDriver driver)
public static void SwitchUser(ChromeDriver driver)
{
ClickElementByID(elementID, true, driver);
try
{
WebDriverWait wait = new(driver, TimeSpan.FromSeconds(10));
// Click "Switch User"
IWebElement switchBtn = wait.Until(d =>
d.FindElement(By.XPath("//*[contains(text(),'Switch User')]"))
);
switchBtn.Click();
// Username
IWebElement userBox = wait.Until(d => d.FindElement(By.Id("username")));
userBox.Clear();
userBox.SendKeys("admin");
// Password
IWebElement passBox = wait.Until(d => d.FindElement(By.Id("password")));
passBox.Clear();
passBox.SendKeys("admin");
// Login button
IWebElement loginBtn = wait.Until(d =>
d.FindElement(By.XPath("//button[contains(normalize-space(text()), 'Login')]"))
);
// Wait until it's clickable manually (no SeleniumExtras)
wait.Until(d => loginBtn.Displayed && loginBtn.Enabled);
loginBtn.Click();
MainForm.Instance.AddToActionsList($"Switched user and logged in.{Level.Success}");
}
catch (Exception ex)
{
MainForm.Instance.AddToActionsList($"SwitchUser failed: {Level.ERROR}" + ex.Message);
}
}
// Attempts to click the web element with the specified ID; refreshes the page and retries once if the initial attempt fails
public static void ClickElementByID(string elementID, bool tryagain, ChromeDriver driver)
public static void ClickElementByID(string elementID, ChromeDriver driver, bool tryagain = true)
{
try
{
@@ -61,26 +96,47 @@ namespace AiQ_GUI
driver.Navigate().Refresh();
WebDriverWait wait = new(driver, TimeSpan.FromSeconds(10));
wait.Until(d => ((IJavaScriptExecutor)d).ExecuteScript("return document.readyState").Equals("complete"));
ClickElementByID(elementID, false, driver);
ClickElementByID(elementID, driver, false);
}
MainForm.Instance.AddToActionsList("Could not click " + elementID);
MainForm.Instance.AddToActionsList($"Could not click {Level.WARNING}" + elementID);
}
}
// Initialises and opens a ChromeDriver with specific options
public static ChromeDriver OpenDriver()
{
ChromeDriverService chromeDriverService = ChromeDriverService.CreateDefaultService();
chromeDriverService.HideCommandPromptWindow = true;
ChromeOptions options = new();
options.AddArguments("--app=data:,", "--window-size=960,1040", "--window-position=0,0");
options.AddExcludedArgument("enable-automation");
options.AddAdditionalChromeOption("useAutomationExtension", false);
try
{
ChromeOptions options = new();
options.AddArguments("--app=data:,",
"--window-size=960,1040",
"--window-position=0,0",
"--no-sandbox",
"--incognito",
"--no-first-run",
"--no-default-browser-check",
"--disable-features=BrowserAddPersonFeature,InterestFeedContentSuggestions");
ChromeDriver driver = new(chromeDriverService, options);
driver.Manage().Timeouts().PageLoad = TimeSpan.FromSeconds(5);
return driver;
// Use a unique temporary profile to avoid conflicts
string tempProfile = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString());
options.AddArguments($"--user-data-dir={tempProfile}");
// manual driver path
string driverPath = @"C:\ProgramData\MAV\AiQ_GUI";
ChromeDriverService service = ChromeDriverService.CreateDefaultService(driverPath);
service.HideCommandPromptWindow = true;
ChromeDriver driver = new(service, options);
driver.Manage().Timeouts().PageLoad = TimeSpan.FromSeconds(5);
return driver;
}
catch (Exception ex)
{
MainForm.Instance.AddToActionsList($"Failed to create ChromeDriver: {Level.ERROR}" + ex.Message);
throw;
}
}
// Changes the value of a dropdown element by ID, logs the action, and verifies the result using flashline feedback
@@ -101,10 +157,7 @@ namespace AiQ_GUI
await Task.Delay(500); // Wait for the change to take effect
if (!await Checkflashline(driver, CamAct))
{
Logging.LogWarningMessage("Bad flashline after changing: " + fullId, SoakLogFile);
MainForm.Instance.AddToActionsList("Bad flashline after changing: " + fullId);
}
}
// Monitors the flashline element's color to determine success (green) or failure (red) of a camera

View File

@@ -7,8 +7,9 @@ namespace AiQ_GUI
internal class SoakTest
{
// Main soak test loop: Randomise dropdowns, run luminance test every hour, power cycle at 7am
public static async Task StartSoak(Camera CamInfo, CancellationToken token)
public static async Task StartSoak(Camera CamInfo, CancellationTokenSource CTS)
{
CancellationToken token = CTS.Token;
if (CamInfo.Serial == "N/A")
CamInfo.Serial = "UNKNOWN"; // If serial is not set, set it to UNKNOWN. Cannot have N/A in file names.
@@ -18,6 +19,8 @@ namespace AiQ_GUI
try
{
driver = Selenium.OpenDriver();
Logging.LogMessage("----- Soak test started -----", SoakLogFile);
await Task.Delay(1000); // Small delay to ensure driver is ready
// Keep retrying until connected or cancelled
bool connected = false;
@@ -27,13 +30,14 @@ namespace AiQ_GUI
{
// Attempt initial connection and navigation to setup tab
Selenium.GoToUrl($"http://{CamInfo.IP}", driver);
Selenium.SwitchUser(driver);
Selenium.ClickElementByID("tabSetup", driver);
connected = true;
}
catch (Exception ex)
{
Logging.LogErrorMessage($"Initial connection failed: {ex.Message}", SoakLogFile);
MainForm.Instance.AddToActionsList($"[{CamInfo.IP}] Initial connection failed: {ex.Message}");
SoakError($"Initial connection failed: {ex.Message}", SoakLogFile, CamInfo.CheckBox);
MainForm.Instance.AddToActionsList($"[{CamInfo.IP}] Initial connection failed: {Level.ERROR}{ex.Message}");
// Wait 10 seconds before trying again
await Task.Delay(TimeSpan.FromSeconds(10), token);
@@ -48,7 +52,7 @@ namespace AiQ_GUI
}
catch (Exception ex)
{
Logging.LogErrorMessage($"Failed to get element IDs: {ex.Message}", SoakLogFile);
SoakError($"Failed to get element IDs: {Level.ERROR} {ex.Message}", SoakLogFile, CamInfo.CheckBox);
return;
}
@@ -56,6 +60,16 @@ namespace AiQ_GUI
while (!token.IsCancellationRequested)
{
try
{
string TheString = "Selenium string: " + driver.Manage().Window.Size; // Fails out if Window doesn't exist.
}
catch
{
CTS.Cancel();
continue;
}
int currentHour = DateTime.Now.Hour;
// At 7am, power cycle the camera
@@ -70,7 +84,7 @@ namespace AiQ_GUI
// Retry ping until camera responds or cancelled
while (!await Network.PingIP(CamInfo.IP) && !token.IsCancellationRequested)
{
Logging.LogErrorMessage($"Camera did not respond after restart.", SoakLogFile);
SoakError($"Camera did not respond after restart.{Level.ERROR}", SoakLogFile, CamInfo.CheckBox);
await Task.Delay(TimeSpan.FromMinutes(1), token); // Retry after delay of 1 minute
}
@@ -84,7 +98,7 @@ namespace AiQ_GUI
}
catch (Exception ex)
{
Logging.LogErrorMessage($"Error during power cycle: {ex.Message}", SoakLogFile);
SoakError($"Error during power cycle: {Level.ERROR} {ex.Message}", SoakLogFile, CamInfo.CheckBox);
}
}
@@ -94,11 +108,11 @@ namespace AiQ_GUI
Logging.LogMessage($"Hour changed to {currentHour}, running ImageCheck.", SoakLogFile);
try
{
ImageCheck(driver, SoakLogFile, CamInfo.IP, CamInfo.DevPass, elementID);
ImageCheck(driver, SoakLogFile, CamInfo, elementID);
}
catch (Exception ex)
{
Logging.LogErrorMessage($"ImageCheck failed: {ex.Message}", SoakLogFile);
SoakError($"ImageCheck failed: {Level.ERROR}{Level.ERROR} {ex.Message}", SoakLogFile, CamInfo.CheckBox);
}
lastHour = currentHour;
}
@@ -117,7 +131,7 @@ namespace AiQ_GUI
}
catch (Exception ex)
{
Logging.LogErrorMessage($"ChangeRandomDropdown failed: {ex.Message}", SoakLogFile);
SoakError($"ChangeRandomDropdown failed: {Level.WARNING} {ex.Message}", SoakLogFile, CamInfo.CheckBox);
}
try
@@ -138,11 +152,12 @@ namespace AiQ_GUI
// Create Test report in the same directory as the final test reports.
string soakLogPath = LDS.MAVPath + SoakLogFile;
string SoakTestPath = PDF.CreateSoakTestReport(CamInfo, MainForm.Instance.CbBxUserName.Text, DateTime.Now, soakLogPath);
if (SoakTestPath != null)
{
CamInfo.TestReportLoc = SoakTestPath;
// Delete the soak test log file if the report was created successfully
try
{
@@ -151,7 +166,7 @@ namespace AiQ_GUI
}
catch (Exception ex)
{
Logging.LogErrorMessage($"Failed to delete soak log file: {ex.Message}{Environment.NewLine}Please delete if you find this.", SoakLogFile);
SoakError($"Failed to delete soak log file: {ex.Message}{Environment.NewLine}Please delete if you find this.", SoakLogFile, CamInfo.CheckBox);
}
// Find the final test report PDF for this camera
@@ -169,13 +184,13 @@ namespace AiQ_GUI
}
catch (Exception ex)
{
MainForm.Instance.AddToActionsList($"Failed to find final test report: {ex.Message}");
MainForm.Instance.AddToActionsList($"Failed to find final test report: {Level.ERROR}{ex.Message}");
}
// Link PDFs if both exist
if (!string.IsNullOrEmpty(finalTestPath) && File.Exists(finalTestPath) && !string.IsNullOrEmpty(SoakTestPath) && File.Exists(SoakTestPath))
{
string outputPath = finalTestDir + $"Final&SoakTestReport_{CamInfo.Model}_{CamInfo.Serial}_{DateTime.Now.ToString("dd-MM-yyyy_HH-mm-ss")}.pdf";
string outputPath = finalTestDir + $"Final&SoakTestReport_{CamInfo.Model}_{CamInfo.Serial}_{DateTime.Now.ToString("dd-MM-yyyy_HH-mm-ss")}.pdf{Level.Success}";
try
{
if (PDF.LinkPDFs(finalTestPath, SoakTestPath, outputPath)) // Delete the separate soak and final test reports if Linking was successful
@@ -185,7 +200,7 @@ namespace AiQ_GUI
File.Delete(SoakTestPath);
}
else
MainForm.Instance.AddToActionsList($"Failed to link or delete PDFs");
MainForm.Instance.AddToActionsList($"Failed to link or delete PDFs {Level.ERROR}");
}
catch (Exception ex)
{
@@ -197,7 +212,7 @@ namespace AiQ_GUI
}
// Capture's bright and dark images, then compares their luminance to verify sufficient contrast and adds results to the log
public static async Task LuminescenceMean(string FullID, ChromeDriver driver, string[] SettingMinMax, string SoakLogFile, string IP, string DevPass, string CamAct)
public static async Task LuminescenceMean(string FullID, ChromeDriver driver, string[] SettingMinMax, string SoakLogFile, Camera CamInfo, string CamAct)
{
string controlType = FullID.Split('_')[0]; // Extract control type from FullID (e.g. "Shutter_1234" → "Shutter")
@@ -207,11 +222,11 @@ namespace AiQ_GUI
await Task.Delay(500);
// Take bright image
Image ImageBright = await ImageProcessing.GetProcessedImage("Infrared-snapshot", IP, DevPass);
Image ImageBright = await ImageProcessing.GetProcessedImage("Infrared", CamInfo.IP, CamInfo.DevPass);
if (ImageBright == null)
{
Logging.LogWarningMessage($"Bright image is null for {controlType} at setting {SettingMinMax[0]}", SoakLogFile);
MainForm.Instance.AddToActionsList($"Bright image is null for {controlType} at setting {SettingMinMax[0]}");
MainForm.Instance.AddToActionsList($"Bright image is null for {controlType} at setting {SettingMinMax[0]}{Level.WARNING}");
return;
}
@@ -221,11 +236,11 @@ namespace AiQ_GUI
await Task.Delay(500);
// Take dark image
Image ImageDark = await ImageProcessing.GetProcessedImage("Infrared-snapshot", IP, DevPass);
Image ImageDark = await ImageProcessing.GetProcessedImage("Infrared", CamInfo.IP, CamInfo.DevPass);
if (ImageDark == null)
{
Logging.LogWarningMessage($"Dark image is null for {controlType} at setting {SettingMinMax[1]}", SoakLogFile);
MainForm.Instance.AddToActionsList($"Dark image is null for {controlType} at setting {SettingMinMax[1]}");
MainForm.Instance.AddToActionsList($"Dark image is null for {controlType} at setting {SettingMinMax[1]}{Level.WARNING}");
return;
}
@@ -235,9 +250,10 @@ namespace AiQ_GUI
if (Bright_Lum < Dark_Lum * 1.01)
{
Logging.LogErrorMessage(
$"Insufficient luminance contrast. Bright: {Bright_Lum:F2}, Dark: {Dark_Lum:F2} | Type: {controlType} | Bright Setting: {SettingMinMax[0]}, Dark Setting: {SettingMinMax[1]}",
SoakLogFile
SoakError(
$"Insufficient luminance contrast. Bright: {Bright_Lum:F2}, Dark: {Dark_Lum:F2} | Type: {controlType} | Bright Setting: {SettingMinMax[0]}, Dark Setting: {SettingMinMax[1]}{Level.WARNING}",
SoakLogFile,
CamInfo.CheckBox
);
}
else
@@ -250,7 +266,7 @@ namespace AiQ_GUI
}
// Performs a series of camera control adjustments and luminance checks to verify image settings functionality
public async static void ImageCheck(ChromeDriver driver, string SoakLogFile, string IP, string DevPass, ElementID elementID)
public async static void ImageCheck(ChromeDriver driver, string SoakLogFile, Camera CamInfo, ElementID elementID)
{
await Selenium.Dropdown_Change(elementID.modeId, "Manual", driver, SoakLogFile, elementID.CamAct);
await Selenium.Dropdown_Change(elementID.shutterId, "1/1000", driver, SoakLogFile, elementID.CamAct);
@@ -258,11 +274,11 @@ namespace AiQ_GUI
await Selenium.Dropdown_Change(elementID.irisId, "F4.0", driver, SoakLogFile, elementID.CamAct);
await Selenium.Dropdown_Change(elementID.irLevelId, "Safe", driver, SoakLogFile, elementID.CamAct);
await LuminescenceMean(elementID.shutterId, driver, ["1/100", "1/10000"], SoakLogFile, IP, DevPass, elementID.CamAct); // Check Shutter goes from min to max
await LuminescenceMean(elementID.shutterId, driver, ["1/100", "1/10000"], SoakLogFile, CamInfo, elementID.CamAct); // Check Shutter goes from min to max
await Selenium.Dropdown_Change(elementID.shutterId, "1/1000", driver, SoakLogFile, elementID.CamAct); // Reset to default
await LuminescenceMean(elementID.irisId, driver, ["F2.0", "F16"], SoakLogFile, IP, DevPass, elementID.CamAct); // Check iris goes from min to max
await LuminescenceMean(elementID.irisId, driver, ["F2.0", "F16"], SoakLogFile, CamInfo, elementID.CamAct); // Check iris goes from min to max
await Selenium.Dropdown_Change(elementID.irisId, "F4.0", driver, SoakLogFile, elementID.CamAct); // Reset to default
await LuminescenceMean(elementID.gainId, driver, ["20dB", "0dB"], SoakLogFile, IP, DevPass, elementID.CamAct); // Check gain goes from min to max
await LuminescenceMean(elementID.gainId, driver, ["20dB", "0dB"], SoakLogFile, CamInfo, elementID.CamAct); // Check gain goes from min to max
}
public async static Task ChangeRandomDropdown(ChromeDriver driver, string SoakLogFile, ElementID elementID)
@@ -318,6 +334,12 @@ namespace AiQ_GUI
return dynamicButton;
}
private static void SoakError(string ErrMssg, string LogFile, CheckBox CkBx)
{
Logging.LogErrorMessage(ErrMssg, LogFile);
CkBx.ForeColor = Color.Red;
}
}
public class ElementID

View File

@@ -1,73 +0,0 @@
{
"id": "GLOBAL--Device",
"configHash": "101478235",
"propHardwarePlatform": {
"value": "JETSON_NANO",
"datatype": "mav.flexi.web.pages.SystemConfig$HardwareConfigurationOption",
"accepted": "[NONE, JETSON_NANO, JETSON_XAVIER, RASPBERRY_PI_CM4, RASPBERRY_PI_CM5, DEVELOPER_WORKSTATION]"
},
"propApplicationPlatform": {
"value": "CUSTOM",
"datatype": "mav.flexi.web.pages.SystemConfig$ApplicationConfigurationOption",
"accepted": "[NONE, CUSTOM, MONO_BASIC_ANPR, DUAL_CAMERA_BASIC_ANPR, SINGLE_SAF_DUAL_CAMERA_BASIC_ANPR, DUAL_CAMERA_STREAMING, BAYGUARD, SITE_ACCESS_CONTROL_WITH_MODBUS, WAPOL_IQ50_VRM_CORRECTION, BAYWATCH_MONO_CAM, BAYWATCH_DUAL_CAM, BAYWATCH_TRIPLE_CAM, BAYWATCH_QUADRUPLE_CAM, IN_CAR_ANPR]"
},
"propDeviceName": {
"value": "Test Nano",
"datatype": "java.lang.String"
},
"propLocalTimeZone": {
"value": "Europe/London (UTC+00)",
"datatype": "mav.util.TimeZoneEnum",
"accepted": "[Africa/Cairo (UTC+02), Africa/Johannesburg (UTC+02), Africa/Lagos (UTC+01), Africa/Monrousing (UTC+00), America/Anchorage (UTC-09), America/Chicago (UTC-06), America/Denver (UTC-07), America/Edmonton (UTC-07), America/Jamaica (UTC-05), America/Los Angeles (UTC-08), America/Mexico City (UTC-06), America/Montreal (UTC-05), America/New/York (UTC-05), America/Phoenix (UTC-07), America/Puerto Rico (UTC-04), America/Sao Paulo (UTC-03), America/Toronto (UTC-05), America/Vancouver (UTC-08), Asia/Hong Kong (UTC+08), Asia/Jerusalem (UTC+02), Asia/Manila (UTC+08), Asia/Seoul (UTC+09), Asia/Tokyo (UTC+09), Atlantic/Reykjavik (UTC+00), Australia/Perth (UTC+08), Australia/Sydney (UTC+10), Europe/Athens (UTC+02), Europe/Berlin (UTC+01), Europe/Brussels (UTC+01), Europe/Copenhagen (UTC+01), Europe/London (UTC+00), Europe/Madrid (UTC+01), Europe/Moscow (UTC+04), Europe/Paris (UTC+01), Europe/Prague (UTC+01), Europe/Rome (UTC+01), Europe/Warsaw (UTC+01), Pacific/Guam (UTC+10), Pacific/Honolulu (UTC-10), UTC (UTC-00)]"
},
"propTimeSource": {
"value": "SNTP Only",
"datatype": "mav.flexi.web.pages.SystemConfig$TimeSourceEnum",
"accepted": "[SNTP Only, GPS Only, SNTP + Soft GPS]"
},
"propSNTPServer": {
"value": "1.uk.pool.ntp.org",
"datatype": "java.lang.String"
},
"propSNTPIntervalMinutes": {
"value": "public long mav.flexi.web.pages.SystemConfig$DeviceConfig.propSNTPIntervalMinutes",
"datatype": "long",
"range": {
"minimum": "1.000000",
"maximum": "99999.000000"
}
},
"propOutputsMaxTimeSyncAge": {
"value": "24 hours",
"datatype": "mav.flexi.web.pages.SystemConfig$HoursEnum",
"accepted": "[NO LIMIT, 01 hours, 02 hours, 03 hours, 04 hours, 05 hours, 06 hours, 07 hours, 08 hours, 09 hours, 10 hours, 11 hours, 12 hours, 18 hours, 24 hours, 36 hours, 48 hours, 72 hours, 96 hours]"
},
"propReservedMemoryMegabytes": {
"value": "public long mav.flexi.web.pages.SystemConfig$DeviceConfig.propReservedMemoryMegabytes",
"datatype": "long",
"range": {
"minimum": "1000.000000",
"maximum": "128000.000000"
}
},
"propClientTrustStorePassword": {
"value": "",
"datatype": "java.lang.String"
},
"propFlexiScriptPath": {
"value": "/home/mav/FlexiAI/bin/FlexiAI",
"datatype": "java.lang.String"
},
"propVideo0Tee": {
"value": "video0tee",
"datatype": "java.lang.String"
},
"propVideo1Tee": {
"value": "video1tee",
"datatype": "java.lang.String"
},
"propRestartCameraModulesOnBoot": {
"value": "true",
"datatype": "boolean"
}
}