21 Commits

Author SHA1 Message Date
30bd2fe73c AddToAction List colours 2025-12-02 12:59:40 +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
b59d023887 In FlexiAPI SetVaxtorMinMaxPlate() Has been added + Other small changes 2025-10-21 14:04:59 +01:00
a2f86625be Final Changes to Stats Excel - This has been tested via Fake Cam Test all scenearios and published and tested 2025-10-20 10:31:58 +01:00
f4081515be This Commit contains all the changes for the new feature for S.L in regards to ExcelStats of the GUI 2025-10-17 12:06:36 +01:00
28 changed files with 1148 additions and 393 deletions

44
AiQ_GUI.Designer.cs generated
View File

@@ -144,6 +144,8 @@ namespace AiQ_GUI
BtnZoom8000 = new Button();
BtnZoomWide = new Button();
TabSettings = new TabPage();
BtnUploadWonwooSetIR = new Button();
BtnUploadWonwooSetOV = new Button();
BtnAdminStart = new Button();
BtnFirewall = new Button();
TabImages = new TabPage();
@@ -270,7 +272,7 @@ namespace AiQ_GUI
CbBxUserName.Font = new Font("Segoe UI Semibold", 10F, FontStyle.Bold);
CbBxUserName.ForeColor = SystemColors.Control;
CbBxUserName.FormattingEnabled = true;
CbBxUserName.Items.AddRange(new object[] { "Guest", "Clive", "Conor", "Charlie", "Henry", "Sam C", "Sam L", "Simon", "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.Margin = new Padding(4, 3, 4, 3);
CbBxUserName.MaxDropDownItems = 20;
@@ -1146,7 +1148,7 @@ namespace AiQ_GUI
TxBxFlexiVer.Name = "TxBxFlexiVer";
TxBxFlexiVer.Size = new Size(205, 25);
TxBxFlexiVer.TabIndex = 162;
TxBxFlexiVer.Text = "1.6.4";
TxBxFlexiVer.Text = "1.7.1";
TxBxFlexiVer.TextAlign = HorizontalAlignment.Center;
TxBxFlexiVer.TextChanged += TxBxFlexiVer_TextChanged;
//
@@ -1836,6 +1838,8 @@ namespace AiQ_GUI
// TabSettings
//
TabSettings.BackColor = Color.FromArgb(39, 37, 55);
TabSettings.Controls.Add(BtnUploadWonwooSetIR);
TabSettings.Controls.Add(BtnUploadWonwooSetOV);
TabSettings.Controls.Add(BtnAdminStart);
TabSettings.Controls.Add(BtnFirewall);
TabSettings.Controls.Add(PanelSettings);
@@ -1846,6 +1850,40 @@ namespace AiQ_GUI
TabSettings.TabIndex = 3;
TabSettings.Text = "Settings";
//
// BtnUploadWonwooSetIR
//
BtnUploadWonwooSetIR.BackColor = Color.FromArgb(70, 65, 80);
BtnUploadWonwooSetIR.FlatAppearance.BorderColor = Color.FromArgb(70, 65, 80);
BtnUploadWonwooSetIR.FlatAppearance.BorderSize = 0;
BtnUploadWonwooSetIR.FlatStyle = FlatStyle.Flat;
BtnUploadWonwooSetIR.Font = new Font("Segoe UI Semibold", 10F, FontStyle.Bold);
BtnUploadWonwooSetIR.ForeColor = SystemColors.Control;
BtnUploadWonwooSetIR.Location = new Point(210, 304);
BtnUploadWonwooSetIR.Margin = new Padding(4, 3, 4, 3);
BtnUploadWonwooSetIR.Name = "BtnUploadWonwooSetIR";
BtnUploadWonwooSetIR.Size = new Size(180, 49);
BtnUploadWonwooSetIR.TabIndex = 244;
BtnUploadWonwooSetIR.Text = "Upload Wonwoo Settings IR";
BtnUploadWonwooSetIR.UseVisualStyleBackColor = false;
BtnUploadWonwooSetIR.Click += UploadWonwooSetIR_Click;
//
// BtnUploadWonwooSetOV
//
BtnUploadWonwooSetOV.BackColor = Color.FromArgb(70, 65, 80);
BtnUploadWonwooSetOV.FlatAppearance.BorderColor = Color.FromArgb(70, 65, 80);
BtnUploadWonwooSetOV.FlatAppearance.BorderSize = 0;
BtnUploadWonwooSetOV.FlatStyle = FlatStyle.Flat;
BtnUploadWonwooSetOV.Font = new Font("Segoe UI Semibold", 10F, FontStyle.Bold);
BtnUploadWonwooSetOV.ForeColor = SystemColors.Control;
BtnUploadWonwooSetOV.Location = new Point(19, 304);
BtnUploadWonwooSetOV.Margin = new Padding(4, 3, 4, 3);
BtnUploadWonwooSetOV.Name = "BtnUploadWonwooSetOV";
BtnUploadWonwooSetOV.Size = new Size(181, 49);
BtnUploadWonwooSetOV.TabIndex = 243;
BtnUploadWonwooSetOV.Text = "Upload Wonwoo Settings OV";
BtnUploadWonwooSetOV.UseVisualStyleBackColor = false;
BtnUploadWonwooSetOV.Click += UploadWonwooSetOV_Click;
//
// BtnAdminStart
//
BtnAdminStart.BackColor = Color.FromArgb(70, 65, 80);
@@ -2211,5 +2249,7 @@ namespace AiQ_GUI
private Button BtnAdminStart;
private Button SetGodModeAll;
private Button BtnFactoryDefault;
private Button BtnUploadWonwooSetOV;
private Button BtnUploadWonwooSetIR;
}
}

View File

