22 Commits

Author SHA1 Message Date
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 1117 additions and 991 deletions

43
AiQ_GUI.Designer.cs generated
View File

@@ -144,6 +144,8 @@ namespace AiQ_GUI
BtnZoom8000 = new Button(); BtnZoom8000 = new Button();
BtnZoomWide = new Button(); BtnZoomWide = new Button();
TabSettings = new TabPage(); TabSettings = new TabPage();
BtnUploadWonwooSetIR = new Button();
BtnUploadWonwooSetOV = new Button();
BtnAdminStart = new Button(); BtnAdminStart = new Button();
BtnFirewall = new Button(); BtnFirewall = new Button();
TabImages = new TabPage(); TabImages = new TabPage();
@@ -273,6 +275,7 @@ namespace AiQ_GUI
CbBxUserName.Items.AddRange(new object[] { "Guest", "Clive", "Conor", "Charlie", "Henry", "Sam C", "Sam L", "Simon", "Sophie", "Toby", "Tom" }); 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.Location = new Point(10, 262);
CbBxUserName.Margin = new Padding(4, 3, 4, 3); CbBxUserName.Margin = new Padding(4, 3, 4, 3);
CbBxUserName.MaxDropDownItems = 20;
CbBxUserName.Name = "CbBxUserName"; CbBxUserName.Name = "CbBxUserName";
CbBxUserName.Size = new Size(233, 25); CbBxUserName.Size = new Size(233, 25);
CbBxUserName.TabIndex = 159; CbBxUserName.TabIndex = 159;
@@ -1145,7 +1148,7 @@ namespace AiQ_GUI
TxBxFlexiVer.Name = "TxBxFlexiVer"; TxBxFlexiVer.Name = "TxBxFlexiVer";
TxBxFlexiVer.Size = new Size(205, 25); TxBxFlexiVer.Size = new Size(205, 25);
TxBxFlexiVer.TabIndex = 162; TxBxFlexiVer.TabIndex = 162;
TxBxFlexiVer.Text = "1.6.4"; TxBxFlexiVer.Text = "1.7.1";
TxBxFlexiVer.TextAlign = HorizontalAlignment.Center; TxBxFlexiVer.TextAlign = HorizontalAlignment.Center;
TxBxFlexiVer.TextChanged += TxBxFlexiVer_TextChanged; TxBxFlexiVer.TextChanged += TxBxFlexiVer_TextChanged;
// //
@@ -1835,6 +1838,8 @@ namespace AiQ_GUI
// TabSettings // TabSettings
// //
TabSettings.BackColor = Color.FromArgb(39, 37, 55); TabSettings.BackColor = Color.FromArgb(39, 37, 55);
TabSettings.Controls.Add(BtnUploadWonwooSetIR);
TabSettings.Controls.Add(BtnUploadWonwooSetOV);
TabSettings.Controls.Add(BtnAdminStart); TabSettings.Controls.Add(BtnAdminStart);
TabSettings.Controls.Add(BtnFirewall); TabSettings.Controls.Add(BtnFirewall);
TabSettings.Controls.Add(PanelSettings); TabSettings.Controls.Add(PanelSettings);
@@ -1845,6 +1850,40 @@ namespace AiQ_GUI
TabSettings.TabIndex = 3; TabSettings.TabIndex = 3;
TabSettings.Text = "Settings"; 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(181, 49);
BtnUploadWonwooSetOV.TabIndex = 243;
BtnUploadWonwooSetOV.Text = "Upload Wonwoo Settings OV";
BtnUploadWonwooSetOV.UseVisualStyleBackColor = false;
BtnUploadWonwooSetOV.Click += UploadWonwooSetOV_Click;
//
// BtnAdminStart // BtnAdminStart
// //
BtnAdminStart.BackColor = Color.FromArgb(70, 65, 80); BtnAdminStart.BackColor = Color.FromArgb(70, 65, 80);
@@ -2210,5 +2249,7 @@ namespace AiQ_GUI
private Button BtnAdminStart; private Button BtnAdminStart;
private Button SetGodModeAll; private Button SetGodModeAll;
private Button BtnFactoryDefault; private Button BtnFactoryDefault;
private Button BtnUploadWonwooSetOV;
private Button BtnUploadWonwooSetIR;
} }
} }

View File

@@ -1,13 +1,18 @@
using AiQ_GUI.Microsoft; using Newtonsoft.Json;
using Newtonsoft.Json;
using System.ComponentModel; using System.ComponentModel;
using System.Data.OleDb;
using System.Diagnostics; using System.Diagnostics;
using System.Numerics;
using System.Reflection; using System.Reflection;
namespace AiQ_GUI namespace AiQ_GUI
{ {
public enum Level
{
ERROR,
WARNING,
LOG,
Success
}
public partial class MainForm : Form public partial class MainForm : Form
{ {
// Classes // Classes
@@ -28,8 +33,6 @@ namespace AiQ_GUI
[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
public static MainForm? Instance { get; private set; } public static MainForm? Instance { get; private set; }
// For Access Stats
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 MainForm() public MainForm()
{ {
@@ -39,20 +42,18 @@ namespace AiQ_GUI
private async void AiQGUI_Load(object sender, EventArgs e) private async void AiQGUI_Load(object sender, EventArgs e)
{ {
Stopwatch stopwatch = Stopwatch.StartNew();
Task? closeProcessesTask = Windows.CloseProcesses(); // Fire and forget closing other apps Task? closeProcessesTask = Windows.CloseProcesses(); // Fire and forget closing other apps
//Windows.UpdateFirewall(); Windows.UpdateFirewall();
Task UniDataTask = Task.Run(() => Access.ReadUniData()); // Get universal data Task UniDataTask = Task.Run(() => Access.ReadUniData()); // Get universal data
Task<LocalDataStore> LDSWAIT = Task.Run(() => LDS.GetLDS()); // Get and deserialise LDS.json Task<LocalDataStore> LDSWAIT = Task.Run(() => LDS.GetLDS()); // Get and deserialise LDS.json
Task<string> guiVerTask = Task.Run(() => GUIUpdate.FindGUIVersion()); // Get GUI Version Task<string> guiVerTask = Task.Run(() => GUIUpdate.FindGUIVersion()); // Get GUI Version
Network.Initialize("admin", "admin"); // Initialise HTTP client Network.Initialize("admin", "admin"); // Initialise HTTP client with basic auth creds.
if (await Network.PingIP("8.8.8.8")) // Ping to check if we're online if (await Network.PingIP("8.8.8.8")) // Ping to check if we're online
{ {
if (!GoogleAPI.Setup()) if (!GoogleAPI.Setup())
AddToActionsList("Cannot setup Google API"); AddToActionsList("Cannot setup Google API", Level.WARNING);
} }
else else
{ {
@@ -74,9 +75,10 @@ namespace AiQ_GUI
// Load local data store // Load local data store
localDataStore = await LDSWAIT; localDataStore = await LDSWAIT;
Logging.LogMessage("Opening GUI"); // Done after LDS to make sure directory exists. Logging.LogMessage("Opening GUI"); // Done after LDS to make sure directory exists.
if (localDataStore == null) if (localDataStore == null)
{ {
AddToActionsList("Could not deserialise LDS.json please help!"); AddToActionsList("Could not deserialise LDS.json please help!", Level.WARNING);
return; return;
} }
@@ -92,9 +94,6 @@ namespace AiQ_GUI
await CheckHWOnline; await CheckHWOnline;
Flags.Start = false; Flags.Start = false;
stopwatch.Stop();
Debug.WriteLine("RunTime " + stopwatch.Elapsed.ToString(@"hh\:mm\:ss\.ff"));
} }
private void PopulateUIWithLDS(LocalDataStore lds) private void PopulateUIWithLDS(LocalDataStore lds)
@@ -140,20 +139,17 @@ namespace AiQ_GUI
string LEDreply = await FlexiAPI.APIHTTPLED(CamOnTest.IP, LEDPOWER.SAFE); // Set LED's to safe (0x0E) to help with eye safety and trim check. string LEDreply = await FlexiAPI.APIHTTPLED(CamOnTest.IP, LEDPOWER.SAFE); // Set LED's to safe (0x0E) to help with eye safety and trim check.
if (!LEDreply.Contains("Power levels set successfully")) if (!LEDreply.Contains("Power levels set successfully"))
AddToActionsList($"LED level could not be set: {LEDreply}"); AddToActionsList($"LED level could not be set: {LEDreply}", Level.ERROR);
} }
else if (!await TestTube.CheckInTestTube(CamOnTest.IP)) // Sets LED's to medium power after checking it is in the test tube else if (!await TestTube.CheckInTestTube(CamOnTest.IP)) // Sets LED's to medium power after checking it is in the test tube
{
await TestFailed(BtnStartTest, "Camera not in test tube"); await TestFailed(BtnStartTest, "Camera not in test tube");
}
Task VisCheck = Helper.VisualCheck(BtnStartTest); Task VisCheck = Helper.VisualCheck(BtnStartTest);
if (!await FlexiAPI.ZoomModules("1F40", CamOnTest.IP)) // Zoom to 8000 (1F40h) at the same time. if (!await CameraModules.ZoomModules("1F40", CamOnTest.IP)) // Zoom to 8000 (1F40h) at the same time.
await TestFailed(BtnStartTest, "Could not zoom modules to 8000"); await TestFailed(BtnStartTest, "Could not zoom modules to 8000");
if (!await CameraModules.SetZoomLockOn(CamOnTest.IP))
if (!await FlexiAPI.SetZoomLockOn(CamOnTest.IP))
Helper.RestartApp(); Helper.RestartApp();
await Task.Delay(1000); // Without sleep it kept failing the factory reset as camera modules were not ready yet await Task.Delay(1000); // Without sleep it kept failing the factory reset as camera modules were not ready yet
@@ -162,7 +158,7 @@ namespace AiQ_GUI
string VISCAReply = await FlexiAPI.APIHTTPVISCA(CamOnTest.IP, "8101043903FF", true); // Manual mode to be able to manipulate the SIG settings. string VISCAReply = await FlexiAPI.APIHTTPVISCA(CamOnTest.IP, "8101043903FF", true); // Manual mode to be able to manipulate the SIG settings.
if (VISCAReply != "9041FF9051FF") if (VISCAReply != "9041FF9051FF")
AddToActionsList("Couldn't set to manual mode"); AddToActionsList("Couldn't set to manual mode",Level.ERROR);
await CameraModules.SetSIG(CbBxShutter, CbBxIris, CbBxGain, CamOnTest.IP); // Set SIG according to the drop downs in settings for a good picture ready for image check await CameraModules.SetSIG(CbBxShutter, CbBxIris, CbBxGain, CamOnTest.IP); // Set SIG according to the drop downs in settings for a good picture ready for image check
await ImageProcessing.ImageCheck(PicBxOV, PicBxIRF2, PicBxIRF16, LblIRImageF2, LblIRImageF16, CamOnTest); // Populates the picture boxes and checks iris changes await ImageProcessing.ImageCheck(PicBxOV, PicBxIRF2, PicBxIRF16, LblIRImageF2, LblIRImageF16, CamOnTest); // Populates the picture boxes and checks iris changes
@@ -174,6 +170,9 @@ namespace AiQ_GUI
// TODO - Force expire sighting. // TODO - Force expire sighting.
Task Wait = Task.Delay(5000); // Wait for 5 seconds to allow the camera to zoom in, set settings and capture some plates before auto trim Task Wait = Task.Delay(5000); // Wait for 5 seconds to allow the camera to zoom in, set settings and capture some plates before auto trim
if (CameraAccessInfo.HardwareExtras.Contains("GPS")) // Check GPS if the hardware has it
await FlexiAPI.GPSFix(CamOnTest.IP);
// While waiting do the SSH tasks. // While waiting do the SSH tasks.
sshData = SSH.CollectSSHData(CamOnTest.IP); // SSH into camera to get Vaxtor packages, filesystem size and if tailscale is installed. sshData = SSH.CollectSSHData(CamOnTest.IP); // SSH into camera to get Vaxtor packages, filesystem size and if tailscale is installed.
await SSH.CheckFSSize(CamOnTest.IP, LblFilesystemSize, sshData); // Check Filesystem size is between 100GB & 150GB await SSH.CheckFSSize(CamOnTest.IP, LblFilesystemSize, sshData); // Check Filesystem size is between 100GB & 150GB
@@ -193,10 +192,8 @@ namespace AiQ_GUI
await Wait; // Finished to 5s wait await Wait; // Finished to 5s wait
await FlexiAPI.SetTrim(CamOnTest.IP, LblTestTubePing.Text); // Auto trims the cameras, some plates should have been captured in the meantime await FlexiAPI.SetTrim(CamOnTest.IP, LblTestTubePing.Text); // Auto trims the cameras, some plates should have been captured in the meantime
if (!await FlexiAPI.ZoomModules("0000", CamOnTest.IP)) // Zoom to full wide if (!await CameraModules.ZoomModules("0000", CamOnTest.IP)) // Zoom to full wide
{
await TestFailed(BtnStartTest, "Could not zoom modules to full wide"); await TestFailed(BtnStartTest, "Could not zoom modules to full wide");
}
await Task.Delay(1000); // Wait to be sure cameras are zoomed out. await Task.Delay(1000); // Wait to be sure cameras are zoomed out.
@@ -207,7 +204,7 @@ namespace AiQ_GUI
string LEDreply = await FlexiAPI.APIHTTPLED(CamOnTest.IP, LEDPOWER.MID); // Set LED's to medium (0x30) string LEDreply = await FlexiAPI.APIHTTPLED(CamOnTest.IP, LEDPOWER.MID); // Set LED's to medium (0x30)
if (!LEDreply.Contains("Power levels set successfully")) if (!LEDreply.Contains("Power levels set successfully"))
AddToActionsList($"LED level could not be set: {LEDreply}"); AddToActionsList($"LED level could not be set: {LEDreply}", Level.ERROR);
} }
await FlexiAPI.SetVaxtorMinMaxPlate(CamOnTest.IP); await FlexiAPI.SetVaxtorMinMaxPlate(CamOnTest.IP);
@@ -217,8 +214,8 @@ namespace AiQ_GUI
await CheckDiagsAPIPt2(); // For only final test parts await CheckDiagsAPIPt2(); // For only final test parts
// Check module has gone to default config // Check module has gone to default config
CameraModules.CheckCamModule(DiagsAPI.IRmodule, LblIRModule); // IR CameraModules.CheckCamModule(DiagsAPI.IRmodule, LblIRModule, CamOnTest); // IR
CameraModules.CheckCamModule(DiagsAPI.OVmodule, LblOVModule); // OV CameraModules.CheckCamModule(DiagsAPI.OVmodule, LblOVModule, CamOnTest); // OV
// Check voltage and current are OK. // Check voltage and current are OK.
LED.CheckLEDs(DiagsAPI.LedVoltage, LblLEDV, "V", CameraAccessInfo.LED_V); // Voltage LED.CheckLEDs(DiagsAPI.LedVoltage, LblLEDV, "V", CameraAccessInfo.LED_V); // Voltage
@@ -229,76 +226,11 @@ namespace AiQ_GUI
// If there are any actions identified then fail the test. // If there are any actions identified then fail the test.
// If any labels are red then fail. Only labels in panel so can foreach on labels not controls // If any labels are red then fail. Only labels in panel so can foreach on labels not controls
if (RhTxBxActions.Text.Length > 2 || PnlLbls.Controls.OfType<Label>().Any(c => c.ForeColor == Color.Red) == true) if (RhTxBxActions.Text.Length > 2 || PnlLbls.Controls.OfType<Label>().Any(c => c.ForeColor == Color.Red) == true)
{
await TestFailed(BtnStartTest, "Diagnostic Failure");// If approved then pass otherwise GUI would have restarted before getting to TestPassed. await TestFailed(BtnStartTest, "Diagnostic Failure");// If approved then pass otherwise GUI would have restarted before getting to TestPassed.
}
await TestPassed(PCTime); await TestPassed(PCTime);
} }
public void Stats(string TypeOfTest)
{
Stats([TypeOfTest]);
}
public void Stats(string[] TypeOfTest)
{
using OleDbConnection conn = new(connString); // Opens connection to Access database
try
{
conn.Open(); // Opens DB
string modelNumber = CbBxCameraType.Text.Substring(0, 6); // Get model number from combobox and make sure it is only 6 characters.
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
// AddToActionsList($"Updated {TypeOfTest} for {modelNumber}");
//else
// AddToActionsList($"No rows found for {modelNumber}");
}
}
catch
{
AddToActionsList("Could not access Access in Google Drive. Is it running?");
return;
}
}
public void StatsDiags(string redDiagLabels, string RhTxBxActionsText, string IsRMA, int RMA)
{
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(CamOnTest?.Model) ? "-" : CamOnTest.Model;
string sql = @"
INSERT INTO DiagsStats ([Date], [Model], [Red Diags Labels], [RhTxBxActions Contents],[IsRMA],[RMA])
VALUES (?, ?, ?, ?, ?, ?)";
using OleDbCommand cmd = new(sql, conn);
// OleDb uses positional parameters — order must match the VALUES list above
cmd.Parameters.Add(new OleDbParameter { OleDbType = OleDbType.Date, Value = DateTime.Now });
cmd.Parameters.Add(new OleDbParameter { OleDbType = OleDbType.VarWChar, Value = model });
cmd.Parameters.Add(new OleDbParameter { OleDbType = OleDbType.VarWChar, Value = redVal });
cmd.Parameters.Add(new OleDbParameter { OleDbType = OleDbType.VarWChar, Value = actVal });
cmd.Parameters.Add(new OleDbParameter { OleDbType = OleDbType.Boolean, Value = IsRMA });
cmd.Parameters.Add(new OleDbParameter { OleDbType = OleDbType.VarWChar, Value = RMA });
cmd.ExecuteNonQuery();
}
private async void BtnPreTest_Click(object sender, EventArgs e) private async void BtnPreTest_Click(object sender, EventArgs e)
{ {
// Show user test has started // Show user test has started
@@ -307,13 +239,13 @@ namespace AiQ_GUI
BtnPreTest.Enabled = BtnStartTest.Enabled = false; // Disable buttons to stop user rnning multiple tests at the same time. BtnPreTest.Enabled = BtnStartTest.Enabled = false; // Disable buttons to stop user rnning multiple tests at the same time.
Logging.LogMessage("Pre Test Started"); Logging.LogMessage("Pre Test Started");
if (!await FlexiAPI.SetZoomLockOn(CamOnTest.IP)) if (!await CameraModules.SetZoomLockOn(CamOnTest.IP))
Helper.RestartApp(); Helper.RestartApp();
string LEDreply = await FlexiAPI.APIHTTPLED(CamOnTest.IP, LEDPOWER.MID); // Set LED's to medium (0x30) string LEDreply = await FlexiAPI.APIHTTPLED(CamOnTest.IP, LEDPOWER.MID); // Set LED's to medium (0x30)
if (!LEDreply.Contains("Power levels set successfully")) if (!LEDreply.Contains("Power levels set successfully"))
AddToActionsList($"LED level could not be set: {LEDreply}"); AddToActionsList($"LED level could not be set: {LEDreply}", Level.ERROR);
await CameraModules.FactoryResetModules(CamOnTest.IP); // Reset both modules and double check await CameraModules.FactoryResetModules(CamOnTest.IP); // Reset both modules and double check
@@ -326,8 +258,8 @@ namespace AiQ_GUI
await CheckDiagsAPIPt1(); await CheckDiagsAPIPt1();
// Check module has gone to default config // Check module has gone to default config
CameraModules.CheckCamModule(DiagsAPI.IRmodule, LblIRModule); // IR CameraModules.CheckCamModule(DiagsAPI.IRmodule, LblIRModule, CamOnTest); // IR
CameraModules.CheckCamModule(DiagsAPI.OVmodule, LblOVModule); // OV CameraModules.CheckCamModule(DiagsAPI.OVmodule, LblOVModule, CamOnTest); // OV
// Check voltage and current are OK. // Check voltage and current are OK.
LED.CheckLEDs(DiagsAPI.LedVoltage, LblLEDV, "V", CameraAccessInfo.LED_V); // Voltage LED.CheckLEDs(DiagsAPI.LedVoltage, LblLEDV, "V", CameraAccessInfo.LED_V); // Voltage
@@ -345,7 +277,9 @@ namespace AiQ_GUI
if (await DisplayQuestion($"Would you like to allocate a serial number to this camera?")) if (await DisplayQuestion($"Would you like to allocate a serial number to this camera?"))
await AllocateSerial(); await AllocateSerial();
else if (GoogleAPI.UpdateSpreadSheetRePreTest(CameraAccessInfo.SpreadsheetID, Vers) != "OK") // If rerun might be different values so update SS else if (GoogleAPI.UpdateSpreadSheetRePreTest(CameraAccessInfo.SpreadsheetID, Vers) != "OK") // If rerun might be different values so update SS
AddToActionsList("Failed to write to spreadsheet, please check manually"); AddToActionsList("Failed to write to spreadsheet, please check manually",Level.WARNING);
// else if (Excel.UpdateSpreadSheetPreTest(CameraAccessInfo.SpreadsheetID, Vers, CamOnTest.GetCamDesc(), CamOnTest.Model) != "OK")
// AddToActionsList("Failed to write to spreadsheet, please check manually");
} }
else // No serial or model so allocate one else // No serial or model so allocate one
await AllocateSerial(); await AllocateSerial();
@@ -357,8 +291,6 @@ namespace AiQ_GUI
{ {
await PreTestFailed("Diagnostic Failure"); await PreTestFailed("Diagnostic Failure");
} }
} }
// ***** Pass/Fails ***** // ***** Pass/Fails *****
@@ -368,7 +300,7 @@ namespace AiQ_GUI
string err = GoogleAPI.UpdateSpreadSheetFinalTest(CameraAccessInfo.SpreadsheetID, DiagsAPI, sshData, CamOnTest.RMANum); string err = GoogleAPI.UpdateSpreadSheetFinalTest(CameraAccessInfo.SpreadsheetID, DiagsAPI, sshData, CamOnTest.RMANum);
if (err != string.Empty) // If there is an error message, display it if (err != string.Empty) // If there is an error message, display it
AddToActionsList("Failed to write to spreadsheet " + err); AddToActionsList("Failed to write to spreadsheet " + err, Level.ERROR);
// Purge camera of all reads // Purge camera of all reads
await FlexiAPI.APIHTTPRequest("/api/purge-all", CamOnTest.IP); await FlexiAPI.APIHTTPRequest("/api/purge-all", CamOnTest.IP);
@@ -377,14 +309,14 @@ namespace AiQ_GUI
{ {
// Turn off God mode // Turn off God mode
string[,] GOD_JSON = { { "propGodMode", "false" } }; string[,] GOD_JSON = { { "propGodMode", "false" } };
string IntConf = await FlexiAPI.HTTP_Update("Internal Config", CamOnTest.IP, GOD_JSON); string IntConf = await FlexiAPI.HTTP_Update("GLOBAL--FlexiApplication", CamOnTest.IP, GOD_JSON);
if (!IntConf.Contains("\"propGodMode\": {\"value\": \"false\", \"datatype\": \"boolean\"},")) if (!IntConf.Contains("\"propGodMode\": {\"value\": \"false\", \"datatype\": \"boolean\"},"))
AddToActionsList("Could not turn off God mode"); AddToActionsList("Could not turn off God mode", Level.WARNING);
Thread Thr211 = new(async () => Thread Thr211 = new(async () =>
{ {
if (!await FlexiAPI.ChangeNetwork211(CamOnTest.IP)) // Change camera IP to 192.168.1.211. Waits for camera to come back. if (!await FlexiAPI.ChangeNetwork211(CamOnTest.IP)) // Change camera IP to 192.168.1.211. Waits for camera to come back.
AddToActionsList("Could not find camera at 192.168.1.211. Please check manually"); AddToActionsList("Could not find camera at 192.168.1.211. Please check manually", Level.WARNING);
}); });
Thr211.IsBackground = true; Thr211.IsBackground = true;
Thr211.Start(); Thr211.Start();
@@ -407,15 +339,11 @@ namespace AiQ_GUI
BtnStartTest.Text = "Test Passed"; BtnStartTest.Text = "Test Passed";
PnlQuestion.Visible = false; // just in case this came from an override PnlQuestion.Visible = false; // just in case this came from an override
Logging.LogMessage("Final Test Passed"); Logging.LogMessage("Final Test Passed");
if (CamOnTest.RMANum == 0) // Yap to check if it is not a RMA
{
Stats("Final Tests Passed");
}
else
{
Stats("RMA Final Tests Passed");
}
if (CamOnTest.RMANum == 0) // Yap to check if it is not a RMA
Access.Stats("Final Tests Passed", CamOnTest.Model);
else
Access.Stats("RMA Final Tests Passed", CamOnTest.Model);
} }
public async Task TestFailed(Button Btn, string ErrMssg) public async Task TestFailed(Button Btn, string ErrMssg)
@@ -424,25 +352,20 @@ namespace AiQ_GUI
Btn.BackColor = Color.Maroon; Btn.BackColor = Color.Maroon;
Btn.Text = "Test Failed"; Btn.Text = "Test Failed";
if (!(CamOnTest.RMANum != 0)) // Yap to check if it is not a RMA
Access.Stats(["Final Tests Failed", ErrMssg], CamOnTest.Model);
else
Access.Stats("RMA Final Tests Failed", CamOnTest.Model);
AddToActionsList(ErrMssg, Level.ERROR);
string RedLbls = string.Join(Environment.NewLine, PnlLbls.Controls string RedLbls = string.Join(Environment.NewLine, PnlLbls.Controls
.OfType<Label>() .OfType<Label>()
.Where(lbl => lbl.ForeColor == Color.Red) // Only include red labels .Where(lbl => lbl.ForeColor == Color.Red) // Only include red labels
.Select(lbl => lbl.Text)); // Extract text .Select(lbl => lbl.Text)); // Extract text
string FullFailureValues = RhTxBxActions.Text + Environment.NewLine + RedLbls; string FullFailureValues = RhTxBxActions.Text + Environment.NewLine + RedLbls;
Access.StatsDiags(RedLbls, RhTxBxActions.Text, CamOnTest.Model); // Log to Access database
// Database logging \\
if (!(CamOnTest.RMANum != 0)) // Yap to check if it is not a RMA
{
Stats(["Final Tests Failed", ErrMssg]);
StatsDiags(RedLbls, RhTxBxActions.Text, "FALSE", CamOnTest.RMANum);
}
else
{
Stats("RMA Final Tests Failed");
StatsDiags(RedLbls, RhTxBxActions.Text, "TRUE", CamOnTest.RMANum);
}
if (await DisplayQuestion("Test failed, appeal?" + Environment.NewLine + "See Actions textbox for details.")) if (await DisplayQuestion("Test failed, appeal?" + Environment.NewLine + "See Actions textbox for details."))
{ {
@@ -460,8 +383,6 @@ namespace AiQ_GUI
// Joins the actions box to any red labels to use as a full failed text // Joins the actions box to any red labels to use as a full failed text
Logging.LogErrorMessage(FullFailureValues); Logging.LogErrorMessage(FullFailureValues);
IList<IList<object>> values = GoogleAPI.service.Spreadsheets.Values.Get(GoogleAPI.spreadsheetId_ModelInfo, "'Approval'!A1:A").Execute().Values; IList<IList<object>> values = GoogleAPI.service.Spreadsheets.Values.Get(GoogleAPI.spreadsheetId_ModelInfo, "'Approval'!A1:A").Execute().Values;
if (values?.Count > 0) if (values?.Count > 0)
@@ -474,15 +395,13 @@ namespace AiQ_GUI
List<object> oblistCD = [CamOnTest.Model, FullFailureValues]; List<object> oblistCD = [CamOnTest.Model, FullFailureValues];
GoogleAPI.WriteToSS(oblistCD, "'Approval'!C" + nextRow + ":D" + nextRow, GoogleAPI.spreadsheetId_ModelInfo); GoogleAPI.WriteToSS(oblistCD, "'Approval'!C" + nextRow + ":D" + nextRow, GoogleAPI.spreadsheetId_ModelInfo);
//await Teams.SendMssg(Convert.ToString(nextRow), CbBxUserName.Text); await Teams.SendMssg(Convert.ToString(nextRow), CbBxUserName.Text);
GoogleAPI.EmailApproval(Convert.ToString(nextRow), CbBxUserName.Text);
string Approved = ""; string Approved = "";
while (Approved != "TRUE") while (Approved != "TRUE")
{ {
await Task.Delay(1000); await Task.Delay(1000);
values = GoogleAPI.service.Spreadsheets.Values.Get(GoogleAPI.spreadsheetId_ModelInfo, "'Approval'!B" + nextRow).Execute().Values; values = GoogleAPI.service.Spreadsheets.Values.Get(GoogleAPI.spreadsheetId_ModelInfo, "'Approval'!B" + nextRow).Execute().Values;
if (values?.Count > 0) if (values?.Count > 0)
@@ -514,14 +433,11 @@ namespace AiQ_GUI
PnlQuestion.Visible = true; PnlQuestion.Visible = true;
BtnNo.Visible = false; BtnNo.Visible = false;
Logging.LogMessage("Pre Test Passed"); Logging.LogMessage("Pre Test Passed");
if (CamOnTest.RMANum == 0) // Yap to check if it is not a RMA if (CamOnTest.RMANum == 0) // Yap to check if it is not a RMA
{ Access.Stats("Pre Tests Passed", CamOnTest.Model);
Stats("Pre Tests Passed");
}
else else
{ Access.Stats("RMA Pre Tests Passed", CamOnTest.Model);
Stats("RMA Pre Tests Passed");
}
if (await DisplayQuestion("Test passed, restart?")) if (await DisplayQuestion("Test passed, restart?"))
Helper.RestartApp(); Helper.RestartApp();
@@ -535,24 +451,18 @@ namespace AiQ_GUI
BtnNo.Visible = false; BtnNo.Visible = false;
Logging.LogMessage("Pre Test Failed"); Logging.LogMessage("Pre Test Failed");
if (CamOnTest.RMANum == 0) // Yap to check if it is not a RMA
Access.Stats(["Pre Tests Failed", ErrMssg], CamOnTest.Model);
else
Access.Stats("RMA Pre Tests Failed", CamOnTest.Model);
string RedLbls = string.Join(Environment.NewLine, PnlLbls.Controls string RedLbls = string.Join(Environment.NewLine, PnlLbls.Controls
.OfType<Label>() .OfType<Label>()
.Where(lbl => lbl.ForeColor == Color.Red) // Only include red labels .Where(lbl => lbl.ForeColor == Color.Red) // Only include red labels
.Select(lbl => lbl.Text)); // Extract text .Select(lbl => lbl.Text)); // Extract text
string FullFailureValues = RhTxBxActions.Text + Environment.NewLine + RedLbls; string FullFailureValues = RhTxBxActions.Text + Environment.NewLine + RedLbls;
if (CamOnTest.RMANum == 0) // Yap to check if it is not a RMA Access.StatsDiags(RedLbls, RhTxBxActions.Text, CamOnTest.Model); // Log to Access database
{
Stats(["Pre Tests Failed", ErrMssg]);
StatsDiags(RedLbls, RhTxBxActions.Text, "FALSE", CamOnTest.RMANum);
}
else
{
Stats("RMA Pre Tests Failed");
StatsDiags(RedLbls, RhTxBxActions.Text, "TRUE", CamOnTest.RMANum);
}
// Log to Access database
if (await DisplayQuestion("Test failed, restart?" + Environment.NewLine + "See Actions textbox for details.")) if (await DisplayQuestion("Test failed, restart?" + Environment.NewLine + "See Actions textbox for details."))
Helper.RestartApp(); Helper.RestartApp();
@@ -596,10 +506,13 @@ namespace AiQ_GUI
else if (RegexCache.MACRegex().IsMatch(DiagsAPI.MAC)) // Is a valid MAC, but not NVIDIA else if (RegexCache.MACRegex().IsMatch(DiagsAPI.MAC)) // Is a valid MAC, but not NVIDIA
{ {
lblMac.ForeColor = Color.Red; lblMac.ForeColor = Color.Red;
AddToActionsList($"{DiagsAPI.MAC} not recognised as NVIDIA MAC address"); AddToActionsList($"{DiagsAPI.MAC} not recognised as NVIDIA MAC address", Level.ERROR);
} }
else else
{
lblMac.ForeColor = Color.Red; lblMac.ForeColor = Color.Red;
AddToActionsList($"{DiagsAPI.MAC} not recognised as a MAC address", Level.ERROR);
}
// Check timestamp // Check timestamp
DateTime dateTime = new(1970, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc); DateTime dateTime = new(1970, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc);
@@ -662,7 +575,7 @@ namespace AiQ_GUI
if (DiagsAPI.modelNumber != CamOnTest.Model) if (DiagsAPI.modelNumber != CamOnTest.Model)
{ {
AddToActionsList("Model number in camera doesn't match what has been selected"); AddToActionsList("Model number in camera doesn't match what has been selected", Level.WARNING);
lblModel.ForeColor = Color.Red; lblModel.ForeColor = Color.Red;
} }
else if (!GoogleAPI.CheckWIP(DiagsAPI.serialNumber, CameraAccessInfo.SpreadsheetID)) // Check WIP column in serial number register, if not ticked then RMA else if (!GoogleAPI.CheckWIP(DiagsAPI.serialNumber, CameraAccessInfo.SpreadsheetID)) // Check WIP column in serial number register, if not ticked then RMA
@@ -674,25 +587,21 @@ namespace AiQ_GUI
CamOnTest.RMANum = Convert.ToInt32(await DisplayInput("What is the RMA number?")); CamOnTest.RMANum = Convert.ToInt32(await DisplayInput("What is the RMA number?"));
if (CamOnTest.RMANum == -1) // Means they chose the 'I don't know' option if (CamOnTest.RMANum == -1) // Means they chose the 'I don't know' option
{
await TestFailed(BtnStartTest, "Please get RMA number from operations team before continuing"); await TestFailed(BtnStartTest, "Please get RMA number from operations team before continuing");
} }
}
// Found RMA num and want to verify it with user // Found RMA num and want to verify it with user
else if (!await DisplayQuestion($"Is {CamOnTest.RMANum} the RMA Number?")) // '!' because if its not the right RMA number let the user to it manually else if (!await DisplayQuestion($"Is {CamOnTest.RMANum} the RMA Number?")) // '!' because if its not the right RMA number let the user to it manually
{ {
CamOnTest.RMANum = Convert.ToInt32(await DisplayInput("What is the RMA number?")); CamOnTest.RMANum = Convert.ToInt32(await DisplayInput("What is the RMA number?"));
if (CamOnTest.RMANum == -1) // Means they chose the 'I don't know' option if (CamOnTest.RMANum == -1) // Means they chose the 'I don't know' option
{
await TestFailed(BtnStartTest, "Please get RMA number from operations team before continuing"); await TestFailed(BtnStartTest, "Please get RMA number from operations team before continuing");
} }
} }
} }
}
else else
{ {
AddToActionsList("Camera has not been given a valid serial and model number, suggest you run through pre test again and check serial number register for any issues."); AddToActionsList("Camera has not been given a valid serial and model number, suggest you run through pre test again and check serial number register for any issues.", Level.ERROR);
} }
} }
catch { } catch { }
@@ -725,11 +634,7 @@ namespace AiQ_GUI
double CPUround = Math.Round(DiagsAPI.CPUusage); // Check CPU usage isn't near max double CPUround = Math.Round(DiagsAPI.CPUusage); // Check CPU usage isn't near max
LblCPUusage.Text += CPUround + "%"; LblCPUusage.Text += CPUround + "%";
if (CPUround < 98 && CPUround > 50) if (CPUround <= 50)
{
LblCPUusage.ForeColor = Color.LightGreen;
}
else if (CPUround <= 50)
{ {
LblCPUusage.Text += " Unexpectedly low CPU usage"; LblCPUusage.Text += " Unexpectedly low CPU usage";
LblCPUusage.ForeColor = Color.Red; LblCPUusage.ForeColor = Color.Red;
@@ -739,6 +644,10 @@ namespace AiQ_GUI
LblCPUusage.Text += " Unexpectedly high CPU usage"; LblCPUusage.Text += " Unexpectedly high CPU usage";
LblCPUusage.ForeColor = Color.Red; LblCPUusage.ForeColor = Color.Red;
} }
else
{
LblCPUusage.ForeColor = Color.LightGreen;
}
// Check Vaxtor if it doesn't need or have license OR has and wants one then pass // Check Vaxtor if it doesn't need or have license OR has and wants one then pass
if (CameraAccessInfo.VaxtorLic == false && DiagsAPI.licenses.raptorKeyID == "Not Licensed" || CameraAccessInfo.VaxtorLic == true && DiagsAPI.licenses.raptorKeyID != "Not Licensed") if (CameraAccessInfo.VaxtorLic == false && DiagsAPI.licenses.raptorKeyID == "Not Licensed" || CameraAccessInfo.VaxtorLic == true && DiagsAPI.licenses.raptorKeyID != "Not Licensed")
@@ -752,7 +661,7 @@ namespace AiQ_GUI
if (RegexCache.VaxtorRegex().IsMatch(ProdcutKeyID)) // Means they chose the 'I don't know' option or isn't valid Key ID if (RegexCache.VaxtorRegex().IsMatch(ProdcutKeyID)) // Means they chose the 'I don't know' option or isn't valid Key ID
{ {
Stats("Please Get A Valid Vaxtor Product Key Before Continuing"); Access.Stats("Please Get A Valid Vaxtor Product Key Before Continuing", CamOnTest.Model);
await TestFailed(BtnStartTest, "Please get a valid Vaxtor Product Key before continuing"); await TestFailed(BtnStartTest, "Please get a valid Vaxtor Product Key before continuing");
} }
@@ -771,8 +680,8 @@ namespace AiQ_GUI
lblTrim.Text += "H: " + DiagsAPI.trim[0] + " V: " + DiagsAPI.trim[1]; lblTrim.Text += "H: " + DiagsAPI.trim[0] + " V: " + DiagsAPI.trim[1];
// Offset accounted for in the SetTrim function, so value should be close to 0,0. // Offset accounted for in the SetTrim function, so value should be close to 0,0.
int HMax = 96; // 5% of 1920 each way = ±96 const int HMax = 96; // 5% of 1920 each way = ±96
int VMax = 54; // 5% of 1080 each way = ±54 const int VMax = 54; // 5% of 1080 each way = ±54
if (Math.Abs(DiagsAPI.trim[0]) <= HMax && Math.Abs(DiagsAPI.trim[1]) <= VMax) if (Math.Abs(DiagsAPI.trim[0]) <= HMax && Math.Abs(DiagsAPI.trim[1]) <= VMax)
lblTrim.ForeColor = Color.LightGreen; lblTrim.ForeColor = Color.LightGreen;
@@ -788,22 +697,22 @@ namespace AiQ_GUI
if (NewSerial.Contains("ERROR")) if (NewSerial.Contains("ERROR"))
{ {
AddToActionsList(NewSerial + Environment.NewLine + "Please remove any information that was put into the serial number mistake, change the camera model/serial to 'N/A' and retry the test."); AddToActionsList(NewSerial + Environment.NewLine + "Please remove any information that was put into the serial number mistake, change the camera model/serial to 'N/A' and retry the test.", Level.ERROR);
return; return;
} }
else if (NewSerial == "Last serial number not found") else if (NewSerial == "Last serial number not found")
{ {
AddToActionsList("Last serial number not found in spreadsheet. Please check spreadsheet is in correct format before retrying."); AddToActionsList("Last serial number not found in spreadsheet. Please check spreadsheet is in correct format before retrying.", Level.WARNING);
return; return;
} }
// Set serial and model into camera // Set serial and model into camera
string[,] TEST_JSON = { { "propSerialNumber", NewSerial }, { "propMavModelNumber", CamOnTest.Model } }; string[,] TEST_JSON = { { "propSerialNumber", NewSerial }, { "propMavModelNumber", CamOnTest.Model } };
string JSONResponse = await FlexiAPI.HTTP_Update("Internal Config", CamOnTest.IP, TEST_JSON); string JSONResponse = await FlexiAPI.HTTP_Update("GLOBAL--FlexiApplication", CamOnTest.IP, TEST_JSON);
if (!JSONResponse.Contains(NewSerial) || !JSONResponse.Contains(CamOnTest.Model)) if (!JSONResponse.Contains(NewSerial) || !JSONResponse.Contains(CamOnTest.Model))
{ {
AddToActionsList("Could not set model or serial numbers into camera."); AddToActionsList("Could not set model or serial numbers into camera.",Level.ERROR);
await PreTestFailed("Failed To Set Model Or Serial Number"); await PreTestFailed("Failed To Set Model Or Serial Number");
} }
@@ -814,7 +723,6 @@ namespace AiQ_GUI
lblSerial.Text += DiagsAPI.serialNumber; lblSerial.Text += DiagsAPI.serialNumber;
Printer.ZebraIP = localDataStore.ZebraIP; Printer.ZebraIP = localDataStore.ZebraIP;
Printer.PrintGBLbl(); // Print GB label Printer.PrintGBLbl(); // Print GB label
Printer.PrintSerialLbl(CamOnTest.Model, NewSerial, CameraAccessInfo.Processor); // Print model/serial label Printer.PrintSerialLbl(CamOnTest.Model, NewSerial, CameraAccessInfo.Processor); // Print model/serial label
} }
@@ -858,7 +766,7 @@ namespace AiQ_GUI
private async void BtnFindCams_Click(object sender, EventArgs e) private async void BtnFindCams_Click(object sender, EventArgs e)
{ {
CbBxFoundCams.Text = "Searching"; CbBxFoundCams.Text = "Searching";
BtnFindCams.Enabled = BtnSetAll211.Enabled = BtnSoak.Enabled = BtnSet211.Enabled = BtnSetGodMode.Enabled = BtnUploadBlob.Enabled = SetGodModeAll.Enabled = BtnFactoryDefault.Enabled = false; BtnFindCams.Enabled = BtnSetAll211.Enabled = BtnSoak.Enabled = BtnSet211.Enabled = BtnSetGodMode.Enabled = BtnUploadBlob.Enabled = SetGodModeAll.Enabled = BtnFactoryDefault.Enabled = BtnUploadWonwooSetIR.Enabled = BtnUploadWonwooSetOV.Enabled = false;
BtnSetGodMode.BackColor = BtnUploadBlob.BackColor = BtnFactoryDefault.BackColor = BtnSetAll211.BackColor = BtnColour; BtnSetGodMode.BackColor = BtnUploadBlob.BackColor = BtnFactoryDefault.BackColor = BtnSetAll211.BackColor = BtnColour;
CbBxFoundCams.Items.Clear(); CbBxFoundCams.Items.Clear();
soakCameraList.Clear(); soakCameraList.Clear();
@@ -891,7 +799,9 @@ namespace AiQ_GUI
foreach (Camera soakInfo in soakCameraList) foreach (Camera soakInfo in soakCameraList)
{ {
TabSoak.Controls.Add(SoakTest.MakeNewCheckbox(soakInfo, YLoc)); CheckBox chkBox = SoakTest.MakeNewCheckbox(soakInfo, YLoc);
TabSoak.Controls.Add(chkBox);
soakInfo.CheckBox = chkBox;
YLoc += 24; YLoc += 24;
} }
@@ -956,7 +866,7 @@ namespace AiQ_GUI
if (Vers == null) // If failed to get versions then return. Flexi most likely not running yet. if (Vers == null) // If failed to get versions then return. Flexi most likely not running yet.
{ {
AddToActionsList("Failed to get API from camera. Flexi not running yet or not an AiQ"); AddToActionsList("Failed to get API from camera. Flexi not running yet or not an AiQ", Level.WARNING);
return; return;
} }
@@ -974,7 +884,7 @@ namespace AiQ_GUI
else else
{ {
CbBxFoundCams.BackColor = Color.Red; CbBxFoundCams.BackColor = Color.Red;
BtnSecret.Enabled = BtnOpenWebpage.Enabled = BtnSet211.Enabled = BtnSetGodMode.Enabled = false; BtnSecret.Enabled = BtnOpenWebpage.Enabled = BtnSet211.Enabled = BtnSetGodMode.Enabled = BtnUploadWonwooSetIR.Enabled = BtnUploadWonwooSetOV.Enabled = false;
} }
TestStartConditions(); TestStartConditions();
@@ -1013,26 +923,43 @@ namespace AiQ_GUI
try try
{ {
await FlexiAPI.HTTP_Update("Internal Config", CamOnTest.IP, GOD_JSON); await FlexiAPI.HTTP_Update("GLOBAL--FlexiApplication", CamOnTest.IP, GOD_JSON);
BtnSetGodMode.Text = newGodModeValue == "true" ? "Set God Mode Off" : "Set God Mode On"; BtnSetGodMode.Text = newGodModeValue == "true" ? "Set God Mode Off" : "Set God Mode On";
BtnSetGodMode.BackColor = Color.Green; BtnSetGodMode.BackColor = Color.Green;
} }
catch (Exception ex) catch (Exception ex)
{ {
AddToActionsList($"Failed to set God mode for camera {CamOnTest.IP}. Reason: {ex.Message}"); AddToActionsList($"Failed to set God mode for camera {CamOnTest.IP}. Reason: {ex.Message}", Level.ERROR);
} }
} }
// ***** Helper functions ***** // ***** Helper functions *****
public void AddToActionsList(string Mssg, bool IsErr = true) public void AddToActionsList(string Mssg, Level Lvl = Level.LOG)
{
if (Lvl == Level.ERROR)
{ {
if (IsErr)
Logging.LogErrorMessage(Mssg); Logging.LogErrorMessage(Mssg);
else RhTxBxActions.SelectionColor = Color.IndianRed;
}
else if (Lvl == Level.WARNING)
{
Logging.LogWarningMessage(Mssg);
RhTxBxActions.SelectionColor = Color.Orange;
}
else if (Lvl == Level.LOG)
{
Logging.LogMessage(Mssg); Logging.LogMessage(Mssg);
RhTxBxActions.SelectionColor = Color.White;
}
else if (Lvl == Level.Success)
{
Logging.LogMessage(Mssg);
RhTxBxActions.SelectionColor = Color.LightGreen;
}
RhTxBxActions.AppendText(Mssg + Environment.NewLine); RhTxBxActions.AppendText(Mssg + Environment.NewLine);
RhTxBxActions.SelectionStart = RhTxBxActions.Text.Length; RhTxBxActions.SelectionStart = RhTxBxActions.Text.Length;
RhTxBxActions.SelectionColor = SystemColors.Control;
RhTxBxActions.ScrollToCaret(); RhTxBxActions.ScrollToCaret();
} }
@@ -1059,7 +986,7 @@ namespace AiQ_GUI
if (CbBxFoundCams.BackColor != BtnColour || CbBxFoundCams.Text.Contains("Found")) if (CbBxFoundCams.BackColor != BtnColour || CbBxFoundCams.Text.Contains("Found"))
TSC = SetInvalid("Select camera IP address."); TSC = SetInvalid("Select camera IP address.");
else else
BtnOpenWebpage.Enabled = BtnSet211.Enabled = BtnSetGodMode.Enabled = BtnZoom8000.Enabled = BtnZoomWide.Enabled = true; // Allow user to go to camera webpage & change DHCP/211 BtnOpenWebpage.Enabled = BtnSet211.Enabled = BtnSetGodMode.Enabled = BtnZoom8000.Enabled = BtnZoomWide.Enabled = BtnUploadWonwooSetIR.Enabled = BtnUploadWonwooSetOV.Enabled = true; // Allow user to go to camera webpage & change DHCP/211
// Name chosen // Name chosen
if (CbBxUserName.Text == "Select Operator to Begin Test" || CbBxUserName.Text.Length < 2) if (CbBxUserName.Text == "Select Operator to Begin Test" || CbBxUserName.Text.Length < 2)
@@ -1136,9 +1063,8 @@ namespace AiQ_GUI
PnlInputValue.Visible = true; PnlInputValue.Visible = true;
while (Flags.Done == false) // Waiting for user input in RMA Num panel while (Flags.Done == false) // Waiting for user input in RMA Num panel
{
await Task.Delay(100); // Check every 100ms await Task.Delay(100); // Check every 100ms
}
Flags.Done = false; // Reset flag Flags.Done = false; // Reset flag
PnlInputValue.Visible = false; PnlInputValue.Visible = false;
@@ -1209,7 +1135,7 @@ namespace AiQ_GUI
bool Online = await FlexiAPI.ChangeNetwork211(CamOnTest.IP); // Change camera IP to 192.168.1.211 and hardware reboot. Waits for camera to come back for 50s. bool Online = await FlexiAPI.ChangeNetwork211(CamOnTest.IP); // Change camera IP to 192.168.1.211 and hardware reboot. Waits for camera to come back for 50s.
if (!Online) if (!Online)
AddToActionsList("Could not find camera at 192.168.1.211. Please check manually"); AddToActionsList("Could not find camera at 192.168.1.211. Please check manually", Level.ERROR);
else else
BtnSet211.Text = "Set to DHCP"; BtnSet211.Text = "Set to DHCP";
} }
@@ -1234,11 +1160,11 @@ namespace AiQ_GUI
{ {
Network.Initialize("developer", SCL.DevPass); // Ensure network is initialized to the right camera Network.Initialize("developer", SCL.DevPass); // Ensure network is initialized to the right camera
await FlexiAPI.HTTP_Update("GLOBAL--NetworkConfig", SCL.IP, Network_JSON); await FlexiAPI.HTTP_Update("GLOBAL--NetworkConfig", SCL.IP, Network_JSON);
Instance.AddToActionsList($"Setting 211 for camera {SCL.IP}", false); Instance.AddToActionsList($"Setting 211 for camera {SCL.IP}", Level.LOG);
} }
catch (Exception ex) catch (Exception ex)
{ {
AddToActionsList("Failed to set all cameras to 211. Reason: " + ex.Message); // In case non AiQ's get caught up AddToActionsList("Failed to set all cameras to 211. Reason: " + ex.Message, Level.ERROR); // In case non AiQ's get caught up
} }
} }
@@ -1260,12 +1186,12 @@ namespace AiQ_GUI
try try
{ {
Network.Initialize("developer", SCL.DevPass); // Ensure network is initialized to the right camera Network.Initialize("developer", SCL.DevPass); // Ensure network is initialized to the right camera
string RESP = await FlexiAPI.HTTP_Update("Internal Config", SCL.IP, GOD_JSON); string RESP = await FlexiAPI.HTTP_Update("GLOBAL--FlexiApplication", SCL.IP, GOD_JSON);
Instance.AddToActionsList($"Setting God mode for camera {SCL.IP} to {newGodModeValue}", false); Instance.AddToActionsList($"Setting God mode for camera {SCL.IP} to {newGodModeValue}", Level.LOG);
} }
catch (Exception ex) catch (Exception ex)
{ {
AddToActionsList($"Failed to set God mode for camera {SCL.IP}. Reason: {ex.Message}"); AddToActionsList($"Failed to set God mode for camera {SCL.IP}. Reason: {ex.Message}", Level.ERROR);
} }
} }
@@ -1393,7 +1319,7 @@ namespace AiQ_GUI
} }
catch catch
{ {
AddToActionsList("Failed to communicate with camera, please check network and try again."); AddToActionsList("Failed to communicate with camera, please check network and try again.", Level.ERROR);
return; return;
} }
@@ -1401,12 +1327,16 @@ namespace AiQ_GUI
{ {
VaxtorLicResp = JsonConvert.DeserializeObject<VaxtorLic>(ALresponse); VaxtorLicResp = JsonConvert.DeserializeObject<VaxtorLic>(ALresponse);
if (VaxtorLicResp.protectionKeyId != string.Empty) if (VaxtorLicResp.error != string.Empty)
{
AddToActionsList(VaxtorLicResp.error, Level.ERROR);
}
else if (VaxtorLicResp.protectionKeyId != string.Empty)
{ {
string err = GoogleAPI.UpdateSpreadSheetVaxtor(VaxtorLicResp, Vers.Serial, CamOnTest.Model); string err = GoogleAPI.UpdateSpreadSheetVaxtor(VaxtorLicResp, Vers.Serial, CamOnTest.Model);
if (err != string.Empty) // If there is an error message, display it if (err != string.Empty) // If there is an error message, display it
AddToActionsList("Failed to update Vaxtor spreadsheet: " + err); AddToActionsList("Failed to update Vaxtor spreadsheet: " + err, Level.ERROR);
RhTxBxCode.AppendText("Licencing Success, Key ID: " + VaxtorLicResp.protectionKeyId + Environment.NewLine + "Waiting for files to save"); RhTxBxCode.AppendText("Licencing Success, Key ID: " + VaxtorLicResp.protectionKeyId + Environment.NewLine + "Waiting for files to save");
@@ -1415,18 +1345,14 @@ namespace AiQ_GUI
await DisplayOK("Please wait at least a minute before turning off the unit."); await DisplayOK("Please wait at least a minute before turning off the unit.");
} }
else if (VaxtorLicResp.error != string.Empty)
{
RhTxBxCode.AppendText(VaxtorLicResp.error);
}
else else
{ {
AddToActionsList($"Error reading JSON - {ALresponse}"); AddToActionsList($"Error reading JSON - {ALresponse}", Level.ERROR);
} }
} }
catch catch
{ {
AddToActionsList($"Error reading JSON - {ALresponse}"); AddToActionsList($"Error reading JSON - {ALresponse}", Level.ERROR);
return; return;
} }
} }
@@ -1548,7 +1474,7 @@ namespace AiQ_GUI
private async void BtnZoomWide_Click(object sender, EventArgs e) private async void BtnZoomWide_Click(object sender, EventArgs e)
{ {
if (await FlexiAPI.ZoomModules("0000", CamOnTest.IP)) if (await CameraModules.ZoomModules("0000", CamOnTest.IP))
BtnZoomWide.BackColor = Color.Green; BtnZoomWide.BackColor = Color.Green;
else else
BtnZoomWide.BackColor = Color.Red; BtnZoomWide.BackColor = Color.Red;
@@ -1558,7 +1484,7 @@ namespace AiQ_GUI
private async void BtnZoom8000_Click(object sender, EventArgs e) private async void BtnZoom8000_Click(object sender, EventArgs e)
{ {
if (await FlexiAPI.ZoomModules("1F40", CamOnTest.IP)) if (await CameraModules.ZoomModules("1F40", CamOnTest.IP))
BtnZoom8000.BackColor = Color.Green; BtnZoom8000.BackColor = Color.Green;
else else
BtnZoom8000.BackColor = Color.Red; BtnZoom8000.BackColor = Color.Red;
@@ -1577,6 +1503,16 @@ namespace AiQ_GUI
Process.Start(psi); Process.Start(psi);
} }
private async void UploadWonwooSetOV_Click(object sender, EventArgs e)
{
await FlexiAPI.UploadWonwooSet(CbBxFoundCams.Text, false); // false = Colour
}
private async void UploadWonwooSetIR_Click(object sender, EventArgs e)
{
await FlexiAPI.UploadWonwooSet(CbBxFoundCams.Text, true); // true = Infrared
}
private void TxBxSerialPrint_Click(object sender, EventArgs e) private void TxBxSerialPrint_Click(object sender, EventArgs e)
{ {
if (TxBxSerialPrint.Text == "K ") // If at default then remove the dashes ready for user to put in number if (TxBxSerialPrint.Text == "K ") // If at default then remove the dashes ready for user to put in number
@@ -1652,8 +1588,8 @@ namespace AiQ_GUI
CancellationTokenSource cts = new(); CancellationTokenSource cts = new();
soakCtsList.Add(cts); soakCtsList.Add(cts);
soakTasks.Add(SoakTest.StartSoak(SCL, cts.Token)); soakTasks.Add(SoakTest.StartSoak(SCL, cts));
await Task.Delay(5000); await Task.Delay(10000);
} }
} }
else else
@@ -1664,14 +1600,51 @@ namespace AiQ_GUI
cts.Cancel(); cts.Cancel();
soakCtsList.Clear(); soakCtsList.Clear();
soakTasks.Clear(); soakTasks.Clear();
int i = soakCameraList.Count + 1; // Add 1 for 211 itself staying in the list
foreach (Camera SCL in soakCameraList) // Reset all cameras that were being soaked to default module settings string[,] Network_JSON = { { "propDHCP", "false" }, { "propHost", "192.168.1.211" }, { "propNetmask", "255.255.255.0" }, { "propGateway", "192.168.1.1" } };
string[,] GOD_JSON = { { "propGodMode", "false" } };
foreach (Camera SCL in soakCameraList.Where(c => c.IsChecked)) // only checked cameras
{ {
if (!SCL.IsChecked) if (!SCL.IsChecked)
continue; continue;
try
{
AddToActionsList($"Setting 211 & God Mode off for camera {SCL.IP}", Level.LOG);
Network.Initialize("developer", SCL.DevPass); // Ensure network is initialized to the right camera
await CameraModules.FactoryResetModules(SCL.IP); // Reset camera modules
Network.Initialize("developer", SCL.DevPass); // Ensure network is initialized to the right camera, cannot be done in soak test finally becuase of this. string GOD = await FlexiAPI.HTTP_Update("GLOBAL--FlexiApplication", SCL.IP, GOD_JSON);
await CameraModules.FactoryResetModules(SCL.IP); if (GOD.Contains("Error"))
throw new Exception("Could not set God mode off");
// Update GLOBAL--NetworkConfig with fixed IP and turn off DHCP
await FlexiAPI.HTTP_Update("GLOBAL--NetworkConfig", SCL.IP, Network_JSON);
i--; // Decriment count becuase they will stack into 211
}
catch (Exception ex)
{
AddToActionsList("Failed to set all cameras to 211 and god mode off. Reason: " + ex.Message, Level.ERROR); // In case non AiQ's get caught up
}
if (SCL.CheckBox.ForeColor == Color.Red)
{
DialogResult DR = MessageBox.Show($"CAMERA {SCL.Serial} {SCL.Model} DID NOT PASS SOAK TEST. Check soak test report to see error. Do you want to open the test report", "SOAK TEST FAILED", MessageBoxButtons.YesNo, MessageBoxIcon.Warning);
if (DR == DialogResult.Yes)
File.Open(SCL.TestReportLoc, FileMode.Open);
}
}
await Task.Delay(5000); // Wait for 5 seconds to allow the camera to restart
IList<string> FoundCams = await Network.SearchForCams(); // Have to check via broadcast becuase Ping sometimes fails across subnets
if ((FoundCams.Count == i && FoundCams.Contains("192.168.1.211")) || FoundCams.Count == 1)
{
AddToActionsList("All cameras successfully changed to 211.", Level.Success);
}
else
{
AddToActionsList($"Some cameras failed: Found {FoundCams.Count}, expected {i}.", Level.ERROR);
} }
} }
} }
@@ -1719,59 +1692,7 @@ namespace AiQ_GUI
private async void BtnUploadBlob_Click(object sender, EventArgs e) private async void BtnUploadBlob_Click(object sender, EventArgs e)
{ {
BtnUploadBlob.BackColor = BtnColour; BtnUploadBlob.BackColor = BtnColour;
const string networkFolderPath = @"G:\Shared drives\MAV Production\MAV_146_AiQ_Mk2\Flexi"; FlexiAPI.UploadBlob(soakCameraList);
string fileToUpload = null;
if (await 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)
{
AddToActionsList("No .blob file found in the directory.", false);
return;
}
}
else
{
using OpenFileDialog openFileDialog1 = new()
{
InitialDirectory = networkFolderPath,
Filter = "Blob files (*.blob)|*.blob",
FilterIndex = 0
};
if (openFileDialog1.ShowDialog() == DialogResult.OK)
fileToUpload = openFileDialog1.FileName;
else
{
AddToActionsList("File selection cancelled.", false);
return;
}
}
string fileName = Path.GetFileName(fileToUpload);
AddToActionsList($"Selected file to upload: {fileToUpload}", false);
foreach (Camera? cam in soakCameraList.Where(c => c.IsChecked))
{
string apiUrl = $"http://{cam.IP}/upload/software-update/2";
Network.Initialize("developer", cam.DevPass);
AddToActionsList($"Uploading to {cam.IP}...", false);
string result = await FlexiAPI.SendBlobFileUpload(apiUrl, fileToUpload, fileName);
// Retry once on transient errors
if (result.Contains("Error while copying content to a stream") || result.Contains("Timeout"))
{
AddToActionsList($"Retrying upload to {cam.IP}...", false);
await Task.Delay(1000);
result = await FlexiAPI.SendBlobFileUpload(apiUrl, fileToUpload, fileName);
}
AddToActionsList($"Upload result for {cam.IP}: {result}", false);
await Task.Delay(500);
}
BtnUploadBlob.BackColor = Color.Green; BtnUploadBlob.BackColor = Color.Green;
} }
@@ -1790,104 +1711,38 @@ namespace AiQ_GUI
BtnFactoryDefault.BackColor = Color.Green; BtnFactoryDefault.BackColor = Color.Green;
} }
// Constants
const double RealPlateWidthMeters = 0.52; // UK standard plate width
// const double FocalLengthPixels = (50 * 1280) / 14.111224; // focal mm * pixel width / sensor width for IQ
const double FocalLengthPixels = (35 * 1920) / 6.95; // focal mm * pixel width / sensor width for AiQ
const double FrameRate = 25.0; // Frames per second
public class FrameData
{
public long FrameID;
public int PlatePosX;
public int PlatePosY;
public int PlateWidthPixels;
}
public double EstimateSpeed(List<FrameData> frames)
{
double TimeElapsed = 0;
int frameCount = frames.Count;
for (int i = 1; i < frameCount; i++)
{
double time = (frames[i].FrameID - frames[i - 1].FrameID) / FrameRate;
TimeElapsed += time;
}
double FarDist = (FocalLengthPixels * RealPlateWidthMeters) / frames[0].PlateWidthPixels;
double CloseDist = (FocalLengthPixels * RealPlateWidthMeters) / frames[frameCount - 1].PlateWidthPixels;
double speedMph = (Math.Abs(FarDist - CloseDist) / TimeElapsed) * 2.237;
return speedMph;
}
// ***** Test & Debug ***** // ***** Test & Debug *****
private async void BtnTest_Click(object sender, EventArgs e) private void BtnTest_Click(object sender, EventArgs e)
{ {
Stopwatch stopWatchTest = Stopwatch.StartNew(); Stopwatch stopWatchTest = Stopwatch.StartNew();
//string[,] GOD_JSON = { { "propURI", "rtsp://ADMIN:1234@192.168.0.49:554/live/main" } };
//string str = FlexiAPI.BuildJsonUpdate(GOD_JSON, "CameraA");
//AddToActionsList(str);
// To estimate speed
//List<FrameData> frames = new List<FrameData>
//{
// new FrameData { FrameID = 60192555, PlatePosX = 1172, PlatePosY = 393, PlateWidthPixels = 108 },
// new FrameData { FrameID = 60192556, PlatePosX = 1103, PlatePosY = 361, PlateWidthPixels = 105 },
// new FrameData { FrameID = 60192558, PlatePosX = 983, PlatePosY = 331, PlateWidthPixels = 99 },
// new FrameData { FrameID = 60192559, PlatePosX = 930, PlatePosY = 301, PlateWidthPixels = 95 },
// new FrameData { FrameID = 60192560, PlatePosX = 880, PlatePosY = 304, PlateWidthPixels = 93 },
// new FrameData { FrameID = 60192561, PlatePosX = 834, PlatePosY = 278, PlateWidthPixels = 89 },
// new FrameData { FrameID = 60192562, PlatePosX = 792, PlatePosY = 229, PlateWidthPixels = 87 },
// new FrameData { FrameID = 60192563, PlatePosX = 752, PlatePosY = 208, PlateWidthPixels = 85 },
// new FrameData { FrameID = 60192565, PlatePosX = 680, PlatePosY = 187, PlateWidthPixels = 81 },
// new FrameData { FrameID = 60192566, PlatePosX = 648, PlatePosY = 167, PlateWidthPixels = 78 },
// new FrameData { FrameID = 60192567, PlatePosX = 617, PlatePosY = 149, PlateWidthPixels = 76 },
// new FrameData { FrameID = 60192568, PlatePosX = 588, PlatePosY = 132, PlateWidthPixels = 75 },
// new FrameData { FrameID = 60192569, PlatePosX = 561, PlatePosY = 100, PlateWidthPixels = 70 },
// new FrameData { FrameID = 60192570, PlatePosX = 535, PlatePosY = 85, PlateWidthPixels = 72 },
// new FrameData { FrameID = 60192572, PlatePosX = 488, PlatePosY = 70, PlateWidthPixels = 69 },
// new FrameData { FrameID = 60192573, PlatePosX = 466, PlatePosY = 55, PlateWidthPixels = 67 }
//};
//double Spd = EstimateSpeed(frames);
//AddToActionsList("Estimated Speed: " + Spd.ToString("F2") + " MPH");
await FlexiAPI.SetVaxtorMinMaxPlate(CamOnTest.IP);
//StatsExcel excelExporter = new(); //StatsExcel excelExporter = new();
//excelExporter.ExportDatabaseToExcel(); //excelExporter.ExportDatabaseToExcel();
//FakeCamera fakeCamera = new FakeCamera(80); // Create an instance of FakeCamera // /api/config-ids - For getting all available config IDs
////CamOnTest.IP = CbBxFoundCams.Text; // Make every log file in the soak log directory into a soak test report PDF
//_ = fakeCamera.StartAsync(CAMTYPE.GOOD).ContinueWith(task => //var files = from file in Directory.EnumerateFiles("C:\\ProgramData\\MAV\\AiQ_GUI") select file;
//{
// //Network.Initialize("developer", "Pass123");
// if (task.IsFaulted) //foreach (var file in files)
//{ //{
// AddToActionsList("Error starting FakeCamera: " + task.Exception?.Message); // if (file.Contains("SoakLog"))
// {
// // File name: SoakLog_{Serial}_{Model}.log
// string[] parts = file.Split('_', '.').Select(p => p.Trim()).ToArray();
// Camera NewCam = new()
// {
// Model = parts[3],
// Serial = parts[2],
// };
// PDF.CreateSoakTestReport(NewCam, "SoakTestRig", DateTime.Now, file);
// } // }
// else
// {
// AddToActionsList($"FakeCamera started successfully. IP: {fakeCamera}", false);
//} //}
//});
//await Task.Delay(3000); // Wait for server to start
//CbBxFoundCams.Text = "localhost"; // Should force update in creds an network reinit
//CmBoFoundCams_TextChanged(sender, e);
//CbBxCameraType.SelectedIndex = CbBxCameraType.Items.Count - 1; // Selects AB12CD as model number
//await Task.Delay(3000); // Wait for server to start
//BtnStartTest_Click(sender, e);
stopWatchTest.Stop(); stopWatchTest.Stop();
//AddToActionsList("RunTime " + stopWatchTest.Elapsed.ToString(@"hh\:mm\:ss\.ff")); AddToActionsList("RunTime " + stopWatchTest.Elapsed.ToString(@"hh\:mm\:ss\.ff"), Level.LOG);
} }
} }
} }