@@ -5,6 +5,14 @@ using System.Reflection;
namespace AiQ_GUI
{
public enum Level
{
ERROR,
WARNING,
LOG,
Success
}
public partial class MainForm : Form
{
// Classes
@@ -34,20 +42,18 @@ namespace AiQ_GUI
private async void AiQGUI_Load(object sender, EventArgs e)
{
Stopwatch stopwatch = Stopwatch.StartNew();
Task? closeProcessesTask = Windows.CloseProcesses(); // Fire and forget closing other apps
Windows.UpdateFirewall();
Task UniDataTask = Task.Run(() => Access.ReadUniData()); // Get universal data
Task<LocalDataStore> LDSWAIT = Task.Run(() => LDS.GetLDS()); // Get and deserialise LDS.json
Task<string> guiVerTask = Task.Run(() => GUIUpdate.FindGUIVersion()); // Get GUI Version
Task<LocalDataStore> LDSWAIT = Task.Run(() => LDS.GetLDS()); // Get and deserialise LDS.json
Task<string> guiVerTask = Task.Run(() => GUIUpdate.FindGUIVersion()); // Get GUI Version
Network.Initialize("admin", "admin"); // Initialise HTTP client
if (await Network.PingIP("8.8.8.8")) // Ping to check if we're online
{
if (!GoogleAPI.Setup())
AddToActionsList("Cannot setup Google API");
AddToActionsList("Cannot setup Google API", Level.WARNING);
}
else
{
@@ -69,9 +75,10 @@ namespace AiQ_GUI
// Load local data store
localDataStore = await LDSWAIT;
Logging.LogMessage("Opening GUI"); // Done after LDS to make sure directory exists.
if (localDataStore == null)
{
AddToActionsList("Could not deserialise LDS.json please help!");
AddToActionsList("Could not deserialise LDS.json please help!", Level.WARNING);
return;
}
@@ -87,9 +94,6 @@ namespace AiQ_GUI
await CheckHWOnline;
Flags.Start = false;
stopwatch.Stop();
Debug.WriteLine("RunTime " + stopwatch.Elapsed.ToString(@"hh\:mm\:ss\.ff"));
}
private void PopulateUIWithLDS(LocalDataStore lds)
@@ -103,7 +107,7 @@ namespace AiQ_GUI
CbBxIris.SelectedIndex = lds.Iris;
CbBxGain.SelectedIndex = lds.Gain;
if (lds.User == "Bradley")
if (lds.User == "Bradley" || lds.User == "Sophie")
BtnTest.Visible = true;
TxBxCheckValid(TxBxPsuIP); // Set save button color if valid
@@ -135,7 +139,7 @@ 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.
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
await TestFailed(BtnStartTest, "Camera not in test tube");
@@ -154,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.
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 ImageProcessing.ImageCheck(PicBxOV, PicBxIRF2, PicBxIRF16, LblIRImageF2, LblIRImageF16, CamOnTest); // Populates the picture boxes and checks iris changes
@@ -197,8 +201,9 @@ namespace AiQ_GUI
string LEDreply = await FlexiAPI.APIHTTPLED(CamOnTest.IP, LEDPOWER.MID); // Set LED's to medium (0x30)
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);
DateTime PCTime = DateTime.Now; // Grab PC time as close to the API as possible to pass onto PDF later
@@ -206,8 +211,8 @@ namespace AiQ_GUI
await CheckDiagsAPIPt2(); // For only final test parts
// Check module has gone to default config
CameraModules.CheckCamModule(DiagsAPI.IRmodule, LblIRModule); // IR
CameraModules.CheckCamModule(DiagsAPI.OVmodule, LblOVModule); // OV
CameraModules.CheckCamModule(DiagsAPI.IRmodule, LblIRModule, CamOnTest); // IR
CameraModules.CheckCamModule(DiagsAPI.OVmodule, LblOVModule, CamOnTest); // OV
// Check voltage and current are OK.
LED.CheckLEDs(DiagsAPI.LedVoltage, LblLEDV, "V", CameraAccessInfo.LED_V); // Voltage
@@ -218,7 +223,7 @@ namespace AiQ_GUI
// 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 (RhTxBxActions.Text.Length > 2 || PnlLbls.Controls.OfType<Label>().Any(c => c.ForeColor == Color.Red) == true)
await TestFailed(BtnStartTest, "Failed due to action box text and/or red label");// 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);
}
@@ -237,7 +242,7 @@ namespace AiQ_GUI
string LEDreply = await FlexiAPI.APIHTTPLED(CamOnTest.IP, LEDPOWER.MID); // Set LED's to medium (0x30)
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
@@ -250,8 +255,8 @@ namespace AiQ_GUI
await CheckDiagsAPIPt1();
// Check module has gone to default config
CameraModules.CheckCamModule(DiagsAPI.IRmodule, LblIRModule); // IR
CameraModules.CheckCamModule(DiagsAPI.OVmodule, LblOVModule); // OV
CameraModules.CheckCamModule(DiagsAPI.IRmodule, LblIRModule, CamOnTest); // IR
CameraModules.CheckCamModule(DiagsAPI.OVmodule, LblOVModule, CamOnTest); // OV
// Check voltage and current are OK.
LED.CheckLEDs(DiagsAPI.LedVoltage, LblLEDV, "V", CameraAccessInfo.LED_V); // Voltage
@@ -269,7 +274,9 @@ namespace AiQ_GUI
if (await DisplayQuestion($"Would you like to allocate a serial number to this camera?"))
await AllocateSerial();
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
await AllocateSerial();
@@ -278,7 +285,9 @@ namespace AiQ_GUI
await PreTestPassed();
}
else
await PreTestFailed();
{
await PreTestFailed("Diagnostic Failure");
}
}
// ***** Pass/Fails *****
@@ -288,7 +297,7 @@ namespace AiQ_GUI
string err = GoogleAPI.UpdateSpreadSheetFinalTest(CameraAccessInfo.SpreadsheetID, DiagsAPI, sshData, CamOnTest.RMANum);
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
await FlexiAPI.APIHTTPRequest("/api/purge-all", CamOnTest.IP);
@@ -297,14 +306,14 @@ namespace AiQ_GUI
{
// Turn off God mode
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\"},"))
AddToActionsList("Could not turn off God mode");
AddToActionsList("Could not turn off God mode", Level.WARNING);
Thread Thr211 = new(async () =>
{
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.Start();
@@ -322,12 +331,16 @@ namespace AiQ_GUI
// TODO make sure serial number is in CamOnTest before making PDF
// Serial number is either what came out of the camera under RMA or updated above as new serial number from next empty row in spreadsheet
PDF.CreateFinalTestReport(CamOnTest, CbBxUserName.Text, fulltestvalues, PCTime);
// Indicators to the user the test has passed
BtnStartTest.BackColor = Color.ForestGreen;
BtnStartTest.Text = "Test Passed";
PnlQuestion.Visible = false; // just in case this came from an override
Logging.LogMessage("Final Test 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)
@@ -336,8 +349,20 @@ namespace AiQ_GUI
Btn.BackColor = Color.Maroon;
Btn.Text = "Test Failed";
AddToActionsList(ErrMssg);
// TODO Track Tests 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
.OfType<Label>()
.Where(lbl => lbl.ForeColor == Color.Red) // Only include red labels
.Select(lbl => lbl.Text)); // Extract text
string FullFailureValues = RhTxBxActions.Text + Environment.NewLine + RedLbls;
Access.StatsDiags(RedLbls, RhTxBxActions.Text, CamOnTest.Model); // Log to Access database
if (await DisplayQuestion("Test failed, appeal?" + Environment.NewLine + "See Actions textbox for details."))
{
@@ -353,12 +378,6 @@ namespace AiQ_GUI
this.Refresh(); // To make sure all labels are up to date before reading them
// Joins the actions box to any red labels to use as a full failed text
string FullFailureValues = RhTxBxActions.Text + Environment.NewLine +
string.Join(Environment.NewLine, PnlLbls.Controls
.OfType<Label>()
.Where(lbl => lbl.ForeColor == Color.Red) // Only include red labels
.Select(lbl => lbl.Text)); // Extract text
Logging.LogErrorMessage(FullFailureValues);
IList<IList<object>> values = GoogleAPI.service.Spreadsheets.Values.Get(GoogleAPI.spreadsheetId_ModelInfo, "'Approval'!A1:A").Execute().Values;
@@ -373,15 +392,13 @@ namespace AiQ_GUI
List<object> oblistCD = [CamOnTest.Model, FullFailureValues];
GoogleAPI.WriteToSS(oblistCD, "'Approval'!C" + nextRow + ":D" + nextRow, GoogleAPI.spreadsheetId_ModelInfo);
//await Teams.SendMssg(Convert.ToString(nextRow), CbBxUserName.Text);
GoogleAPI.EmailApproval(Convert.ToString(nextRow), CbBxUserName.Text);
await Teams.SendMssg(Convert.ToString(nextRow), CbBxUserName.Text);
string Approved = "";
while (Approved != "TRUE")
{
await Task.Delay(1000);
values = GoogleAPI.service.Spreadsheets.Values.Get(GoogleAPI.spreadsheetId_ModelInfo, "'Approval'!B" + nextRow).Execute().Values;
if (values?.Count > 0)
@@ -414,11 +431,16 @@ namespace AiQ_GUI
BtnNo.Visible = false;
Logging.LogMessage("Pre Test Passed");
if (CamOnTest.RMANum == 0) // Yap to check if it is not a RMA
Access.Stats("Pre Tests Passed", CamOnTest.Model);
else
Access.Stats("RMA Pre Tests Passed", CamOnTest.Model);
if (await DisplayQuestion("Test passed, restart?"))
Helper.RestartApp();
}
private async Task PreTestFailed()
private async Task PreTestFailed(string ErrMssg)
{
BtnPreTest.BackColor = Color.Maroon; // Indicators to the user the test has failed
BtnPreTest.Text = "Test Failed";
@@ -426,6 +448,19 @@ namespace AiQ_GUI
BtnNo.Visible = false;
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
.OfType<Label>()
.Where(lbl => lbl.ForeColor == Color.Red) // Only include red labels
.Select(lbl => lbl.Text)); // Extract text
string FullFailureValues = RhTxBxActions.Text + Environment.NewLine + RedLbls;
Access.StatsDiags(RedLbls, RhTxBxActions.Text, CamOnTest.Model); // Log to Access database
if (await DisplayQuestion("Test failed, restart?" + Environment.NewLine + "See Actions textbox for details."))
Helper.RestartApp();
}
@@ -468,10 +503,13 @@ namespace AiQ_GUI
else if (RegexCache.MACRegex().IsMatch(DiagsAPI.MAC)) // Is a valid MAC, but not NVIDIA
{
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
{
lblMac.ForeColor = Color.Red;
AddToActionsList($"{DiagsAPI.MAC} not recognised as a MAC address", Level.ERROR);
}
// Check timestamp
DateTime dateTime = new(1970, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc);
@@ -534,7 +572,7 @@ namespace AiQ_GUI
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;
}
else if (!GoogleAPI.CheckWIP(DiagsAPI.serialNumber, CameraAccessInfo.SpreadsheetID)) // Check WIP column in serial number register, if not ticked then RMA
@@ -560,7 +598,7 @@ namespace AiQ_GUI
}
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 { }
@@ -593,11 +631,7 @@ namespace AiQ_GUI
double CPUround = Math.Round(DiagsAPI.CPUusage); // Check CPU usage isn't near max
LblCPUusage.Text += CPUround + "%";
if (CPUround < 98 && CPUround > 50)
{
LblCPUusage.ForeColor = Color.LightGreen;
}
else if (CPUround <= 50)
if (CPUround <= 50)
{
LblCPUusage.Text += " Unexpectedly low CPU usage";
LblCPUusage.ForeColor = Color.Red;
@@ -607,6 +641,10 @@ namespace AiQ_GUI
LblCPUusage.Text += " Unexpectedly high CPU usage";
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
if (CameraAccessInfo.VaxtorLic == false && DiagsAPI.licenses.raptorKeyID == "Not Licensed" || CameraAccessInfo.VaxtorLic == true && DiagsAPI.licenses.raptorKeyID != "Not Licensed")
@@ -619,7 +657,11 @@ namespace AiQ_GUI
string ProdcutKeyID = await DisplayInput("What is the Key ID?", false);
if (RegexCache.VaxtorRegex().IsMatch(ProdcutKeyID)) // Means they chose the 'I don't know' option or isn't valid Key ID
{
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");
}
DiagsAPI.licenses.raptorKeyID = TxBxProductKey.Text;
lblVaxtor.Text += DiagsAPI.licenses.raptorKeyID;
@@ -652,23 +694,23 @@ namespace AiQ_GUI
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;
}
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;
}
// Set serial and model into camera
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))
{
AddToActionsList("Could not set model or serial numbers into camera.");
await PreTestFailed();
AddToActionsList("Could not set model or serial numbers into camera.",Level.ERROR);
await PreTestFailed("Failed To Set Model Or Serial Number");
}
DiagsAPI.modelNumber = CamOnTest.Model; // Update Diags and labels
@@ -678,7 +720,6 @@ namespace AiQ_GUI
lblSerial.Text += DiagsAPI.serialNumber;
Printer.ZebraIP = localDataStore.ZebraIP;
Printer.PrintGBLbl(); // Print GB label
Printer.PrintSerialLbl(CamOnTest.Model, NewSerial, CameraAccessInfo.Processor); // Print model/serial label
}
@@ -722,7 +763,7 @@ namespace AiQ_GUI
private async void BtnFindCams_Click(object sender, EventArgs e)
{
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;
CbBxFoundCams.Items.Clear();
soakCameraList.Clear();
@@ -755,7 +796,9 @@ namespace AiQ_GUI
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;
}
@@ -820,7 +863,7 @@ namespace AiQ_GUI
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;
}
@@ -838,7 +881,7 @@ namespace AiQ_GUI
else
{
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();
@@ -877,26 +920,43 @@ namespace AiQ_GUI
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.BackColor = Color.Green;
}
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 *****
public void AddToActionsList(string Mssg, bool IsErr = true)
public void AddToActionsList(string Mssg, Level Lvl = Level.LOG)
{
if (IsErr)
if (Lvl == Level.ERROR)
{
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);
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.SelectionColor = SystemColors.Control;
RhTxBxActions.ScrollToCaret();
}
@@ -923,7 +983,7 @@ namespace AiQ_GUI
if (CbBxFoundCams.BackColor != BtnColour || CbBxFoundCams.Text.Contains("Found"))
TSC = SetInvalid("Select camera IP address.");
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
if (CbBxUserName.Text == "Select Operator to Begin Test" || CbBxUserName.Text.Length < 2)
@@ -1072,7 +1132,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.
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
BtnSet211.Text = "Set to DHCP";
}
@@ -1097,11 +1157,11 @@ namespace AiQ_GUI
{
Network.Initialize("developer", SCL.DevPass); // Ensure network is initialized to the right camera
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)
{
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
}
}
@@ -1123,12 +1183,12 @@ namespace AiQ_GUI
try
{
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);
Instance.AddToActionsList($"Setting God mode for camera {SCL.IP} to {newGodModeValue}", false);
string RESP = await FlexiAPI.HTTP_Update("GLOBAL--FlexiApplication", SCL.IP, GOD_JSON);
Instance.AddToActionsList($"Setting God mode for camera {SCL.IP} to {newGodModeValue}", Level.LOG);
}
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);
}
}
@@ -1256,7 +1316,7 @@ namespace AiQ_GUI
}
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;
}
@@ -1264,12 +1324,16 @@ namespace AiQ_GUI
{
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);
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");
@@ -1278,18 +1342,14 @@ namespace AiQ_GUI
await DisplayOK("Please wait at least a minute before turning off the unit.");
}
else if (VaxtorLicResp.error != string.Empty)
{
RhTxBxCode.AppendText(VaxtorLicResp.error);
}
else
{
AddToActionsList($"Error reading JSON - {ALresponse}");
AddToActionsList($"Error reading JSON - {ALresponse}", Level.ERROR);
}
}
catch
{
AddToActionsList($"Error reading JSON - {ALresponse}");
AddToActionsList($"Error reading JSON - {ALresponse}", Level.ERROR);
return;
}
}
@@ -1440,6 +1500,16 @@ namespace AiQ_GUI
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)
{
if (TxBxSerialPrint.Text == "K ") // If at default then remove the dashes ready for user to put in number
@@ -1515,8 +1585,8 @@ namespace AiQ_GUI
CancellationTokenSource cts = new();
soakCtsList.Add(cts);
soakTasks.Add(SoakTest.StartSoak(SCL, cts.Token));
await Task.Delay(5000);
soakTasks.Add(SoakTest.StartSoak(SCL, SCL.CheckBox, cts.Token));
await Task.Delay(10000);
}
}
else
@@ -1527,14 +1597,51 @@ namespace AiQ_GUI
cts.Cancel();
soakCtsList.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)
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.
await CameraModules.FactoryResetModules(SCL.IP);
string GOD = await FlexiAPI.HTTP_Update("GLOBAL--FlexiApplication", SCL.IP, GOD_JSON);
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);
}
}
}
@@ -1582,59 +1689,7 @@ namespace AiQ_GUI
private async void BtnUploadBlob_Click(object sender, EventArgs e)
{
BtnUploadBlob.BackColor = BtnColour;
const string networkFolderPath = @"G:\Shared drives\MAV Production\MAV_146_AiQ_Mk2\Flexi";
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);
}
FlexiAPI.UploadBlob(soakCameraList);
BtnUploadBlob.BackColor = Color.Green;
}
@@ -1654,16 +1709,37 @@ namespace AiQ_GUI
}
// ***** Test & Debug *****
private async void BtnTest_Click(object sender, EventArgs e)
private void BtnTest_Click(object sender, EventArgs e)
{
Stopwatch stopWatchTest = Stopwatch.StartNew();
string[,] Vaxtor_JSON = { { "propMinCharHeight", "14" }, { "propMinGlobalConfidence", "30" } };
string response = await FlexiAPI.HTTP_Update("RaptorOCR", "192.168.0.100", Vaxtor_JSON);
AddToActionsList(response);
//StatsExcel excelExporter = new();
//excelExporter.ExportDatabaseToExcel();
// /api/config-ids - For getting all available config IDs
// Make every log file in the soak log directory into a soak test report PDF
//var files = from file in Directory.EnumerateFiles("C:\\ProgramData\\MAV\\AiQ_GUI") select file;
//foreach (var file in files)
//{
// 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);
// }
//}
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>
</metadata>
<metadata name="TimerDDC.TrayLocation" type="System.Drawing.Point, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
<value>690, 19</value>
<value>531, 18</value>
</metadata>
<metadata name="timerTypeIP.TrayLocation" type="System.Drawing.Point, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
<value>305, 17</value>

View File

@@ -2,7 +2,7 @@
<PropertyGroup>
<OutputType>WinExe</OutputType>
<TargetFramework>net9.0-windows7.0</TargetFramework>
<TargetFramework>net9.0-windows10.0.17763.0</TargetFramework>
<Nullable>enable</Nullable>
<UseWindowsForms>true</UseWindowsForms>
<ImplicitUsings>enable</ImplicitUsings>
@@ -16,7 +16,7 @@
<Product>AiQ GUI</Product>
<Authors>MAV Systems Ltd</Authors>
<PackageId>AiQ GUI</PackageId>
<Version>3.14.0</Version>
<Version>4.5.0</Version>
<Description>A GUI to control and test the AiQ</Description>
<Copyright>MAV Systems Ltd 2025</Copyright>
<PackageIcon>MAV - Plain - Blue.png</PackageIcon>
@@ -26,14 +26,25 @@
<ProduceReferenceAssembly>False</ProduceReferenceAssembly>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<SignAssembly>False</SignAssembly>
<StartupObject>AiQ_GUI.Program</StartupObject>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
<Optimize>True</Optimize>
<DefineConstants>$(DefineConstants);_PUBLISH_CHROMEDRIVER</DefineConstants>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x86'">
<Optimize>True</Optimize>
<DefineConstants>$(DefineConstants);_PUBLISH_CHROMEDRIVER</DefineConstants>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'">
<DefineConstants>$(DefineConstants);_PUBLISH_CHROMEDRIVER</DefineConstants>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x86'">
<DefineConstants>$(DefineConstants);_PUBLISH_CHROMEDRIVER</DefineConstants>
</PropertyGroup>
<ItemGroup>
@@ -49,11 +60,11 @@
<PackageReference Include="Google.Apis.Sheets.v4" Version="1.70.0.3819" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.4" />
<PackageReference Include="PDFsharp-MigraDoc-gdi" Version="6.2.2" />
<PackageReference Include="Selenium.Support" Version="4.37.0" />
<PackageReference Include="Selenium.WebDriver" Version="4.37.0" />
<PackageReference Include="Selenium.WebDriver.ChromeDriver" Version="141.0.7390.7800" />
<PackageReference Include="SSH.NET" Version="2025.0.0" />
<PackageReference Include="System.Data.OleDb" Version="9.0.10" />
<PackageReference Include="Selenium.Support" Version="4.38.0" />
<PackageReference Include="Selenium.WebDriver" Version="4.38.0" />
<PackageReference Include="Selenium.WebDriver.ChromeDriver" Version="142.0.7444.6100" />
<PackageReference Include="SSH.NET" Version="2025.1.0" />
<PackageReference Include="System.Data.OleDb" Version="10.0.0" />
</ItemGroup>
<ItemGroup>

View File

@@ -4,11 +4,11 @@ namespace AiQ_GUI
internal class CameraModules
{
// Chack camera modules are in default state according to what the diagnostics API.
public static void CheckCamModule(Module CamMod, Label Lbl)
public static void CheckCamModule(Module CamMod, Label Lbl, Camera CamOnTest)
{
if (CamMod == null || Lbl == null)
{
MainForm.Instance.AddToActionsList("Camera module or label was null in CheckCamModule.");
MainForm.Instance.AddToActionsList("Camera module or label was null in CheckCamModule.", Level.ERROR);
return;
}
@@ -17,7 +17,21 @@ namespace AiQ_GUI
if (CamMod.zoom != 0) // Check camera module is at full wide
errMssg += $"Zoom not at 0 - {CamMod.zoom} ";
if (CamMod.firmwareVer != UniversalData.WonwooFirmware) // Check camera module firmware version is up to date.
bool LessTanOrEqualTo = false;
try
{
LessTanOrEqualTo = Convert.ToDouble(CamMod.firmwareVer) <= Convert.ToDouble(UniversalData.WonwooFirmware);
}
catch
{
MainForm.Instance.AddToActionsList($"{CamMod.firmwareVer} or {UniversalData.WonwooFirmware} could not be converted to a double", Level.ERROR);
}
if (CamOnTest.RMANum > 0 && LessTanOrEqualTo)
errMssg += $"Firmware: {CamMod.firmwareVer} should be less than or equal to {UniversalData.WonwooFirmware} for RMA {CamOnTest.RMANum}";
else if ((CamOnTest.RMANum == 0 || CamOnTest.RMANum == -1) && CamMod.firmwareVer != UniversalData.WonwooFirmware)
errMssg += $"Firmware: {CamMod.firmwareVer} should be {UniversalData.WonwooFirmware} ";
if (CamMod.expMode != 0) // Auto 0=0x00
@@ -41,7 +55,7 @@ namespace AiQ_GUI
}
catch (Exception ex)
{
MainForm.Instance.AddToActionsList("Exception in CheckCamModule: " + ex.Message);
MainForm.Instance.AddToActionsList("Exception in CheckCamModule: " + ex.Message, Level.ERROR);
}
}
@@ -51,7 +65,7 @@ namespace AiQ_GUI
{
if (Shutter.SelectedIndex == -1 || Iris.SelectedIndex == -1 || Gain.SelectedIndex == -1)
{
MainForm.Instance.AddToActionsList("Shutter, Iris and Gain need selecting in images tab.");
MainForm.Instance.AddToActionsList("Shutter, Iris and Gain need selecting in images tab.", Level.WARNING);
return;
}
@@ -61,7 +75,7 @@ namespace AiQ_GUI
if (ShutterVISCA.Contains("ERROR") || IrisVISCA.Contains("ERROR") || GainVISCA.Contains("ERROR"))
{
MainForm.Instance.AddToActionsList("Problem with selected SIG values");
MainForm.Instance.AddToActionsList("Problem with selected SIG values", Level.ERROR);
return;
}
@@ -71,7 +85,7 @@ namespace AiQ_GUI
string OneshotReply = await FlexiAPI.APIHTTPVISCA(IPAddress, "8101041801FF", true); // Oneshot auto focus
if (!ShutterReply.Contains("41") || !IrisReply.Contains("41") || !GainReply.Contains("41") || !OneshotReply.Contains("41"))
MainForm.Instance.AddToActionsList("Could not set Shutter, Iris, Gain correctly" + Environment.NewLine + "Shutter: " + ShutterReply + Environment.NewLine + "Iris: " + IrisReply + Environment.NewLine + "Gain: " + GainReply + Environment.NewLine + "Oneshot: " + OneshotReply);
MainForm.Instance.AddToActionsList("Could not set Shutter, Iris, Gain correctly" + Environment.NewLine + "Shutter: " + ShutterReply + Environment.NewLine + "Iris: " + IrisReply + Environment.NewLine + "Gain: " + GainReply + Environment.NewLine + "Oneshot: " + OneshotReply, Level.ERROR);
}
// Sets back to the latest factory defaults CSV that is in Flexi.
@@ -83,7 +97,7 @@ namespace AiQ_GUI
await Task.WhenAll(IRReply, OVReply);
if (IRReply.Result != "Factory reset OK." || OVReply.Result != "Factory reset OK.")
MainForm.Instance.AddToActionsList($"Could not reset camera modules to factory default.{Environment.NewLine}{IRReply}{Environment.NewLine}{OVReply}");
MainForm.Instance.AddToActionsList($"Could not reset camera modules to factory default.{Environment.NewLine}{IRReply}{Environment.NewLine}{OVReply}", Level.ERROR);
}
public static string BuildVISCACommand(string command, int hexValue)