View File

@@ -124,7 +124,7 @@
<value>159, 17</value> <value>159, 17</value>
</metadata> </metadata>
<metadata name="TimerDDC.TrayLocation" type="System.Drawing.Point, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a"> <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>
<metadata name="timerTypeIP.TrayLocation" type="System.Drawing.Point, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a"> <metadata name="timerTypeIP.TrayLocation" type="System.Drawing.Point, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
<value>305, 17</value> <value>305, 17</value>

View File

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

View File

@@ -4,11 +4,11 @@ namespace AiQ_GUI
internal class CameraModules internal class CameraModules
{ {
// Chack camera modules are in default state according to what the diagnostics API. // 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) 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; return;
} }
@@ -17,7 +17,21 @@ namespace AiQ_GUI
if (CamMod.zoom != 0) // Check camera module is at full wide if (CamMod.zoom != 0) // Check camera module is at full wide
errMssg += $"Zoom not at 0 - {CamMod.zoom} "; 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} "; errMssg += $"Firmware: {CamMod.firmwareVer} should be {UniversalData.WonwooFirmware} ";
if (CamMod.expMode != 0) // Auto 0=0x00 if (CamMod.expMode != 0) // Auto 0=0x00
@@ -41,7 +55,7 @@ namespace AiQ_GUI
} }
catch (Exception ex) 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) 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; return;
} }
@@ -61,7 +75,7 @@ namespace AiQ_GUI
if (ShutterVISCA.Contains("ERROR") || IrisVISCA.Contains("ERROR") || GainVISCA.Contains("ERROR")) 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; return;
} }
@@ -71,7 +85,7 @@ namespace AiQ_GUI
string OneshotReply = await FlexiAPI.APIHTTPVISCA(IPAddress, "8101041801FF", true); // Oneshot auto focus string OneshotReply = await FlexiAPI.APIHTTPVISCA(IPAddress, "8101041801FF", true); // Oneshot auto focus
if (!ShutterReply.Contains("41") || !IrisReply.Contains("41") || !GainReply.Contains("41") || !OneshotReply.Contains("41")) 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. // Sets back to the latest factory defaults CSV that is in Flexi.
@@ -83,7 +97,7 @@ namespace AiQ_GUI
await Task.WhenAll(IRReply, OVReply); await Task.WhenAll(IRReply, OVReply);
if (IRReply.Result != "Factory reset OK." || OVReply.Result != "Factory reset OK.") 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) 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 // 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"; 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) 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 try
{ {
string JSONdata = BuildJsonUpdate(jsonArrayData, ID); string JSONdata = BuildJsonUpdate(jsonArrayData, ID);
JSONdata = JSONdata.Replace("\"14\"", "14").Replace("\"30\"", "30"); // Fixes & encoding issue
string url = $"http://{IPAddress}/api/update-config"; string url = $"http://{IPAddress}/api/update-config";
return await Network.SendHttpRequest(url, HttpMethod.Post, 2, JSONdata); return await Network.SendHttpRequest(url, HttpMethod.Post, 2, JSONdata);
} }
catch (Exception ex) 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) 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 // Always Infrared as LED's are controlled from infrared page
// Level can be word eg. SAFE or hex eg. 0x0E // Level can be word eg. SAFE or hex eg. 0x0E
string suffix = $"/Infrared/led-controls?power={LEVEL}"; 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) 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(); string responseBody = await response.Content.ReadAsStringAsync();
if (!response.IsSuccessStatusCode) 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; return responseBody;
} }
catch (TaskCanceledException) catch (TaskCanceledException)
{ {
return $"Timeout uploading to {url}."; return $"Timeout uploading to {url}.{Level.ERROR}";
} }
catch (HttpRequestException ex) catch (HttpRequestException ex)
{ {
return $"HTTP error uploading to {url}: {ex.Message}"; return $"HTTP error uploading to {url}: {ex.Message}{Level.ERROR}";
} }
catch (Exception ex) 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")) 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; return null;
} }
@@ -159,24 +162,25 @@ namespace AiQ_GUI
return null; // If it fails to parse the JSON return null; // If it fails to parse the JSON
} }
} }
public static async Task<bool> SetVaxtorMinMaxPlate(string IP) public static async Task<bool> SetVaxtorMinMaxPlate(string IP)
{ {
try try
{ {
// Build JSON array for Vaxtor min/max plate configuration // 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); string response = await HTTP_Update("RaptorOCR".Trim(), IP, Vaxtor_JSON);
// Treat "operation was canceled" as a successful apply // Treat "operation was canceled" as a successful apply
if (response.Contains("The operation was canceled", StringComparison.OrdinalIgnoreCase)) 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).{Level.WARNING}");
return true; return true;
} }
if (response.Contains("error", StringComparison.OrdinalIgnoreCase)) if (response.Contains("error", StringComparison.OrdinalIgnoreCase))
{ {
MainForm.Instance.DisplayQuestion($"SetVaxtorMinMaxPlate: failed - Please set manually"); MainForm.Instance.DisplayQuestion($"SetVaxtorMinMaxPlate: failed - Please set manually{Level.WARNING}");
return false; return false;
} }
@@ -184,39 +188,21 @@ namespace AiQ_GUI
} }
catch (Exception ex) 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; return false;
} }
} }
public static async Task GPSFix(string IPAddress)
public static async Task<bool> SetZoomLockOn(string IP)
{ {
// Set Zoomlock on and if it fails ask user to set it manually string sysstatus = await APIHTTPRequest("/sysstatus", IPAddress, 5);
if (!(await APIHTTPRequest("/api/zoomLock?enable=true", IP)).Contains("Zoom lock enabled.") SysStatus status = JsonConvert.DeserializeObject<SysStatus>(sysstatus);
&& !await MainForm.Instance.DisplayQuestion("Could not set zoomlock on" + Environment.NewLine + "Set Zoomlock to on then click YES. Click NO to restart."))
if (status.gpsState == 0 || status.gpsPresent == "Not Fitted")
{ {
return false; MainForm.Instance.AddToActionsList($"GPS not present in camera. State: {status.gpsState} & Status: {status.gpsPresent}");
return;
} }
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 public static async Task SetTrim(string IPAddress, string LblTxt, int RetryCount = 0) // Sets trim by getting plate postion as metric
@@ -231,22 +217,21 @@ namespace AiQ_GUI
} }
catch catch
{ {
MainForm.Instance.AddToActionsList("Error reading trim JSON - " + trimData); MainForm.Instance.AddToActionsList($"Error reading trim JSON - {Level.ERROR}" + trimData);
return; 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 // 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) if (new[] { trim.infraredX, trim.infraredY, trim.colourX, trim.colourY }.Any(value => value == -1) || (trim.infraredX == trim.colourX && trim.infraredY == trim.colourY))
|| (trim.infraredX == trim.colourX && trim.infraredY == trim.colourY))
{ {
if (RetryCount >= 3) 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; return;
} }
await Task.Delay(5000); // Give 5 second delay for it to see a plate 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; int offset = 105;
@@ -269,15 +254,15 @@ namespace AiQ_GUI
} }
else // Ask user to centre the plate in the field of view 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 {Level.WARNING}"); // Awaited till OK has been clicked
if (RetryCount >= 3) 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; return;
} }
await Task.Delay(5000); // Give 5 second delay for it to see a plate 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 +275,7 @@ namespace AiQ_GUI
string TrimResp = await HTTP_Update("SightingCreator", IPAddress, Trim_JSON); 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\"}},")) 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 // Processes the network config from the camera and returns a string indicating the status
@@ -308,7 +293,7 @@ namespace AiQ_GUI
} }
catch (Exception ex) 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 return null; // Return empty string if there is an error
} }
} }
@@ -354,11 +339,143 @@ namespace AiQ_GUI
} }
// Change network settings to DHCP and restart camera for it to take effect // 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 string[,] TEST_JSON = { { "propDHCP", "true" } };
await HTTP_Update("GLOBAL--NetworkConfig", IPAddress, TEST_JSON); await HTTP_Update("GLOBAL--NetworkConfig", IPAddress, TEST_JSON); // Don't care about response because it will fail as it has changed IP.
// TODO - Check if this worked, if not return false
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 +485,8 @@ namespace AiQ_GUI
public string version { get; set; } = string.Empty; public string version { get; set; } = string.Empty;
public string revision { get; set; } = string.Empty; public string revision { get; set; } = string.Empty;
public string buildtime { 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 string MAC { get; set; } = string.Empty;
public int timeStamp { get; set; } public int timeStamp { get; set; }
public string UUID { get; set; } = string.Empty;
public string proquint { get; set; } = string.Empty; public string proquint { get; set; } = string.Empty;
[JsonProperty("Serial No.")] [JsonProperty("Serial No.")]
@@ -427,6 +542,12 @@ namespace AiQ_GUI
public int colourY { get; set; } 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 class NetworkConfig
{ {
public Property propDHCP { get; set; } = new Property(); public Property propDHCP { get; set; } = new Property();
@@ -435,13 +556,5 @@ namespace AiQ_GUI
public class Property public class Property
{ {
public string Value { get; set; } = string.Empty; 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 internal class ImageProcessing
{ {
// API to get snapshot then downsize and downscale image to save size. // 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 try
{ {
string requestUrl = $"http://{IPAddress}/{suffix}"; string requestUrl = $"http://{IPAddress}/{Module}-snapshot";
HttpClientHandler handler = new() HttpClientHandler handler = new()
{ {
@@ -26,7 +26,7 @@ namespace AiQ_GUI
HttpResponseMessage response = await httpClient.GetAsync(requestUrl); HttpResponseMessage response = await httpClient.GetAsync(requestUrl);
if (!response.IsSuccessStatusCode) 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; return null;
} }
@@ -34,7 +34,7 @@ namespace AiQ_GUI
if (imageBytes.Length == 0) // Check if the imageBytes is empty 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; return null;
} }
@@ -43,7 +43,7 @@ namespace AiQ_GUI
CvInvoke.Imdecode(imageBytes, ImreadModes.AnyColor, mat); CvInvoke.Imdecode(imageBytes, ImreadModes.AnyColor, mat);
if (mat.IsEmpty) 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; return null;
} }
@@ -70,12 +70,12 @@ namespace AiQ_GUI
} }
catch (HttpRequestException ex) catch (HttpRequestException ex)
{ {
MainForm.Instance.AddToActionsList($"HTTP error: {ex.Message}"); MainForm.Instance.AddToActionsList($"HTTP error: {ex.Message}{Level.ERROR}");
return null; return null;
} }
catch (Exception ex) catch (Exception ex)
{ {
MainForm.Instance.AddToActionsList($"Error processing image: {ex.Message}"); MainForm.Instance.AddToActionsList($"Error processing image: {ex.Message} {Level.ERROR}");
return null; 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) public static async Task ImageCheck(PictureBox PicBxOV, PictureBox PicBxF2, PictureBox PicBxF16, Label LblF2, Label LblF16, Camera CamOnTest)
{ {
// Take OV snapshot // 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 // Change to wide iris F2.0
await FlexiAPI.APIHTTPVISCA(CamOnTest.IP, "8101044B00000100FF", true); 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 await Task.Delay(200); // Wait for iris to settle before taking IR image
// Take IR bright light 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) if (F2_Response == null)
{ {
MainForm.Instance.AddToActionsList("IR F2.0 image response is blank."); 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 await Task.Delay(200); // Wait for iris to settle before taking IR image
// Take IR low light 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) if (F16_Response == null)
{ {
MainForm.Instance.AddToActionsList("IR F16.0 image response is blank."); MainForm.Instance.AddToActionsList("IR F16.0 image response is blank.");
@@ -123,7 +123,7 @@ namespace AiQ_GUI
} }
catch (Exception ex) catch (Exception ex)
{ {
MainForm.Instance.AddToActionsList($"Error awaiting Colour snapshot: {ex.Message}"); MainForm.Instance.AddToActionsList($"Error awaiting Colour snapshot: {ex.Message} {Level.ERROR}");
return; return;
} }
@@ -146,7 +146,7 @@ namespace AiQ_GUI
} }
catch (Exception ex) catch (Exception ex)
{ {
MainForm.Instance.AddToActionsList($"Error calculating luminance: {ex.Message}"); MainForm.Instance.AddToActionsList($"Error calculating luminance: {ex.Message} {Level.ERROR}");
return; return;
} }
} }

View File

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

View File

@@ -25,13 +25,13 @@ namespace AiQ_GUI
else if (Type == "Audit") else if (Type == "Audit")
salt = Auditsalt; salt = Auditsalt;
else else
return "Unrecognised challenge type: " + Type; return $"Unrecognised challenge type:{Level.ERROR}" + Type;
if (string.IsNullOrEmpty(challenge) || challenge.Length != 6) // Check challenge format 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 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 // Hash computation using SHA256 algorithm
byte[] inputBytes = Encoding.UTF8.GetBytes(challenge + " " + salt); // SHA hash format challenge and salt with space between byte[] inputBytes = Encoding.UTF8.GetBytes(challenge + " " + salt); // SHA hash format challenge and salt with space between
@@ -60,7 +60,7 @@ namespace AiQ_GUI
} }
catch (Exception ex) catch (Exception ex)
{ {
return "Error: Could not generate password " + ex.Message; return $"Error: Could not generate password{Level.ERROR}" + ex.Message;
} }
} }
@@ -85,7 +85,7 @@ namespace AiQ_GUI
} }
catch (Exception ex) catch (Exception ex)
{ {
MainForm.Instance.AddToActionsList("Exception in FetchDevPassword: " + ex.Message); MainForm.Instance.AddToActionsList($"Exception in FetchDevPassword: {Level.ERROR}" + ex.Message);
return null; return null;
} }
} }