View File

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

View File

@@ -26,7 +26,7 @@ namespace AiQ_GUI
HttpResponseMessage response = await httpClient.GetAsync(requestUrl);
if (!response.IsSuccessStatusCode)
{
MainForm.Instance.AddToActionsList($"No success from {requestUrl} replied {response.StatusCode}");
MainForm.Instance.AddToActionsList($"No success from {requestUrl} replied {response.StatusCode}{Level.ERROR}");
return null;
}
@@ -34,7 +34,7 @@ namespace AiQ_GUI
if (imageBytes.Length == 0) // Check if the imageBytes is empty
{
MainForm.Instance.AddToActionsList($"No image data received from {requestUrl}");
MainForm.Instance.AddToActionsList($"No image data received from {requestUrl}{Level.ERROR}");
return null;
}
@@ -43,7 +43,7 @@ namespace AiQ_GUI
CvInvoke.Imdecode(imageBytes, ImreadModes.AnyColor, mat);
if (mat.IsEmpty)
{
MainForm.Instance.AddToActionsList("Failed to decode image with Emgu CV.");
MainForm.Instance.AddToActionsList($"Failed to decode image with Emgu CV.{Level.ERROR}");
return null;
}
@@ -70,12 +70,12 @@ namespace AiQ_GUI
}
catch (HttpRequestException ex)
{
MainForm.Instance.AddToActionsList($"HTTP error: {ex.Message}");
MainForm.Instance.AddToActionsList($"HTTP error: {ex.Message}{Level.ERROR}");
return null;
}
catch (Exception ex)
{
MainForm.Instance.AddToActionsList($"Error processing image: {ex.Message}");
MainForm.Instance.AddToActionsList($"Error processing image: {ex.Message} {Level.ERROR}");
return null;
}
}
@@ -123,7 +123,7 @@ namespace AiQ_GUI
}
catch (Exception ex)
{
MainForm.Instance.AddToActionsList($"Error awaiting Colour snapshot: {ex.Message}");
MainForm.Instance.AddToActionsList($"Error awaiting Colour snapshot: {ex.Message} {Level.ERROR}");
return;
}
@@ -146,7 +146,7 @@ namespace AiQ_GUI
}
catch (Exception ex)
{
MainForm.Instance.AddToActionsList($"Error calculating luminance: {ex.Message}");
MainForm.Instance.AddToActionsList($"Error calculating luminance: {ex.Message} {Level.ERROR}");
return;
}
}

View File

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

View File

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

View File

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

View File

@@ -25,7 +25,7 @@ namespace AiQ_GUI
}
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 null;
@@ -75,7 +75,7 @@ namespace AiQ_GUI
}
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)}"));
@@ -170,7 +170,7 @@ namespace AiQ_GUI
}
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);
@@ -323,7 +323,7 @@ namespace AiQ_GUI
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;
}
@@ -378,7 +378,7 @@ namespace AiQ_GUI
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;
}
@@ -387,7 +387,7 @@ namespace AiQ_GUI
}
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",
"version": "1.6.4",
"revision": "bf16134",
"version": "1.7.1",
"revision": "4b63df5",
"serialNumber": "K1005001",
"modelNumber": "AB12CD",
"MAC": "3C:6D:66:0A:BA:18",

View File

@@ -13,7 +13,7 @@ namespace AiQ_GUI
public class FakeCamera
{
public const string JSONLoc = "C:\\Users\\BradleyBorn\\OneDrive - MAV Systems Ltd\\Desktop\\AIQ_GUI_TEST\\FakeCameraGood\\";
public const string JSONLoc = "C:\\Users\\BradleyBorn\\OneDrive - MAV Systems Ltd\\Desktop\\AiQ_GUI\\AiQ_GUI\\FakeCamera\\";
public static bool Snapshot = false;
private HttpListener listener;
@@ -227,8 +227,8 @@ namespace AiQ_GUI
else if (path.Equals("/Infrared-camera-control") || path.Equals("/Colour-camera-control")) // Fixed
{
string viscaReply = "9041FF9051FF";
if (!RegexCache.VISCARegex().IsMatch(context.Request.QueryString["commandHex"]))
//MainForm.Instance.AddToActionsList($"VISCA Command Received: {context.Request.QueryString["commandHex"]}");
if (!RegexCache.VISCAAPIRegex().IsMatch(context.Request.QueryString["commandHex"]))
viscaReply = "9060FF";
await HTTPReplySetup(context, viscaReply, 200, "text/plain");
@@ -293,7 +293,7 @@ namespace AiQ_GUI
{
await HTTPReplySetup(context, "Update successful.", 200, "text/plain");
}
else if (fo.Id == "Internal Config")
else if (fo.Id == "GLOBAL--FlexiApplication")
{
string SerialNumber = "";
string ModelNumber = "";

View File

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

View File

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

View File

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

View File

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

10
LDS.cs
View File

@@ -17,15 +17,10 @@ namespace AiQ_GUI
try
{
if (!Directory.Exists(MAVPath)) // Check the AiQ folder exists in ProgramData and if it doesn't then create it
{
Directory.CreateDirectory(MAVPath);
}
if (!File.Exists(MAVPath + LDSFileName)) // Check the LDS file exists and if it doesn't then create it
{
// Save a blank version of the JSON
File.WriteAllText(MAVPath + LDSFileName, DefaultJSON);
}
File.WriteAllText(MAVPath + LDSFileName, DefaultJSON); // Save a blank version of the JSON
StreamReader file = new(MAVPath + LDSFileName);
string Content = file.ReadToEnd();
@@ -35,6 +30,7 @@ namespace AiQ_GUI
}
catch // If file can't deserialise
{
MainForm.Instance.AddToActionsList($"Error loading Local Data Store{Level.WARNING}");
return null; // Return null to indicate failure
}
}
@@ -49,7 +45,7 @@ namespace AiQ_GUI
}
catch (Exception ex)
{
MainForm.Instance.AddToActionsList($"Error saving Local Data Store: {ex.Message}");
MainForm.Instance.AddToActionsList($"Error saving Local Data Store: {ex.Message}{Level.WARNING}");
}
}
}