View File

@@ -32,7 +32,7 @@ namespace AiQ_GUI
} }
catch 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; return null;
@@ -48,31 +48,31 @@ namespace AiQ_GUI
if (Strength < 25.0) 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; PassTest = false;
} }
if (!Router.SimStatus.Contains("SIM Ready")) 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; PassTest = false;
} }
if (!Router.Port3Status.Contains("port:3 link:up speed:100baseT full-duplex")) 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; PassTest = false;
} }
if (!Router.Port4Status.Contains("port:4 link:up speed:100baseT full-duplex")) 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; PassTest = false;
} }
if (!Router.GoodPing) 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; PassTest = false;
} }

View File

@@ -1,4 +1,5 @@
using Renci.SshNet; using Renci.SshNet;
using Renci.SshNet.Common;
namespace AiQ_GUI namespace AiQ_GUI
{ {
@@ -6,6 +7,29 @@ namespace AiQ_GUI
{ {
public const string SSHUsername = "mav"; public const string SSHUsername = "mav";
public const string SSHPassword = "mavPA$$"; 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. // Connects to camera over SSH and collects the Vaxtor packages, filesystem name, filesystem size, and tailscale status.
public static SSHData CollectSSHData(string IPAddress) public static SSHData CollectSSHData(string IPAddress)
@@ -14,8 +38,7 @@ namespace AiQ_GUI
try try
{ {
using SshClient client = new(IPAddress, SSHUsername, SSHPassword); SshClient client = SshConnect(IPAddress);
client.Connect();
try try
{ {
@@ -34,8 +57,7 @@ namespace AiQ_GUI
catch (Exception ex) catch (Exception ex)
{ {
MainForm.Instance.AddToActionsList($"Failed to get filesystem info: {ex.Message}"); MainForm.Instance.AddToActionsList($"Failed to get filesystem info: {ex.Message}");
Data.FilesystemName = "Unknown"; Data.FilesystemName = Data.FilesystemSize = "Unknown";
Data.FilesystemSize = "Unknown";
} }
try try
@@ -49,10 +71,11 @@ namespace AiQ_GUI
} }
client.Disconnect(); client.Disconnect();
client.Dispose();
} }
catch (Exception ex) 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)}")); string LogMssg = string.Join(" | ", typeof(SSHData).GetProperties().Select(p => $"{p.Name}: {p.GetValue(Data)}"));
@@ -131,8 +154,7 @@ namespace AiQ_GUI
{ {
try try
{ {
using SshClient client = new SshClient(IPAddress, SSHUsername, SSHPassword); SshClient client = SshConnect(IPAddress);
client.Connect();
try try
{ {
@@ -144,10 +166,11 @@ namespace AiQ_GUI
} }
client.Disconnect(); client.Disconnect();
client.Dispose();
} }
catch (Exception ex) 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); return (string.Empty, string.Empty);
@@ -191,10 +214,10 @@ namespace AiQ_GUI
{ {
try try
{ {
using SshClient client = new(IPAddress, SSHUsername, SSHPassword); SshClient client = SshConnect(IPAddress);
client.Connect();
(string FilesystemName, string FilesystemSize) = GetRootFilesystemInfo(client); (string FilesystemName, string FilesystemSize) = GetRootFilesystemInfo(client);
client.Disconnect(); client.Disconnect();
client.Dispose();
return (FilesystemName, FilesystemSize); return (FilesystemName, FilesystemSize);
} }
catch (Exception ex) 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. // 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) public static async Task<SSHData> CheckFSSize(string IPAddress, Label LblFSSize, SSHData sshData)
{ {
const double MinGoodSize = 100.0; // 100GB const double GoodSize = 128.0; // 128GB
const double MaxGoodSize = 150.0; // 150GB const double Deviation = 20.0; // ±20GB
double currentSize = NormaliseFSSize(sshData.FilesystemSize); double currentSize = NormaliseFSSize(sshData.FilesystemSize);
LblFSSize.Text = $"Filesystem Size = {currentSize}GB"; LblFSSize.Text = $"Filesystem Size = {currentSize}GB";
if (currentSize >= MinGoodSize && currentSize <= MaxGoodSize) if (Math.Abs(GoodSize - currentSize) < Deviation)
{ {
LblFSSize.ForeColor = Color.LightGreen; LblFSSize.ForeColor = Color.LightGreen;
return sshData; return sshData;
} }
if (currentSize < MinGoodSize) if (currentSize < GoodSize - Deviation)
{ {
try try
{ {
@@ -230,7 +253,7 @@ namespace AiQ_GUI
double newSize = NormaliseFSSize(sshData.FilesystemSize); double newSize = NormaliseFSSize(sshData.FilesystemSize);
LblFSSize.Text = $"Filesystem Size = {newSize}GB"; LblFSSize.Text = $"Filesystem Size = {newSize}GB";
if (newSize >= MinGoodSize && newSize <= MaxGoodSize) if (Math.Abs(GoodSize - newSize) < Deviation)
{ {
LblFSSize.ForeColor = Color.LightGreen; LblFSSize.ForeColor = Color.LightGreen;
return sshData; return sshData;
@@ -261,10 +284,9 @@ namespace AiQ_GUI
// Extract value & unit // Extract value & unit
System.Text.RegularExpressions.Match match = RegexCache.FileSizeRegex().Match(rootSize.Trim()); 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; return 0;
string unit = match.Groups["unit"].Value.ToUpperInvariant(); string unit = match.Groups["unit"].Value.ToUpperInvariant();
@@ -293,20 +315,19 @@ namespace AiQ_GUI
{ {
try try
{ {
using SshClient ssh = new SshClient(IPAddress, SSHUsername, SSHPassword); SshClient client = SshConnect(IPAddress);
ssh.Connect();
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)) if (!string.IsNullOrWhiteSpace(checkDevice.Error))
throw new Exception(checkDevice.Error); throw new Exception(checkDevice.Error);
if (checkDevice.Result.Trim() != "OK") // Device not found 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; 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")) if (!string.IsNullOrWhiteSpace(umountCmd.Error) && !umountCmd.Error.Contains("not mounted"))
{ {
MainForm.Instance.AddToActionsList($"Unmount error: {umountCmd.Error}"); MainForm.Instance.AddToActionsList($"Unmount error: {umountCmd.Error}");
@@ -315,15 +336,16 @@ namespace AiQ_GUI
await Task.Delay(1000); // Wait for mount to settle 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)) if (!string.IsNullOrWhiteSpace(fsckCmd.Error))
{ {
MainForm.Instance.AddToActionsList($"e2fsck error: {fsckCmd.Error}"); MainForm.Instance.AddToActionsList($"e2fsck error: {fsckCmd.Error}");
return false; return false;
} }
SshCommand resizeFs = ssh.RunCommand($"sudo resize2fs {device}"); SshCommand resizeFs = client.RunCommand($"sudo resize2fs {device}");
ssh.Disconnect(); client.Disconnect();
client.Dispose();
if (!string.IsNullOrWhiteSpace(resizeFs.Error)) if (!string.IsNullOrWhiteSpace(resizeFs.Error))
{ {
@@ -348,22 +370,24 @@ namespace AiQ_GUI
{ {
try try
{ {
using SshClient ssh = new SshClient(IPAddress, SSHUsername, SSHPassword); SshClient client = SshConnect(IPAddress);
ssh.Connect();
SshCommand checkDevice = ssh.RunCommand("sync"); SshCommand checkDevice = client.RunCommand("sync");
if (!string.IsNullOrWhiteSpace(checkDevice.Error)) if (!string.IsNullOrWhiteSpace(checkDevice.Error))
throw new Exception(checkDevice.Error); throw new Exception(checkDevice.Error);
if (checkDevice.Result.Trim().Length > 1) // Device not found 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; return;
} }
client.Disconnect();
client.Dispose();
} }
catch (Exception ex) 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}");
} }
} }
} }

View File

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

View File

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

View File

@@ -1,6 +1,6 @@
{ {
"version": "1.6.4", "version": "1.7.1",
"revision": "77e042f", "revision": "4b63df5",
"buildtime": "2025-07-24T10:35:36.445241784Z", "buildtime": "2025-07-24T10:35:36.445241784Z",
"appname": "FlexiAI", "appname": "FlexiAI",
"MAC": "3C:6D:66:0A:BA:18", "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 there is no dot in the version string, return the version as is
if (dotLocation < 0) if (dotLocation < 0)
{
return GUIVersion; return GUIVersion;
}
// Check if the next character after the dot is "0" // Check if the next character after the dot is "0"
if (dotLocation + 1 < GUIVersion.Length && GUIVersion[dotLocation + 1] == '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 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\\AiQ_GUI.application";
// Check if path is real // Check if path is real
if (!File.Exists(GUIPath)) if (!File.Exists(GUIPath))

View File

@@ -1,12 +1,12 @@
using Google.Apis.Auth.OAuth2; using Google.Apis.Auth.OAuth2;
using Google.Apis.Gmail.v1; //using Google.Apis.Gmail.v1;
using Google.Apis.Services; using Google.Apis.Services;
using Google.Apis.Sheets.v4; using Google.Apis.Sheets.v4;
using Google.Apis.Sheets.v4.Data; using Google.Apis.Sheets.v4.Data;
using Google.Apis.Util.Store; using Google.Apis.Util.Store;
using System.Net.Mail; //using System.Net.Mail;
using System.Net.Mime; //using System.Net.Mime;
using System.Reflection; //using System.Reflection;
namespace AiQ_GUI namespace AiQ_GUI
{ {
@@ -18,13 +18,14 @@ namespace AiQ_GUI
static readonly string credPath = Environment.GetFolderPath(Environment.SpecialFolder.Personal); static readonly string credPath = Environment.GetFolderPath(Environment.SpecialFolder.Personal);
public const string spreadsheetId_ModelInfo = "1bCcCr4OYqfjmydt6UqtmN4FQETezXmZRSStJdCCcqZM"; 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 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 // Startup and make necessary connections to Google servers and make sure user is logged in
public static bool Setup() public static bool Setup()
{ {
try try
{ {
string streamPath = $"{DrivePath}R50IQ\\client_secret.json"; const string streamPath = $"{DrivePath}R50IQ\\client_secret.json";
string[] Scopes = [SheetsService.Scope.Spreadsheets]; string[] Scopes = [SheetsService.Scope.Spreadsheets];
FileStream stream = new(streamPath, FileMode.Open, FileAccess.Read); FileStream stream = new(streamPath, FileMode.Open, FileAccess.Read);
credential = GoogleWebAuthorizationBroker.AuthorizeAsync(GoogleClientSecrets.FromStream(stream).Secrets, Scopes, "user", CancellationToken.None, new FileDataStore(credPath, true)).Result; credential = GoogleWebAuthorizationBroker.AuthorizeAsync(GoogleClientSecrets.FromStream(stream).Secrets, Scopes, "user", CancellationToken.None, new FileDataStore(credPath, true)).Result;
@@ -106,12 +107,12 @@ namespace AiQ_GUI
} }
else else
{ {
return "Last serial number not found"; return $"Last serial number not found{Level.ERROR}";
} }
} }
catch (Exception ex) catch (Exception ex)
{ {
return $"ERROR: {ex.Message}"; return $"ERROR: {ex.Message}{Level.ERROR}";
} }
} }
@@ -147,12 +148,12 @@ namespace AiQ_GUI
} }
else else
{ {
return "Serial number not found"; return $"Serial number not found{Level.ERROR}";
} }
} }
catch (Exception ex) catch (Exception ex)
{ {
return $"ERROR: {ex.Message}"; return $"ERROR: {ex.Message}{Level.ERROR}";
} }
} }
@@ -195,7 +196,7 @@ namespace AiQ_GUI
} }
catch (Exception ex) 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) 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 // Checks RMA control sheet for a model and serial that match current camera
public static int CheckRMANum(string serial, string model) public static int CheckRMANum(string serial, string model)
{ {
string spreadsheetId_RMAControl = "1tZhkYrqBQ3BcL7ZS4q3ghzCgHSJ8f5LVSj7nh6fIRC8"; const string spreadsheetId_RMAControl = "1tZhkYrqBQ3BcL7ZS4q3ghzCgHSJ8f5LVSj7nh6fIRC8";
try try
{ {
// Get all info in H and I columns // 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 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) // 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]; // FileStream GmailStream = new($"{DrivePath}R50IQ\\creds.json", FileMode.Open, FileAccess.Read);
// string[] ScopesGmail = [GmailService.Scope.GmailSend];
using (GmailStream) // using (GmailStream)
{ // {
string credPathGmail = Path.Combine(credPath, ".credentials/gmail-dotnet-quickstart.json"); // 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; // credential = GoogleWebAuthorizationBroker.AuthorizeAsync(GoogleClientSecrets.FromStream(GmailStream).Secrets, ScopesGmail, "user", CancellationToken.None, new FileDataStore(credPathGmail, true)).Result;
GmailStream.Close(); // GmailStream.Close();
} // }
// Build the MIME message with attachment // // Build the MIME message with attachment
using MailMessage mail = new MailMessage(); // using MailMessage mail = new();
mail.From = new MailAddress("me"); // mail.From = new MailAddress("me");
mail.To.Add("richard.porter@mav-systems.com"); // mail.To.Add("richard.porter@mav-systems.com");
mail.To.Add("bradley.relyea@mav-systems.com"); // mail.To.Add("bradley.relyea@mav-systems.com");
mail.To.Add("bradley.born@mav-systems.com"); // mail.To.Add("bradley.born@mav-systems.com");
mail.Subject = "Approval required"; // mail.Subject = "Approval required";
mail.Body = $"Dear Rich,<br><br>Camera needs approval<br>" + // mail.Body = $"Dear Rich,<br><br>Camera needs approval<br>" +
$"https://docs.google.com/spreadsheets/d/1bCcCr4OYqfjmydt6UqtmN4FQETezXmZRSStJdCCcqZM/edit#gid=1931079354&range=A{ApprovalRow}" + // $"https://docs.google.com/spreadsheets/d/1bCcCr4OYqfjmydt6UqtmN4FQETezXmZRSStJdCCcqZM/edit#gid=1931079354&range=A{ApprovalRow}" +
$"<br><br><br>Thanks,<br><br>{User}"; // $"<br><br><br>Thanks,<br><br>{User}";
mail.IsBodyHtml = true; // mail.IsBodyHtml = true;
// Attach the log file if it exists // // Attach the log file if it exists
string logFilePath = LDS.MAVPath + Logging.LogFileName; // string logFilePath = LDS.MAVPath + Logging.LogFileName;
if (File.Exists(logFilePath)) // if (File.Exists(logFilePath))
{ // {
Attachment logAttachment = new(logFilePath, MediaTypeNames.Text.Plain); // Attachment logAttachment = new(logFilePath, MediaTypeNames.Text.Plain);
logAttachment.Name = Logging.LogFileName; // logAttachment.Name = Logging.LogFileName;
mail.Attachments.Add(logAttachment); // mail.Attachments.Add(logAttachment);
} // }
// Save the MIME message to a stream // // Save the MIME message to a stream
using MemoryStream ms = new(); // using MemoryStream ms = new();
SmtpClient smtpClient = new(); // Only used to access the internal Write method // SmtpClient smtpClient = new(); // Only used to access the internal Write method
Type mailWriterType = typeof(SmtpClient).Assembly.GetType("System.Net.Mail.MailWriter"); // Type mailWriterType = typeof(SmtpClient).Assembly.GetType("System.Net.Mail.MailWriter");
object? mailWriter = Activator.CreateInstance( // object? mailWriter = Activator.CreateInstance(
mailWriterType, // mailWriterType,
BindingFlags.Instance | BindingFlags.NonPublic, // BindingFlags.Instance | BindingFlags.NonPublic,
null, // null,
[ms, true], // [ms, true],
null); // null);
typeof(MailMessage).InvokeMember( // typeof(MailMessage).InvokeMember(
"Send", // "Send",
BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.InvokeMethod, // BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.InvokeMethod,
null, // null,
mail, // mail,
[mailWriter, true, true]); // [mailWriter, true, true]);
ms.Position = 0; // ms.Position = 0;
byte[] rawBytes = ms.ToArray(); // byte[] rawBytes = ms.ToArray();
GmailService service = new(new BaseClientService.Initializer() // GmailService service = new(new BaseClientService.Initializer()
{ // {
HttpClientInitializer = credential, // HttpClientInitializer = credential,
ApplicationName = ApplicationName, // ApplicationName = ApplicationName,
}); // });
Google.Apis.Gmail.v1.Data.Message newMsg = new() // Google.Apis.Gmail.v1.Data.Message newMsg = new()
{ // {
Raw = Convert.ToBase64String(rawBytes) // Raw = Convert.ToBase64String(rawBytes)
.Replace("+", "-") // .Replace("+", "-")
.Replace("/", "_") // .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() public static void PrintGBLbl()
{ {
// New Label with UKCA logo // 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" + 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" + "^XA\n" +
"^MMT\n" + "^MMT\n" +
"^PW384\n" + "^PW384\n" +
"^LL0096\n" + "^LL96\n" +
"^LS0\n" + "^LS0\n" +
"^FO192,0^GFA,01152,01152,00012,:Z64:\n" + "^FO212,34^GFA,225,280,8,:Z64:eJxVkDEKwkAQRZ+GRbs0oo2CtYcwV0hno+AR0oiVkKOk9jTbWAa2TLllKte/uJi1WIbHnz/zZ5l3cABMAyfVaqQILVwsphcHTxlgdoNNDcWoFsnGwlly6eCtutBrIktTCztZvOpRFrWwFl8TD0vYil/1l4MMK/G9mTjX3f7f79J8m+377U95Yr4HKW835ScMuie7r/JaGAek++N/PFs+dGoy4g==:0511\n" +
"eJzt0DEKgDAMBdCUDo49Qm9ij2aP1qN4BEeHYvziL0TURRcHA4VXaEryRf76agWciVZcaQ8nuoMHOsBKxypO2ZqK+Jke0NK+XNAy0hUuOx3exkzjbZSzPdw3j0cnzY999efdDHZOO/9hL7tvyuIrHSfEyGbk5vQiT5uzzX+bwDX/9a5WFTRBPA==:FF83\n" + "^FO336,26^GFA,217,384,8,:Z64:eJxrYEAAFiBWAGIOME8ACIFsvncdIDEWFhERByDNZLdqXwOQZhRkFAQrW8DABaYDGETAtAGDFVQ7hM+htQNMi9QLgGkuZQUwzQi1lKMDwmdwgPINoHwBVHmYei4mBbhjwfIMD6DKIQZoMHyASDBCDOjhfgWmWUMDQBQTE8MGJC+D/boAia/AQBng////P8hB8kD6ABKfHwcfACScKCU=:8B00\n" +
"^FO320,0^GFA,00768,00768,00008,:Z64:\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" +
"eJxjYBjOgAWIHYCYA4gNGOwYBEBs9v8HFEBycnY2IDkmOwabBiDNaP//L1jTBAYxMB3AIAOmDaC0AIMNmJawLwDTMvUTwLQQswCYlmRSANMcLVA6A0obQOkKVHlOqHouRgjNw/AAIs+QALEHSuswfADTLow/QBRjC2MNiGZi//8L7Ec2xo8MUD82gJ3JANaoAHY65UD+////D0ighxYAAPB/L1Q=:B1A7\n" + "^FO278,28^GFA,273,360,8,:Z64:eJx1j7ENwjAQRX8UoRQUKel8LcoMyG6yB2ukCCSlR0EMgSgYgBEyAUrpwspxZwcJIWFLPj3/++fvC/YNGqBHb+RAgLVyoMPBFHPmeso63TM7ZF3alV1I3NO88pR1mr508dPaLw+s86Qu15jmDxxMJdVxsKUYdjynPFseLW74v7yHH4ENx2J4AjUvJYufOFYcdd4iV8CpbelxBM5yJVvygWTrVy1qzQOj9cOdctYdc9JJ6y8v3rsXa/6Y/PJe8pMypTyJ30wJYOg=:5E2C\n" +
"^FO0,0^GFA,02688,02688,00028,:Z64:\n" + "^PQ1,0,1,Y\n" +
"eJzt071OwzAQB/BEHjL6ETzwAowMSH4Y3gMXdejII/RRcJWBsY9QVx0YsdTFEsbH3fmjSaGMDKhXZ/oldc73T9dd61r/sLTXcACnu8ENXsBCAHRGLhRbyLaQTqKFbDZbUmxWsaVsLhtINvwpemjJ5osNv9jI5o1TEW1kC8X2bAGvKLweQ4cWi3m2z2zqwJaqGTLAKwkvs0GxqIuZRzJIZGsyBUmRGXCPRvhhahJAsr27NLFdsRWZRtPNdkBGLZGprYt6WffbsvUAezL5vMdDqe/5ynYL4HlGEm2s/Y1sdwbCw8nMODVs8IbtAHhA5Tw3bPeKZnayPAfLphVAtjd6SShzN2wrujfbcKxzt5ptSXuwObQwsz1t4y5kEOB40Qx8XDLMJ1wyzCfNPOJd1FASoUs115hPMrC0qtmWTzZeaENEcy2fbJ5WNd/yObc0Md8skEmy0PJJtvRaBN3h2ZHlXDPPDNBSNTUz9YIG1WTZr9oitcxD7W9g02zrmXEPZIpsN7PQzKaS66k5Mhx6Krmu9hTprMnApZLrn208N8X/id/AueHnK32zzfm7DGQ9m/1m1HsfRUg116fe+cz6wKZnZvERNBH6WO1a1/qD+gLCtYx7:603A\n" + "^XZ";
"^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";
if (!PrintToZebra(MadeInGB)) if (!PrintToZebra(MadeInGB))
MainForm.Instance.AddToActionsList("Error printing GB label"); MainForm.Instance.AddToActionsList("Error printing GB label");

View File

@@ -83,26 +83,26 @@ namespace AiQ_GUI
case 0: case 0:
if (!await MainForm.Instance.DisplayQuestion("Is the sleeve aligned correctly?")) 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; break;
case 1: case 1:
if (!await MainForm.Instance.DisplayQuestion("Are all the screws fitted in the front?")) 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; break;
case 2: case 2:
if (!await MainForm.Instance.DisplayQuestion("Are all the screws fitted in the rear?")) 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; break;
case 3: case 3:
if (await MainForm.Instance.DisplayQuestion("Shake unit, does it rattle?")) 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; break;
default: default:
@@ -123,7 +123,7 @@ namespace AiQ_GUI
} }
catch (Exception ex) 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; return null;
} }
@@ -184,9 +184,9 @@ namespace AiQ_GUI
public string IP { get; set; } = string.Empty; public string IP { get; set; } = string.Empty;
public int RMANum { get; set; } = 0; public int RMANum { get; set; } = 0;
public string FlexiVersion { get; set; } = string.Empty; // Flexi version 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 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 // Static class for global flags

12
LDS.cs
View File

@@ -17,15 +17,10 @@ namespace AiQ_GUI
try try
{ {
if (!Directory.Exists(MAVPath)) // Check the AiQ folder exists in ProgramData and if it doesn't then create it if (!Directory.Exists(MAVPath)) // Check the AiQ folder exists in ProgramData and if it doesn't then create it
{
Directory.CreateDirectory(MAVPath); Directory.CreateDirectory(MAVPath);
}
if (!File.Exists(MAVPath + LDSFileName)) // Check the LDS file exists and if it doesn't then create it if (!File.Exists(MAVPath + LDSFileName)) // Check the LDS file exists and if it doesn't then create it
{ File.WriteAllText(MAVPath + LDSFileName, DefaultJSON); // Save a blank version of the JSON
// Save a blank version of the JSON
File.WriteAllText(MAVPath + LDSFileName, DefaultJSON);
}
StreamReader file = new(MAVPath + LDSFileName); StreamReader file = new(MAVPath + LDSFileName);
string Content = file.ReadToEnd(); string Content = file.ReadToEnd();
@@ -35,9 +30,8 @@ namespace AiQ_GUI
} }
catch // If file can't deserialise 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 return null; // Return null to indicate failure
} }
} }
@@ -51,7 +45,7 @@ namespace AiQ_GUI
} }
catch (Exception ex) 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}");
} }
} }
} }