View File

@@ -43,7 +43,7 @@ namespace AiQ_GUI
}
catch (Exception ex)
{
Debug.WriteLine($"Error logging message: {ex.Message}");
MainForm.Instance.AddToActionsList($"Error logging message: {ex.Message}{Level.ERROR}");
}
}
}

View File

@@ -4,7 +4,7 @@ namespace AiQ_GUI
{
class Access
{
const string connString = @"Provider=Microsoft.ACE.OLEDB.12.0;Data Source=G:\Shared drives\MAV Production GUI's\AiQ\GUI's\AiQ_Final_Test.accdb;Persist Security Info=False;OLE DB Services=-1;";
public const string connString = @"Provider=Microsoft.ACE.OLEDB.12.0;Data Source=G:\Shared drives\MAV Production GUI's\AiQ\GUI's\AiQ_Final_Test.accdb;Persist Security Info=False;OLE DB Services=-1;";
// Reads camera model numbers and descriptions from the database, sorts them alphabetically by model number (except "AB12CD", which appears last), and formats each entry as "ModelNumber - Description".
public static string[] ReadCamTypes()
@@ -101,6 +101,68 @@ namespace AiQ_GUI
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

View File

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

269
Microsoft/StatsExcel.cs Normal file
View File

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

View File

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

View File

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

13
PDF.cs
View File

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

View File

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

View File

@@ -7,7 +7,7 @@ namespace AiQ_GUI
internal class SoakTest
{
// Main soak test loop: Randomise dropdowns, run luminance test every hour, power cycle at 7am
public static async Task StartSoak(Camera CamInfo, CancellationToken token)
public static async Task StartSoak(Camera CamInfo, CheckBox CkBx, CancellationToken token)
{
if (CamInfo.Serial == "N/A")
CamInfo.Serial = "UNKNOWN"; // If serial is not set, set it to UNKNOWN. Cannot have N/A in file names.
@@ -18,6 +18,7 @@ namespace AiQ_GUI
try
{
driver = Selenium.OpenDriver();
await Task.Delay(1000); // Small delay to ensure driver is ready
// Keep retrying until connected or cancelled
bool connected = false;
@@ -27,13 +28,14 @@ namespace AiQ_GUI
{
// Attempt initial connection and navigation to setup tab
Selenium.GoToUrl($"http://{CamInfo.IP}", driver);
Selenium.SwitchUser(driver);
Selenium.ClickElementByID("tabSetup", driver);
connected = true;
}
catch (Exception ex)
{
Logging.LogErrorMessage($"Initial connection failed: {ex.Message}", SoakLogFile);
MainForm.Instance.AddToActionsList($"[{CamInfo.IP}] Initial connection failed: {ex.Message}");
SoakError($"Initial connection failed: {ex.Message}", SoakLogFile, CamInfo.CheckBox);
MainForm.Instance.AddToActionsList($"[{CamInfo.IP}] Initial connection failed: {Level.ERROR}{ex.Message}");
// Wait 10 seconds before trying again
await Task.Delay(TimeSpan.FromSeconds(10), token);
@@ -48,7 +50,7 @@ namespace AiQ_GUI
}
catch (Exception ex)
{
Logging.LogErrorMessage($"Failed to get element IDs: {ex.Message}", SoakLogFile);
SoakError($"Failed to get element IDs: {Level.ERROR} {ex.Message}", SoakLogFile, CamInfo.CheckBox);
return;
}
@@ -70,7 +72,7 @@ namespace AiQ_GUI
// Retry ping until camera responds or cancelled
while (!await Network.PingIP(CamInfo.IP) && !token.IsCancellationRequested)
{
Logging.LogErrorMessage($"Camera did not respond after restart.", SoakLogFile);
SoakError($"Camera did not respond after restart.{Level.ERROR}", SoakLogFile, CamInfo.CheckBox);
await Task.Delay(TimeSpan.FromMinutes(1), token); // Retry after delay of 1 minute
}
@@ -84,7 +86,7 @@ namespace AiQ_GUI
}
catch (Exception ex)
{
Logging.LogErrorMessage($"Error during power cycle: {ex.Message}", SoakLogFile);
SoakError($"Error during power cycle: {Level.ERROR} {ex.Message}", SoakLogFile, CamInfo.CheckBox);
}
}
@@ -94,11 +96,11 @@ namespace AiQ_GUI
Logging.LogMessage($"Hour changed to {currentHour}, running ImageCheck.", SoakLogFile);
try
{
ImageCheck(driver, SoakLogFile, CamInfo.IP, CamInfo.DevPass, elementID);
ImageCheck(driver, SoakLogFile, CamInfo, elementID);
}
catch (Exception ex)
{
Logging.LogErrorMessage($"ImageCheck failed: {ex.Message}", SoakLogFile);
SoakError($"ImageCheck failed: {Level.ERROR}{Level.ERROR} {ex.Message}", SoakLogFile, CamInfo.CheckBox);
}
lastHour = currentHour;
}
@@ -117,7 +119,7 @@ namespace AiQ_GUI
}
catch (Exception ex)
{
Logging.LogErrorMessage($"ChangeRandomDropdown failed: {ex.Message}", SoakLogFile);
SoakError($"ChangeRandomDropdown failed: {Level.WARNING} {ex.Message}", SoakLogFile, CamInfo.CheckBox);
}
try
@@ -142,6 +144,8 @@ namespace AiQ_GUI
if (SoakTestPath != null)
{
CamInfo.TestReportLoc = SoakTestPath;
// Delete the soak test log file if the report was created successfully
try
{
@@ -150,7 +154,7 @@ namespace AiQ_GUI
}
catch (Exception ex)
{
Logging.LogErrorMessage($"Failed to delete soak log file: {ex.Message}{Environment.NewLine}Please delete if you find this.", SoakLogFile);
SoakError($"Failed to delete soak log file: {ex.Message}{Environment.NewLine}Please delete if you find this.", SoakLogFile, CamInfo.CheckBox);
}
// Find the final test report PDF for this camera
@@ -168,13 +172,13 @@ namespace AiQ_GUI
}
catch (Exception ex)
{
MainForm.Instance.AddToActionsList($"Failed to find final test report: {ex.Message}");
MainForm.Instance.AddToActionsList($"Failed to find final test report: {Level.ERROR}{ex.Message}");
}
// Link PDFs if both exist
if (!string.IsNullOrEmpty(finalTestPath) && File.Exists(finalTestPath) && !string.IsNullOrEmpty(SoakTestPath) && File.Exists(SoakTestPath))
{
string outputPath = finalTestDir + $"Final&SoakTestReport_{CamInfo.Model}_{CamInfo.Serial}_{DateTime.Now.ToString("dd-MM-yyyy_HH-mm-ss")}.pdf";
string outputPath = finalTestDir + $"Final&SoakTestReport_{CamInfo.Model}_{CamInfo.Serial}_{DateTime.Now.ToString("dd-MM-yyyy_HH-mm-ss")}.pdf{Level.Success}";
try
{
if (PDF.LinkPDFs(finalTestPath, SoakTestPath, outputPath)) // Delete the separate soak and final test reports if Linking was successful
@@ -184,7 +188,7 @@ namespace AiQ_GUI
File.Delete(SoakTestPath);
}
else
MainForm.Instance.AddToActionsList($"Failed to link or delete PDFs");
MainForm.Instance.AddToActionsList($"Failed to link or delete PDFs {Level.ERROR}");
}
catch (Exception ex)
{
@@ -196,7 +200,7 @@ namespace AiQ_GUI
}
// Capture's bright and dark images, then compares their luminance to verify sufficient contrast and adds results to the log
public static async Task LuminescenceMean(string FullID, ChromeDriver driver, string[] SettingMinMax, string SoakLogFile, string IP, string DevPass, string CamAct)
public static async Task LuminescenceMean(string FullID, ChromeDriver driver, string[] SettingMinMax, string SoakLogFile, Camera CamInfo, string CamAct)
{
string controlType = FullID.Split('_')[0]; // Extract control type from FullID (e.g. "Shutter_1234" → "Shutter")
@@ -206,11 +210,11 @@ namespace AiQ_GUI
await Task.Delay(500);
// Take bright image
Image ImageBright = await ImageProcessing.GetProcessedImage("Infrared", IP, DevPass);
Image ImageBright = await ImageProcessing.GetProcessedImage("Infrared", CamInfo.IP, CamInfo.DevPass);
if (ImageBright == null)
{
Logging.LogWarningMessage($"Bright image is null for {controlType} at setting {SettingMinMax[0]}", SoakLogFile);
MainForm.Instance.AddToActionsList($"Bright image is null for {controlType} at setting {SettingMinMax[0]}");
MainForm.Instance.AddToActionsList($"Bright image is null for {controlType} at setting {SettingMinMax[0]}{Level.WARNING}");
return;
}
@@ -220,11 +224,11 @@ namespace AiQ_GUI
await Task.Delay(500);
// Take dark image
Image ImageDark = await ImageProcessing.GetProcessedImage("Infrared", IP, DevPass);
Image ImageDark = await ImageProcessing.GetProcessedImage("Infrared", CamInfo.IP, CamInfo.DevPass);
if (ImageDark == null)
{
Logging.LogWarningMessage($"Dark image is null for {controlType} at setting {SettingMinMax[1]}", SoakLogFile);
MainForm.Instance.AddToActionsList($"Dark image is null for {controlType} at setting {SettingMinMax[1]}");
MainForm.Instance.AddToActionsList($"Dark image is null for {controlType} at setting {SettingMinMax[1]}{Level.WARNING}");
return;
}
@@ -234,9 +238,10 @@ namespace AiQ_GUI
if (Bright_Lum < Dark_Lum * 1.01)
{
Logging.LogErrorMessage(
$"Insufficient luminance contrast. Bright: {Bright_Lum:F2}, Dark: {Dark_Lum:F2} | Type: {controlType} | Bright Setting: {SettingMinMax[0]}, Dark Setting: {SettingMinMax[1]}",
SoakLogFile
SoakError(
$"Insufficient luminance contrast. Bright: {Bright_Lum:F2}, Dark: {Dark_Lum:F2} | Type: {controlType} | Bright Setting: {SettingMinMax[0]}, Dark Setting: {SettingMinMax[1]}{Level.WARNING}",
SoakLogFile,
CamInfo.CheckBox
);
}
else
@@ -249,7 +254,7 @@ namespace AiQ_GUI
}
// Performs a series of camera control adjustments and luminance checks to verify image settings functionality
public async static void ImageCheck(ChromeDriver driver, string SoakLogFile, string IP, string DevPass, ElementID elementID)
public async static void ImageCheck(ChromeDriver driver, string SoakLogFile, Camera CamInfo, ElementID elementID)
{
await Selenium.Dropdown_Change(elementID.modeId, "Manual", driver, SoakLogFile, elementID.CamAct);
await Selenium.Dropdown_Change(elementID.shutterId, "1/1000", driver, SoakLogFile, elementID.CamAct);
@@ -257,11 +262,11 @@ namespace AiQ_GUI
await Selenium.Dropdown_Change(elementID.irisId, "F4.0", driver, SoakLogFile, elementID.CamAct);
await Selenium.Dropdown_Change(elementID.irLevelId, "Safe", driver, SoakLogFile, elementID.CamAct);
await LuminescenceMean(elementID.shutterId, driver, ["1/100", "1/10000"], SoakLogFile, IP, DevPass, elementID.CamAct); // Check Shutter goes from min to max
await LuminescenceMean(elementID.shutterId, driver, ["1/100", "1/10000"], SoakLogFile, CamInfo, elementID.CamAct); // Check Shutter goes from min to max
await Selenium.Dropdown_Change(elementID.shutterId, "1/1000", driver, SoakLogFile, elementID.CamAct); // Reset to default
await LuminescenceMean(elementID.irisId, driver, ["F2.0", "F16"], SoakLogFile, IP, DevPass, elementID.CamAct); // Check iris goes from min to max
await LuminescenceMean(elementID.irisId, driver, ["F2.0", "F16"], SoakLogFile, CamInfo, elementID.CamAct); // Check iris goes from min to max
await Selenium.Dropdown_Change(elementID.irisId, "F4.0", driver, SoakLogFile, elementID.CamAct); // Reset to default
await LuminescenceMean(elementID.gainId, driver, ["20dB", "0dB"], SoakLogFile, IP, DevPass, elementID.CamAct); // Check gain goes from min to max
await LuminescenceMean(elementID.gainId, driver, ["20dB", "0dB"], SoakLogFile, CamInfo, elementID.CamAct); // Check gain goes from min to max
}
public async static Task ChangeRandomDropdown(ChromeDriver driver, string SoakLogFile, ElementID elementID)
@@ -317,6 +322,12 @@ namespace AiQ_GUI
return dynamicButton;
}
private static void SoakError(string ErrMssg, string LogFile, CheckBox CkBx)
{
Logging.LogErrorMessage(ErrMssg, LogFile);
CkBx.ForeColor = Color.Red;
}
}
public class ElementID