View File

@@ -43,7 +43,7 @@ namespace AiQ_GUI
} }
catch (Exception ex) 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 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". // 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() 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) IOrderedEnumerable<Tuple<string, string>> sorted = modelTuples.OrderBy(t => t.Item1.Equals("AB12CD", StringComparison.OrdinalIgnoreCase) ? 1 : 0)
.ThenBy(t => t.Item1, StringComparer.OrdinalIgnoreCase); .ThenBy(t => t.Item1, StringComparer.OrdinalIgnoreCase);
conn.Close();
// Format the sorted tuples as "ModelNumber - Description" strings and return as array // Format the sorted tuples as "ModelNumber - Description" strings and return as array
return sorted.Select(t => $"{t.Item1} - {t.Item2}").ToArray(); return sorted.Select(t => $"{t.Item1} - {t.Item2}").ToArray();
} }
@@ -67,6 +68,7 @@ namespace AiQ_GUI
UniversalData.LatestVersion = Convert.ToString(reader["AiQGUIVersion"]); UniversalData.LatestVersion = Convert.ToString(reader["AiQGUIVersion"]);
UniversalData.PowerConsumption = Convert.ToInt16(reader["PowerConsumption"]); UniversalData.PowerConsumption = Convert.ToInt16(reader["PowerConsumption"]);
UniversalData.LicencingServerURL = Convert.ToString(reader["LicencingServerURL"]); 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. // Knowing the model number on test, this function reads the database and populates the Camera class with the values.
@@ -97,11 +99,71 @@ namespace AiQ_GUI
CameraAccessInfo.LED_V = Convert.ToDouble(reader["LEDVoltage"]); CameraAccessInfo.LED_V = Convert.ToDouble(reader["LEDVoltage"]);
CameraAccessInfo.LED_I = Convert.ToInt32(reader["LEDCurrent"]); CameraAccessInfo.LED_I = Convert.ToInt32(reader["LEDCurrent"]);
CameraAccessInfo.SpreadsheetID = Convert.ToString(reader["SSID"]); 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 // Expected universal data for the GUI, read from the database
public class UniversalData public class UniversalData

View File

@@ -17,7 +17,7 @@ namespace AiQ_GUI
} }
else 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 else
{ {
MainForm.Instance.AddToActionsList("Could not find spreadsheet :("); MainForm.Instance.AddToActionsList($"Could not find spreadsheet :( {Level.ERROR}");
} }
return null; return null;
} }
@@ -40,7 +40,7 @@ namespace AiQ_GUI
{ {
if (!File.Exists(FilePath)) if (!File.Exists(FilePath))
{ {
MainForm.Instance.AddToActionsList("Could not find spreadsheet :("); MainForm.Instance.AddToActionsList($"Could not find spreadsheet :( {Level.ERROR}");
return -1; return -1;
} }
@@ -62,7 +62,7 @@ namespace AiQ_GUI
{ {
if (!File.Exists(FilePath)) if (!File.Exists(FilePath))
{ {
MainForm.Instance.AddToActionsList("Could not find spreadsheet :("); MainForm.Instance.AddToActionsList($"Could not find spreadsheet :( {Level.ERROR}");
return "Spreadsheet not found"; return "Spreadsheet not found";
} }
@@ -114,7 +114,7 @@ namespace AiQ_GUI
{ {
if (!File.Exists(filePath)) if (!File.Exists(filePath))
{ {
MainForm.Instance.AddToActionsList("Could not find spreadsheet :("); MainForm.Instance.AddToActionsList($"Could not find spreadsheet :) {Level.ERROR}");
return "Spreadsheet not found"; return "Spreadsheet not found";
} }
@@ -150,7 +150,7 @@ namespace AiQ_GUI
} }
catch (Exception ex) 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}"; return $"ERROR: {ex.Message}";
} }
} }
@@ -161,7 +161,7 @@ namespace AiQ_GUI
{ {
if (!File.Exists(FilePath)) if (!File.Exists(FilePath))
{ {
MainForm.Instance.AddToActionsList("Could not find spreadsheet :("); MainForm.Instance.AddToActionsList($"Could not find spreadsheet :( {Level.ERROR}");
return "Spreadsheet not found"; return "Spreadsheet not found";
} }
@@ -202,8 +202,8 @@ namespace AiQ_GUI
} }
catch (Exception ex) catch (Exception ex)
{ {
MainForm.Instance.AddToActionsList("Error updating spreadsheet: " + ex.Message); MainForm.Instance.AddToActionsList($"Error updating spreadsheet: {Level.WARNING}" + ex.Message);
return "Failed to update spreadsheet data, please check manually: " + 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)) if (!File.Exists(FilePath))
{ {
MainForm.Instance.AddToActionsList("Could not find spreadsheet :("); MainForm.Instance.AddToActionsList($"Could not find spreadsheet :( {Level.ERROR}");
return "Spreadsheet not found"; return $"Spreadsheet not found{ Level.ERROR}"
;
} }
using XLWorkbook workbook = new(FilePath); using XLWorkbook workbook = new(FilePath);
@@ -239,8 +240,8 @@ namespace AiQ_GUI
} }
catch (Exception ex) catch (Exception ex)
{ {
MainForm.Instance.AddToActionsList("Error updating Vaxtor spreadsheet: " + ex.Message); MainForm.Instance.AddToActionsList($"Error updating Vaxtor spreadsheet: {Level.WARNING}" + ex.Message);
return "Failed to update spreadsheet data, please check manually: " + 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)) 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; return 0;
} }
@@ -280,7 +281,7 @@ namespace AiQ_GUI
} }
catch (Exception ex) 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 return 0; // Default if not found
@@ -292,7 +293,7 @@ namespace AiQ_GUI
{ {
if (!File.Exists(filePath)) if (!File.Exists(filePath))
{ {
MainForm.Instance.AddToActionsList("Could not find spreadsheet :("); MainForm.Instance.AddToActionsList($"Could not find spreadsheet :( {Level.ERROR}");
return 0; return 0;
} }
@@ -316,7 +317,7 @@ namespace AiQ_GUI
} }
catch (Exception ex) 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; return 0;
@@ -328,7 +329,7 @@ namespace AiQ_GUI
{ {
if (!File.Exists(filePath)) if (!File.Exists(filePath))
{ {
MainForm.Instance.AddToActionsList("Could not find spreadsheet :("); MainForm.Instance.AddToActionsList($"Could not find spreadsheet :({Level.ERROR}");
return -1; return -1;
} }
@@ -343,7 +344,7 @@ namespace AiQ_GUI
} }
catch (Exception ex) 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; return -1;
} }
} }
@@ -417,7 +418,6 @@ namespace AiQ_GUI
// int nextFree = Excel.CheckNextFree(filePath); // int nextFree = Excel.CheckNextFree(filePath);
// Debug.WriteLine("Next free row in Vaxtor sheet (Column C): " + nextFree); // 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;
using System.Data.OleDb; using System.Data.OleDb;
using ClosedXML.Excel;
namespace AiQ_GUI.Microsoft namespace AiQ_GUI
{ {
internal class StatsExcel internal class StatsExcel
{ {
private const string connString = private const string exportPath = @"G:\Shared drives\MAV Production GUI's\AiQ\GUI's\AiQ_Test_Stats.xlsx";
@"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";
public void ExportDatabaseToExcel() public void ExportDatabaseToExcel()
{ {
@@ -19,18 +14,16 @@ namespace AiQ_GUI.Microsoft
{ {
MainForm.Instance.AddToActionsList("=== ExportDatabaseToExcel START ==="); MainForm.Instance.AddToActionsList("=== ExportDatabaseToExcel START ===");
using (OleDbConnection conn = new(connString)) using OleDbConnection conn = new(Access.connString);
{
conn.Open(); conn.Open();
MainForm.Instance.AddToActionsList("Connected to Access database."); MainForm.Instance.AddToActionsList("Connected to Access database.");
DateTime now = DateTime.Now; DateTime now = DateTime.Now;
// Read LastStatsRun from UniversalData (if any) // Read LastStatsRun from UniversalData (if any)
DateTime? lastStatsRun = null; DateTime? lastStatsRun = null;
try try
{ {
using (var cmdPeriod = new OleDbCommand("SELECT LastStatsRun FROM UniversalData", conn)) using (OleDbCommand cmdPeriod = new("SELECT LastStatsRun FROM UniversalData", conn))
{ {
object res = cmdPeriod.ExecuteScalar(); object res = cmdPeriod.ExecuteScalar();
if (res != null && res != DBNull.Value) if (res != null && res != DBNull.Value)
@@ -44,7 +37,7 @@ namespace AiQ_GUI.Microsoft
} }
// ==================== MAIN STATS EXPORT ==================== // ==================== MAIN STATS EXPORT ====================
string selectColumns = @" const string selectColumns = @"
ModelNumber, ModelNumber,
[Total Tests Run], [Total Tests Run],
[Pre Tests Passed], [Pre Tests Passed],
@@ -89,7 +82,7 @@ namespace AiQ_GUI.Microsoft
MainForm.Instance.AddToActionsList($"Adding sheet: {sheetName}"); MainForm.Instance.AddToActionsList($"Adding sheet: {sheetName}");
XLWorkbook workbook; XLWorkbook workbook;
if (System.IO.File.Exists(exportPath)) if (File.Exists(exportPath))
{ {
workbook = new XLWorkbook(exportPath); workbook = new XLWorkbook(exportPath);
MainForm.Instance.AddToActionsList("Opened existing workbook."); MainForm.Instance.AddToActionsList("Opened existing workbook.");
@@ -106,7 +99,7 @@ namespace AiQ_GUI.Microsoft
MainForm.Instance.AddToActionsList($"Deleted old sheet: {sheetName}"); MainForm.Instance.AddToActionsList($"Deleted old sheet: {sheetName}");
} }
var ws = workbook.Worksheets.Add(sheetName); IXLWorksheet ws = workbook.Worksheets.Add(sheetName);
ws.Cell(1, 1).InsertTable(dataTable, "AiQ_Stats", true); ws.Cell(1, 1).InsertTable(dataTable, "AiQ_Stats", true);
ws.Columns().AdjustToContents(); ws.Columns().AdjustToContents();
@@ -129,10 +122,10 @@ namespace AiQ_GUI.Microsoft
// ==================== DIAGS STATS EXPORT (CURRENT MONTH ONLY) ==================== // ==================== DIAGS STATS EXPORT (CURRENT MONTH ONLY) ====================
MainForm.Instance.AddToActionsList("Exporting DiagsStats for current month..."); MainForm.Instance.AddToActionsList("Exporting DiagsStats for current month...");
DateTime monthStart = new DateTime(now.Year, now.Month, 1); DateTime monthStart = new(now.Year, now.Month, 1);
DateTime nextMonth = monthStart.AddMonths(1); DateTime nextMonth = monthStart.AddMonths(1);
string diagsQuery = @" const string diagsQuery = @"
SELECT SELECT
[Date], [Date],
[Model], [Model],
@@ -143,13 +136,12 @@ namespace AiQ_GUI.Microsoft
FROM DiagsStats FROM DiagsStats
WHERE [Date] >= ? AND [Date] < ?"; 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 = monthStart });
diagsCmd.Parameters.Add(new OleDbParameter { OleDbType = OleDbType.Date, Value = nextMonth }); diagsCmd.Parameters.Add(new OleDbParameter { OleDbType = OleDbType.Date, Value = nextMonth });
using (var diagsAdapter = new OleDbDataAdapter(diagsCmd)) using OleDbDataAdapter diagsAdapter = new(diagsCmd);
{
DataTable diagsTable = new(); DataTable diagsTable = new();
diagsAdapter.Fill(diagsTable); diagsAdapter.Fill(diagsTable);
@@ -167,12 +159,10 @@ namespace AiQ_GUI.Microsoft
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 ==================== // ==================== SAVE MAIN SHEET ====================
workbook.SaveAs(exportPath); workbook.SaveAs(exportPath);
MainForm.Instance.AddToActionsList($"Added {sheetName} to workbook: {exportPath}"); MainForm.Instance.AddToActionsList($"Added {sheetName} to workbook: {exportPath}");
workbook.Dispose(); workbook.Dispose();
// ==================== BACKUP TABLE (ALWAYS, BEFORE RESET) ==================== // ==================== BACKUP TABLE (ALWAYS, BEFORE RESET) ====================
@@ -189,7 +179,7 @@ namespace AiQ_GUI.Microsoft
FROM AiQ"; FROM AiQ";
try try
{ {
using (var cmdBackup = new OleDbCommand(backupSql, conn)) using (OleDbCommand cmdBackup = new(backupSql, conn))
{ {
cmdBackup.ExecuteNonQuery(); cmdBackup.ExecuteNonQuery();
} }
@@ -202,11 +192,11 @@ namespace AiQ_GUI.Microsoft
return; return;
} }
using (var tx = conn.BeginTransaction()) using (OleDbTransaction tx = conn.BeginTransaction())
{ {
try try
{ {
string resetSql = @" const string resetSql = @"
UPDATE AiQ SET UPDATE AiQ SET
[Total Tests Run] = 0, [Total Tests Run] = 0,
[Pre Tests Passed] = 0, [Pre Tests Passed] = 0,
@@ -230,14 +220,14 @@ namespace AiQ_GUI.Microsoft
[Visual Test Fail - Not All Rear Screws Fitted] = 0, [Visual Test Fail - Not All Rear Screws Fitted] = 0,
[Visual Test Fail - Unit rattles] = 0;"; [Visual Test Fail - Unit rattles] = 0;";
using (var cmdReset = new OleDbCommand(resetSql, conn, tx)) using (OleDbCommand cmdReset = new(resetSql, conn, tx))
{ {
int affected = cmdReset.ExecuteNonQuery(); int affected = cmdReset.ExecuteNonQuery();
MainForm.Instance.AddToActionsList($"Zeroed counters on AiQ rows: {affected} row(s)."); MainForm.Instance.AddToActionsList($"Zeroed counters on AiQ rows: {affected} row(s).");
} }
int updatedRows; int updatedRows;
using (var cmdUpd = new OleDbCommand("UPDATE UniversalData SET LastStatsRun = ?", conn, tx)) using (OleDbCommand cmdUpd = new("UPDATE UniversalData SET LastStatsRun = ?", conn, tx))
{ {
cmdUpd.Parameters.Add(new OleDbParameter { OleDbType = OleDbType.Date, Value = now }); cmdUpd.Parameters.Add(new OleDbParameter { OleDbType = OleDbType.Date, Value = now });
updatedRows = cmdUpd.ExecuteNonQuery(); updatedRows = cmdUpd.ExecuteNonQuery();
@@ -245,13 +235,11 @@ namespace AiQ_GUI.Microsoft
if (updatedRows == 0) if (updatedRows == 0)
{ {
using (var cmdIns = new OleDbCommand("INSERT INTO UniversalData (LastStatsRun) VALUES (?)", conn, tx)) using OleDbCommand cmdIns = new("INSERT INTO UniversalData (LastStatsRun) VALUES (?)", conn, tx);
{
cmdIns.Parameters.Add(new OleDbParameter { OleDbType = OleDbType.Date, Value = now }); cmdIns.Parameters.Add(new OleDbParameter { OleDbType = OleDbType.Date, Value = now });
cmdIns.ExecuteNonQuery(); cmdIns.ExecuteNonQuery();
MainForm.Instance.AddToActionsList("Inserted LastStatsRun row."); MainForm.Instance.AddToActionsList("Inserted LastStatsRun row.");
} }
}
else else
{ {
MainForm.Instance.AddToActionsList("Updated LastStatsRun successfully."); MainForm.Instance.AddToActionsList("Updated LastStatsRun successfully.");
@@ -265,7 +253,8 @@ namespace AiQ_GUI.Microsoft
tx.Rollback(); tx.Rollback();
MainForm.Instance.AddToActionsList($"ERROR during reset, rolled back. Details: {resetEx.Message}"); MainForm.Instance.AddToActionsList($"ERROR during reset, rolled back. Details: {resetEx.Message}");
} }
}
conn.Close();
} }
MainForm.Instance.AddToActionsList("=== ExportDatabaseToExcel END ==="); 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"; 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 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); 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); HttpResponseMessage response = await client.PostAsync(webhookUrl, content);
} }

View File

@@ -1,6 +1,5 @@
using System.Diagnostics; using System.Diagnostics;
using System.Reflection; using System.Reflection;
using System.Runtime.InteropServices;
using System.Security.Principal; using System.Security.Principal;
namespace AiQ_GUI namespace AiQ_GUI
@@ -15,15 +14,12 @@ namespace AiQ_GUI
IEnumerable<Task> tasks = Process.GetProcesses() IEnumerable<Task> tasks = Process.GetProcesses()
.Where(p => targetProcesses.Any(tp => p.ProcessName.Contains(tp))) .Where(p => targetProcesses.Any(tp => p.ProcessName.Contains(tp)))
.Select(clsProcess => .Select(clsProcess =>
{
using (clsProcess)
{ {
try try
{ {
clsProcess.CloseMainWindow(); clsProcess.CloseMainWindow();
} }
catch { } catch { }
}
return Task.CompletedTask; return Task.CompletedTask;
}); });
@@ -34,7 +30,7 @@ namespace AiQ_GUI
{ {
Logging.LogMessage($"Starting exe from {ExeLoc}"); Logging.LogMessage($"Starting exe from {ExeLoc}");
ProcessStartInfo processInfo = new ProcessStartInfo(ExeLoc) ProcessStartInfo processInfo = new(ExeLoc)
{ {
UseShellExecute = true, UseShellExecute = true,
Verb = "runas" Verb = "runas"
@@ -64,7 +60,7 @@ namespace AiQ_GUI
{ {
StartAsAdmin(ExeLoc); StartAsAdmin(ExeLoc);
} }
else if (runAsAdmin) else if (Properties.Settings.Default.FirstRun && runAsAdmin)
{ {
try 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? SingleHTTPClient;
public static HttpClient Client => SingleHTTPClient ?? throw new InvalidOperationException("Client not initialized."); 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() HttpClientHandler handler = new()
{ {
@@ -23,6 +23,19 @@ namespace AiQ_GUI
{ {
Timeout = TimeSpan.FromSeconds(20) 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 // Handles get and post to the camera API
@@ -58,15 +71,15 @@ namespace AiQ_GUI
} }
catch (TaskCanceledException ex) catch (TaskCanceledException ex)
{ {
return $"HTTP error calling {url}: {ex.Message}"; return $"HTTP error calling {url}: {ex.Message}{Level.ERROR}";
} }
catch (HttpRequestException ex) catch (HttpRequestException ex)
{ {
return $"HTTP error calling {url}: {ex.Message}"; return $"HTTP error calling {url}: {ex.Message}{Level.ERROR}";
} }
catch (Exception ex) 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 sendPort = 6666;
const int receivePort = 6667; const int receivePort = 6667;
const int discoveryTimeoutMs = 1000;
IList<string> FoundCams = []; IList<string> FoundCams = [];
byte[] discoveryPacket = [0x50, 0x4f, 0x4c, 0x4c, 0xaf, 0xb0, 0xb3, 0xb3, 0xb6, 0x01, 0xa8, 0xc0, 0x0b, 0x1a, 0x00, 0x00]; 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 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 try
{ {
while (DateTime.Now < timeout) while (DateTime.Now < timeout)
@@ -143,9 +157,9 @@ namespace AiQ_GUI
PingReply reply = await myPing.SendPingAsync(ipAddress, 1000); // Timeout is 1s PingReply reply = await myPing.SendPingAsync(ipAddress, 1000); // Timeout is 1s
return reply.Status == IPStatus.Success; return reply.Status == IPStatus.Success;
} }
catch (PingException ex) { MainForm.Instance.AddToActionsList($"PingException: Unable to ping {ipAddress}. Reason: {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}"); } 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}"); } catch (Exception ex) { MainForm.Instance.AddToActionsList($"Unexpected error while pinging {ipAddress}: {ex.Message}{Level.ERROR}"); }
} }
return false; 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> logLines = File.Exists(logFilePath) ? File.ReadAllLines(logFilePath).ToList() : new List<string>();
List<string> errorLines = logLines.Where(l => l.Contains("[ERROR]")).ToList(); List<string> errorLines = logLines.Where(l => l.Contains("[ERROR]")).ToList();
List<string> warningLines = logLines.Where(l => l.Contains("[WARNING]")).ToList(); List<string> warningLines = logLines.Where(l => l.Contains("[WARNING]")).ToList();
@@ -337,7 +336,17 @@ namespace AiQ_GUI
renderer.PdfDocument.Options.Layout = PdfWriterLayout.Compact; renderer.PdfDocument.Options.Layout = PdfWriterLayout.Compact;
// Construct save path // 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); renderer.PdfDocument.Save(fullPath);
Logging.LogMessage("Soak Test PDF saved to " + fullPath); Logging.LogMessage("Soak Test PDF saved to " + fullPath);

View File

@@ -19,7 +19,7 @@ namespace AiQ_GUI
} }
catch (WebDriverTimeoutException) 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; return elementID;
} }
// Clicks the element with the specified ID using Chromedriver public static void SwitchUser(ChromeDriver driver)
public static void ClickElementByID(string elementID, 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 // 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 try
{ {
@@ -61,27 +96,48 @@ namespace AiQ_GUI
driver.Navigate().Refresh(); driver.Navigate().Refresh();
WebDriverWait wait = new(driver, TimeSpan.FromSeconds(10)); WebDriverWait wait = new(driver, TimeSpan.FromSeconds(10));
wait.Until(d => ((IJavaScriptExecutor)d).ExecuteScript("return document.readyState").Equals("complete")); 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 // Initialises and opens a ChromeDriver with specific options
public static ChromeDriver OpenDriver() public static ChromeDriver OpenDriver()
{ {
ChromeDriverService chromeDriverService = ChromeDriverService.CreateDefaultService(); try
chromeDriverService.HideCommandPromptWindow = true; {
ChromeOptions options = new(); ChromeOptions options = new();
options.AddArguments("--app=data:,", "--window-size=960,1040", "--window-position=0,0"); options.AddArguments("--app=data:,",
options.AddExcludedArgument("enable-automation"); "--window-size=960,1040",
options.AddAdditionalChromeOption("useAutomationExtension", false); "--window-position=0,0",
"--no-sandbox",
"--incognito",
"--no-first-run",
"--no-default-browser-check",
"--disable-features=BrowserAddPersonFeature,InterestFeedContentSuggestions");
ChromeDriver driver = new(chromeDriverService, options); // 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); driver.Manage().Timeouts().PageLoad = TimeSpan.FromSeconds(5);
return driver; 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 // Changes the value of a dropdown element by ID, logs the action, and verifies the result using flashline feedback
public static async Task Dropdown_Change(string fullId, string value, ChromeDriver driver, string SoakLogFile, string CamAct) public static async Task Dropdown_Change(string fullId, string value, ChromeDriver driver, string SoakLogFile, string CamAct)
@@ -101,10 +157,7 @@ namespace AiQ_GUI
await Task.Delay(500); // Wait for the change to take effect await Task.Delay(500); // Wait for the change to take effect
if (!await Checkflashline(driver, CamAct)) if (!await Checkflashline(driver, CamAct))
{
Logging.LogWarningMessage("Bad flashline after changing: " + fullId, SoakLogFile); 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 // 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 internal class SoakTest
{ {
// Main soak test loop: Randomise dropdowns, run luminance test every hour, power cycle at 7am // 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") if (CamInfo.Serial == "N/A")
CamInfo.Serial = "UNKNOWN"; // If serial is not set, set it to UNKNOWN. Cannot have N/A in file names. 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 try
{ {
driver = Selenium.OpenDriver(); 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 // Keep retrying until connected or cancelled
bool connected = false; bool connected = false;
@@ -27,13 +30,14 @@ namespace AiQ_GUI
{ {
// Attempt initial connection and navigation to setup tab // Attempt initial connection and navigation to setup tab
Selenium.GoToUrl($"http://{CamInfo.IP}", driver); Selenium.GoToUrl($"http://{CamInfo.IP}", driver);
Selenium.SwitchUser(driver);
Selenium.ClickElementByID("tabSetup", driver); Selenium.ClickElementByID("tabSetup", driver);
connected = true; connected = true;
} }
catch (Exception ex) catch (Exception ex)
{ {
Logging.LogErrorMessage($"Initial connection failed: {ex.Message}", SoakLogFile); SoakError($"Initial connection failed: {ex.Message}", SoakLogFile, CamInfo.CheckBox);
MainForm.Instance.AddToActionsList($"[{CamInfo.IP}] Initial connection failed: {ex.Message}"); MainForm.Instance.AddToActionsList($"[{CamInfo.IP}] Initial connection failed: {Level.ERROR}{ex.Message}");
// Wait 10 seconds before trying again // Wait 10 seconds before trying again
await Task.Delay(TimeSpan.FromSeconds(10), token); await Task.Delay(TimeSpan.FromSeconds(10), token);
@@ -48,7 +52,7 @@ namespace AiQ_GUI
} }
catch (Exception ex) 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; return;
} }
@@ -56,6 +60,16 @@ namespace AiQ_GUI
while (!token.IsCancellationRequested) 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; int currentHour = DateTime.Now.Hour;
// At 7am, power cycle the camera // At 7am, power cycle the camera
@@ -70,7 +84,7 @@ namespace AiQ_GUI
// Retry ping until camera responds or cancelled // Retry ping until camera responds or cancelled
while (!await Network.PingIP(CamInfo.IP) && !token.IsCancellationRequested) 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 await Task.Delay(TimeSpan.FromMinutes(1), token); // Retry after delay of 1 minute
} }
@@ -84,7 +98,7 @@ namespace AiQ_GUI
} }
catch (Exception ex) 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); Logging.LogMessage($"Hour changed to {currentHour}, running ImageCheck.", SoakLogFile);
try try
{ {
ImageCheck(driver, SoakLogFile, CamInfo.IP, CamInfo.DevPass, elementID); ImageCheck(driver, SoakLogFile, CamInfo, elementID);
} }
catch (Exception ex) catch (Exception ex)
{ {
Logging.LogErrorMessage($"ImageCheck failed: {ex.Message}", SoakLogFile); SoakError($"ImageCheck failed: {Level.ERROR}{Level.ERROR} {ex.Message}", SoakLogFile, CamInfo.CheckBox);
} }
lastHour = currentHour; lastHour = currentHour;
} }
@@ -117,7 +131,7 @@ namespace AiQ_GUI
} }
catch (Exception ex) catch (Exception ex)
{ {
Logging.LogErrorMessage($"ChangeRandomDropdown failed: {ex.Message}", SoakLogFile); SoakError($"ChangeRandomDropdown failed: {Level.WARNING} {ex.Message}", SoakLogFile, CamInfo.CheckBox);
} }
try try
@@ -138,11 +152,12 @@ namespace AiQ_GUI
// Create Test report in the same directory as the final test reports. // Create Test report in the same directory as the final test reports.
string soakLogPath = LDS.MAVPath + SoakLogFile; string soakLogPath = LDS.MAVPath + SoakLogFile;
string SoakTestPath = PDF.CreateSoakTestReport(CamInfo, MainForm.Instance.CbBxUserName.Text, DateTime.Now, soakLogPath); string SoakTestPath = PDF.CreateSoakTestReport(CamInfo, MainForm.Instance.CbBxUserName.Text, DateTime.Now, soakLogPath);
if (SoakTestPath != null) if (SoakTestPath != null)
{ {
CamInfo.TestReportLoc = SoakTestPath;
// Delete the soak test log file if the report was created successfully // Delete the soak test log file if the report was created successfully
try try
{ {
@@ -151,7 +166,7 @@ namespace AiQ_GUI
} }
catch (Exception ex) 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 // Find the final test report PDF for this camera
@@ -169,13 +184,13 @@ namespace AiQ_GUI
} }
catch (Exception ex) 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 // Link PDFs if both exist
if (!string.IsNullOrEmpty(finalTestPath) && File.Exists(finalTestPath) && !string.IsNullOrEmpty(SoakTestPath) && File.Exists(SoakTestPath)) 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 try
{ {
if (PDF.LinkPDFs(finalTestPath, SoakTestPath, outputPath)) // Delete the separate soak and final test reports if Linking was successful 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); File.Delete(SoakTestPath);
} }
else else
MainForm.Instance.AddToActionsList($"Failed to link or delete PDFs"); MainForm.Instance.AddToActionsList($"Failed to link or delete PDFs {Level.ERROR}");
} }
catch (Exception ex) 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 // 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") 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); await Task.Delay(500);
// Take bright image // 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) if (ImageBright == null)
{ {
Logging.LogWarningMessage($"Bright image is null for {controlType} at setting {SettingMinMax[0]}", SoakLogFile); 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; return;
} }
@@ -221,11 +236,11 @@ namespace AiQ_GUI
await Task.Delay(500); await Task.Delay(500);
// Take dark image // 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) if (ImageDark == null)
{ {
Logging.LogWarningMessage($"Dark image is null for {controlType} at setting {SettingMinMax[1]}", SoakLogFile); 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; return;
} }
@@ -235,9 +250,10 @@ namespace AiQ_GUI
if (Bright_Lum < Dark_Lum * 1.01) if (Bright_Lum < Dark_Lum * 1.01)
{ {
Logging.LogErrorMessage( SoakError(
$"Insufficient luminance contrast. Bright: {Bright_Lum:F2}, Dark: {Dark_Lum:F2} | Type: {controlType} | Bright Setting: {SettingMinMax[0]}, Dark Setting: {SettingMinMax[1]}", $"Insufficient luminance contrast. Bright: {Bright_Lum:F2}, Dark: {Dark_Lum:F2} | Type: {controlType} | Bright Setting: {SettingMinMax[0]}, Dark Setting: {SettingMinMax[1]}{Level.WARNING}",
SoakLogFile SoakLogFile,
CamInfo.CheckBox
); );
} }
else else
@@ -250,7 +266,7 @@ namespace AiQ_GUI
} }
// Performs a series of camera control adjustments and luminance checks to verify image settings functionality // 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.modeId, "Manual", driver, SoakLogFile, elementID.CamAct);
await Selenium.Dropdown_Change(elementID.shutterId, "1/1000", 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.irisId, "F4.0", driver, SoakLogFile, elementID.CamAct);
await Selenium.Dropdown_Change(elementID.irLevelId, "Safe", 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 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 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) public async static Task ChangeRandomDropdown(ChromeDriver driver, string SoakLogFile, ElementID elementID)
@@ -318,6 +334,12 @@ namespace AiQ_GUI
return dynamicButton; return dynamicButton;
} }
private static void SoakError(string ErrMssg, string LogFile, CheckBox CkBx)
{
Logging.LogErrorMessage(ErrMssg, LogFile);
CkBx.ForeColor = Color.Red;
}
} }
public class ElementID 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"
}
}