15 Commits

26 changed files with 860 additions and 526 deletions

43
AiQ_GUI.Designer.cs generated
View File

@@ -144,6 +144,8 @@ namespace AiQ_GUI
BtnZoom8000 = new Button(); BtnZoom8000 = new Button();
BtnZoomWide = new Button(); BtnZoomWide = new Button();
TabSettings = new TabPage(); TabSettings = new TabPage();
BtnUploadWonwooSetIR = new Button();
BtnUploadWonwooSetOV = new Button();
BtnAdminStart = new Button(); BtnAdminStart = new Button();
BtnFirewall = new Button(); BtnFirewall = new Button();
TabImages = new TabPage(); TabImages = new TabPage();
@@ -270,7 +272,7 @@ namespace AiQ_GUI
CbBxUserName.Font = new Font("Segoe UI Semibold", 10F, FontStyle.Bold); CbBxUserName.Font = new Font("Segoe UI Semibold", 10F, FontStyle.Bold);
CbBxUserName.ForeColor = SystemColors.Control; CbBxUserName.ForeColor = SystemColors.Control;
CbBxUserName.FormattingEnabled = true; 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.Location = new Point(10, 262);
CbBxUserName.Margin = new Padding(4, 3, 4, 3); CbBxUserName.Margin = new Padding(4, 3, 4, 3);
CbBxUserName.MaxDropDownItems = 20; CbBxUserName.MaxDropDownItems = 20;
@@ -587,6 +589,7 @@ namespace AiQ_GUI
BtnTest.TabIndex = 189; BtnTest.TabIndex = 189;
BtnTest.Text = "Test"; BtnTest.Text = "Test";
BtnTest.UseVisualStyleBackColor = false; BtnTest.UseVisualStyleBackColor = false;
BtnTest.Visible = false;
BtnTest.Click += BtnTest_Click; BtnTest.Click += BtnTest_Click;
// //
// PicBxIRF2 // PicBxIRF2
@@ -1835,6 +1838,8 @@ namespace AiQ_GUI
// TabSettings // TabSettings
// //
TabSettings.BackColor = Color.FromArgb(39, 37, 55); TabSettings.BackColor = Color.FromArgb(39, 37, 55);
TabSettings.Controls.Add(BtnUploadWonwooSetIR);
TabSettings.Controls.Add(BtnUploadWonwooSetOV);
TabSettings.Controls.Add(BtnAdminStart); TabSettings.Controls.Add(BtnAdminStart);
TabSettings.Controls.Add(BtnFirewall); TabSettings.Controls.Add(BtnFirewall);
TabSettings.Controls.Add(PanelSettings); TabSettings.Controls.Add(PanelSettings);
@@ -1845,6 +1850,40 @@ namespace AiQ_GUI
TabSettings.TabIndex = 3; TabSettings.TabIndex = 3;
TabSettings.Text = "Settings"; TabSettings.Text = "Settings";
// //
// BtnUploadWonwooSetIR
//
BtnUploadWonwooSetIR.BackColor = Color.FromArgb(70, 65, 80);
BtnUploadWonwooSetIR.FlatAppearance.BorderColor = Color.FromArgb(70, 65, 80);
BtnUploadWonwooSetIR.FlatAppearance.BorderSize = 0;
BtnUploadWonwooSetIR.FlatStyle = FlatStyle.Flat;
BtnUploadWonwooSetIR.Font = new Font("Segoe UI Semibold", 10F, FontStyle.Bold);
BtnUploadWonwooSetIR.ForeColor = SystemColors.Control;
BtnUploadWonwooSetIR.Location = new Point(210, 304);
BtnUploadWonwooSetIR.Margin = new Padding(4, 3, 4, 3);
BtnUploadWonwooSetIR.Name = "BtnUploadWonwooSetIR";
BtnUploadWonwooSetIR.Size = new Size(180, 49);
BtnUploadWonwooSetIR.TabIndex = 244;
BtnUploadWonwooSetIR.Text = "Upload Wonwoo Settings IR";
BtnUploadWonwooSetIR.UseVisualStyleBackColor = false;
BtnUploadWonwooSetIR.Click += UploadWonwooSetIR_Click;
//
// BtnUploadWonwooSetOV
//
BtnUploadWonwooSetOV.BackColor = Color.FromArgb(70, 65, 80);
BtnUploadWonwooSetOV.FlatAppearance.BorderColor = Color.FromArgb(70, 65, 80);
BtnUploadWonwooSetOV.FlatAppearance.BorderSize = 0;
BtnUploadWonwooSetOV.FlatStyle = FlatStyle.Flat;
BtnUploadWonwooSetOV.Font = new Font("Segoe UI Semibold", 10F, FontStyle.Bold);
BtnUploadWonwooSetOV.ForeColor = SystemColors.Control;
BtnUploadWonwooSetOV.Location = new Point(19, 304);
BtnUploadWonwooSetOV.Margin = new Padding(4, 3, 4, 3);
BtnUploadWonwooSetOV.Name = "BtnUploadWonwooSetOV";
BtnUploadWonwooSetOV.Size = new Size(181, 49);
BtnUploadWonwooSetOV.TabIndex = 243;
BtnUploadWonwooSetOV.Text = "Upload Wonwoo Settings OV";
BtnUploadWonwooSetOV.UseVisualStyleBackColor = false;
BtnUploadWonwooSetOV.Click += UploadWonwooSetOV_Click;
//
// BtnAdminStart // BtnAdminStart
// //
BtnAdminStart.BackColor = Color.FromArgb(70, 65, 80); BtnAdminStart.BackColor = Color.FromArgb(70, 65, 80);
@@ -2210,5 +2249,7 @@ namespace AiQ_GUI
private Button BtnAdminStart; private Button BtnAdminStart;
private Button SetGodModeAll; private Button SetGodModeAll;
private Button BtnFactoryDefault; private Button BtnFactoryDefault;
private Button BtnUploadWonwooSetOV;
private Button BtnUploadWonwooSetIR;
} }
} }

View File

@@ -5,6 +5,14 @@ using System.Reflection;
namespace AiQ_GUI namespace AiQ_GUI
{ {
public enum Level
{
ERROR,
WARNING,
LOG,
Success
}
public partial class MainForm : Form public partial class MainForm : Form
{ {
// Classes // Classes
@@ -34,20 +42,18 @@ namespace AiQ_GUI
private async void AiQGUI_Load(object sender, EventArgs e) private async void AiQGUI_Load(object sender, EventArgs e)
{ {
Stopwatch stopwatch = Stopwatch.StartNew();
Task? closeProcessesTask = Windows.CloseProcesses(); // Fire and forget closing other apps Task? closeProcessesTask = Windows.CloseProcesses(); // Fire and forget closing other apps
Windows.UpdateFirewall(); Windows.UpdateFirewall();
Task UniDataTask = Task.Run(() => Access.ReadUniData()); // Get universal data Task UniDataTask = Task.Run(() => Access.ReadUniData()); // Get universal data
Task<LocalDataStore> LDSWAIT = Task.Run(() => LDS.GetLDS()); // Get and deserialise LDS.json Task<LocalDataStore> LDSWAIT = Task.Run(() => LDS.GetLDS()); // Get and deserialise LDS.json
Task<string> guiVerTask = Task.Run(() => GUIUpdate.FindGUIVersion()); // Get GUI Version Task<string> guiVerTask = Task.Run(() => GUIUpdate.FindGUIVersion()); // Get GUI Version
Network.Initialize("admin", "admin"); // Initialise HTTP client Network.Initialize("admin", "admin"); // Initialise HTTP client
if (await Network.PingIP("8.8.8.8")) // Ping to check if we're online if (await Network.PingIP("8.8.8.8")) // Ping to check if we're online
{ {
if (!GoogleAPI.Setup()) if (!GoogleAPI.Setup())
AddToActionsList("Cannot setup Google API"); AddToActionsList("Cannot setup Google API", Level.WARNING);
} }
else else
{ {
@@ -69,9 +75,10 @@ namespace AiQ_GUI
// Load local data store // Load local data store
localDataStore = await LDSWAIT; localDataStore = await LDSWAIT;
Logging.LogMessage("Opening GUI"); // Done after LDS to make sure directory exists. Logging.LogMessage("Opening GUI"); // Done after LDS to make sure directory exists.
if (localDataStore == null) if (localDataStore == null)
{ {
AddToActionsList("Could not deserialise LDS.json please help!"); AddToActionsList("Could not deserialise LDS.json please help!", Level.WARNING);
return; return;
} }
@@ -87,9 +94,6 @@ namespace AiQ_GUI
await CheckHWOnline; await CheckHWOnline;
Flags.Start = false; Flags.Start = false;
stopwatch.Stop();
Debug.WriteLine("RunTime " + stopwatch.Elapsed.ToString(@"hh\:mm\:ss\.ff"));
} }
private void PopulateUIWithLDS(LocalDataStore lds) private void PopulateUIWithLDS(LocalDataStore lds)
@@ -103,7 +107,7 @@ namespace AiQ_GUI
CbBxIris.SelectedIndex = lds.Iris; CbBxIris.SelectedIndex = lds.Iris;
CbBxGain.SelectedIndex = lds.Gain; CbBxGain.SelectedIndex = lds.Gain;
if (lds.User == "Bradley") if (lds.User == "Bradley" || lds.User == "Sophie")
BtnTest.Visible = true; BtnTest.Visible = true;
TxBxCheckValid(TxBxPsuIP); // Set save button color if valid 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. string LEDreply = await FlexiAPI.APIHTTPLED(CamOnTest.IP, LEDPOWER.SAFE); // Set LED's to safe (0x0E) to help with eye safety and trim check.
if (!LEDreply.Contains("Power levels set successfully")) if (!LEDreply.Contains("Power levels set successfully"))
AddToActionsList($"LED level could not be set: {LEDreply}"); AddToActionsList($"LED level could not be set: {LEDreply}", Level.ERROR);
} }
else if (!await TestTube.CheckInTestTube(CamOnTest.IP)) // Sets LED's to medium power after checking it is in the test tube else if (!await TestTube.CheckInTestTube(CamOnTest.IP)) // Sets LED's to medium power after checking it is in the test tube
await TestFailed(BtnStartTest, "Camera not in test tube"); await TestFailed(BtnStartTest, "Camera not in test tube");
@@ -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. string VISCAReply = await FlexiAPI.APIHTTPVISCA(CamOnTest.IP, "8101043903FF", true); // Manual mode to be able to manipulate the SIG settings.
if (VISCAReply != "9041FF9051FF") if (VISCAReply != "9041FF9051FF")
AddToActionsList("Couldn't set to manual mode"); AddToActionsList("Couldn't set to manual mode",Level.ERROR);
await CameraModules.SetSIG(CbBxShutter, CbBxIris, CbBxGain, CamOnTest.IP); // Set SIG according to the drop downs in settings for a good picture ready for image check await CameraModules.SetSIG(CbBxShutter, CbBxIris, CbBxGain, CamOnTest.IP); // Set SIG according to the drop downs in settings for a good picture ready for image check
await ImageProcessing.ImageCheck(PicBxOV, PicBxIRF2, PicBxIRF16, LblIRImageF2, LblIRImageF16, CamOnTest); // Populates the picture boxes and checks iris changes await ImageProcessing.ImageCheck(PicBxOV, PicBxIRF2, PicBxIRF16, LblIRImageF2, LblIRImageF16, CamOnTest); // Populates the picture boxes and checks iris changes
@@ -197,8 +201,9 @@ namespace AiQ_GUI
string LEDreply = await FlexiAPI.APIHTTPLED(CamOnTest.IP, LEDPOWER.MID); // Set LED's to medium (0x30) string LEDreply = await FlexiAPI.APIHTTPLED(CamOnTest.IP, LEDPOWER.MID); // Set LED's to medium (0x30)
if (!LEDreply.Contains("Power levels set successfully")) if (!LEDreply.Contains("Power levels set successfully"))
AddToActionsList($"LED level could not be set: {LEDreply}"); AddToActionsList($"LED level could not be set: {LEDreply}", Level.ERROR);
} }
await FlexiAPI.SetVaxtorMinMaxPlate(CamOnTest.IP);
DateTime PCTime = DateTime.Now; // Grab PC time as close to the API as possible to pass onto PDF later 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 await CheckDiagsAPIPt2(); // For only final test parts
// Check module has gone to default config // Check module has gone to default config
CameraModules.CheckCamModule(DiagsAPI.IRmodule, LblIRModule); // IR CameraModules.CheckCamModule(DiagsAPI.IRmodule, LblIRModule, CamOnTest); // IR
CameraModules.CheckCamModule(DiagsAPI.OVmodule, LblOVModule); // OV CameraModules.CheckCamModule(DiagsAPI.OVmodule, LblOVModule, CamOnTest); // OV
// Check voltage and current are OK. // Check voltage and current are OK.
LED.CheckLEDs(DiagsAPI.LedVoltage, LblLEDV, "V", CameraAccessInfo.LED_V); // Voltage LED.CheckLEDs(DiagsAPI.LedVoltage, LblLEDV, "V", CameraAccessInfo.LED_V); // Voltage
@@ -218,10 +223,7 @@ namespace AiQ_GUI
// If there are any actions identified then fail the test. // If there are any actions identified then fail the test.
// If any labels are red then fail. Only labels in panel so can foreach on labels not controls // If any labels are red then fail. Only labels in panel so can foreach on labels not controls
if (RhTxBxActions.Text.Length > 2 || PnlLbls.Controls.OfType<Label>().Any(c => c.ForeColor == Color.Red) == true) if (RhTxBxActions.Text.Length > 2 || PnlLbls.Controls.OfType<Label>().Any(c => c.ForeColor == Color.Red) == true)
await TestFailed(BtnStartTest, "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 TestFailed(BtnStartTest, "Diagnostic Failure");// If approved then pass otherwise GUI would have restarted before getting to TestPassed.
}
await TestPassed(PCTime); await TestPassed(PCTime);
} }
@@ -240,7 +242,7 @@ namespace AiQ_GUI
string LEDreply = await FlexiAPI.APIHTTPLED(CamOnTest.IP, LEDPOWER.MID); // Set LED's to medium (0x30) string LEDreply = await FlexiAPI.APIHTTPLED(CamOnTest.IP, LEDPOWER.MID); // Set LED's to medium (0x30)
if (!LEDreply.Contains("Power levels set successfully")) if (!LEDreply.Contains("Power levels set successfully"))
AddToActionsList($"LED level could not be set: {LEDreply}"); AddToActionsList($"LED level could not be set: {LEDreply}", Level.ERROR);
await CameraModules.FactoryResetModules(CamOnTest.IP); // Reset both modules and double check await CameraModules.FactoryResetModules(CamOnTest.IP); // Reset both modules and double check
@@ -253,8 +255,8 @@ namespace AiQ_GUI
await CheckDiagsAPIPt1(); await CheckDiagsAPIPt1();
// Check module has gone to default config // Check module has gone to default config
CameraModules.CheckCamModule(DiagsAPI.IRmodule, LblIRModule); // IR CameraModules.CheckCamModule(DiagsAPI.IRmodule, LblIRModule, CamOnTest); // IR
CameraModules.CheckCamModule(DiagsAPI.OVmodule, LblOVModule); // OV CameraModules.CheckCamModule(DiagsAPI.OVmodule, LblOVModule, CamOnTest); // OV
// Check voltage and current are OK. // Check voltage and current are OK.
LED.CheckLEDs(DiagsAPI.LedVoltage, LblLEDV, "V", CameraAccessInfo.LED_V); // Voltage LED.CheckLEDs(DiagsAPI.LedVoltage, LblLEDV, "V", CameraAccessInfo.LED_V); // Voltage
@@ -272,9 +274,9 @@ namespace AiQ_GUI
if (await DisplayQuestion($"Would you like to allocate a serial number to this camera?")) if (await DisplayQuestion($"Would you like to allocate a serial number to this camera?"))
await AllocateSerial(); await AllocateSerial();
else if (GoogleAPI.UpdateSpreadSheetRePreTest(CameraAccessInfo.SpreadsheetID, Vers) != "OK") // If rerun might be different values so update SS else if (GoogleAPI.UpdateSpreadSheetRePreTest(CameraAccessInfo.SpreadsheetID, Vers) != "OK") // If rerun might be different values so update SS
AddToActionsList("Failed to write to spreadsheet, please check manually"); AddToActionsList("Failed to write to spreadsheet, please check manually",Level.WARNING);
// else if (Excel.UpdateSpreadSheetPreTest(CameraAccessInfo.SpreadsheetID, Vers, CamOnTest.GetCamDesc(), CamOnTest.Model) != "OK") // else if (Excel.UpdateSpreadSheetPreTest(CameraAccessInfo.SpreadsheetID, Vers, CamOnTest.GetCamDesc(), CamOnTest.Model) != "OK")
// AddToActionsList("Failed to write to spreadsheet, please check manually"); // AddToActionsList("Failed to write to spreadsheet, please check manually");
} }
else // No serial or model so allocate one else // No serial or model so allocate one
await AllocateSerial(); await AllocateSerial();
@@ -286,8 +288,6 @@ namespace AiQ_GUI
{ {
await PreTestFailed("Diagnostic Failure"); await PreTestFailed("Diagnostic Failure");
} }
} }
// ***** Pass/Fails ***** // ***** Pass/Fails *****
@@ -297,7 +297,7 @@ namespace AiQ_GUI
string err = GoogleAPI.UpdateSpreadSheetFinalTest(CameraAccessInfo.SpreadsheetID, DiagsAPI, sshData, CamOnTest.RMANum); string err = GoogleAPI.UpdateSpreadSheetFinalTest(CameraAccessInfo.SpreadsheetID, DiagsAPI, sshData, CamOnTest.RMANum);
if (err != string.Empty) // If there is an error message, display it if (err != string.Empty) // If there is an error message, display it
AddToActionsList("Failed to write to spreadsheet " + err); AddToActionsList("Failed to write to spreadsheet " + err, Level.ERROR);
// Purge camera of all reads // Purge camera of all reads
await FlexiAPI.APIHTTPRequest("/api/purge-all", CamOnTest.IP); await FlexiAPI.APIHTTPRequest("/api/purge-all", CamOnTest.IP);
@@ -306,14 +306,14 @@ namespace AiQ_GUI
{ {
// Turn off God mode // Turn off God mode
string[,] GOD_JSON = { { "propGodMode", "false" } }; string[,] GOD_JSON = { { "propGodMode", "false" } };
string IntConf = await FlexiAPI.HTTP_Update("Internal Config", CamOnTest.IP, GOD_JSON); string IntConf = await FlexiAPI.HTTP_Update("GLOBAL--FlexiApplication", CamOnTest.IP, GOD_JSON);
if (!IntConf.Contains("\"propGodMode\": {\"value\": \"false\", \"datatype\": \"boolean\"},")) if (!IntConf.Contains("\"propGodMode\": {\"value\": \"false\", \"datatype\": \"boolean\"},"))
AddToActionsList("Could not turn off God mode"); AddToActionsList("Could not turn off God mode", Level.WARNING);
Thread Thr211 = new(async () => Thread Thr211 = new(async () =>
{ {
if (!await FlexiAPI.ChangeNetwork211(CamOnTest.IP)) // Change camera IP to 192.168.1.211. Waits for camera to come back. if (!await FlexiAPI.ChangeNetwork211(CamOnTest.IP)) // Change camera IP to 192.168.1.211. Waits for camera to come back.
AddToActionsList("Could not find camera at 192.168.1.211. Please check manually"); AddToActionsList("Could not find camera at 192.168.1.211. Please check manually", Level.WARNING);
}); });
Thr211.IsBackground = true; Thr211.IsBackground = true;
Thr211.Start(); Thr211.Start();
@@ -336,15 +336,11 @@ namespace AiQ_GUI
BtnStartTest.Text = "Test Passed"; BtnStartTest.Text = "Test Passed";
PnlQuestion.Visible = false; // just in case this came from an override PnlQuestion.Visible = false; // just in case this came from an override
Logging.LogMessage("Final Test Passed"); Logging.LogMessage("Final Test Passed");
if (CamOnTest.RMANum == 0) // Yap to check if it is not a RMA
{
Access.Stats("Final Tests Passed", CamOnTest.Model);
}
else
{
Access.Stats("RMA Final Tests Passed", CamOnTest.Model);
}
if (CamOnTest.RMANum == 0) // Yap to check if it is not a RMA
Access.Stats("Final Tests Passed", CamOnTest.Model);
else
Access.Stats("RMA Final Tests Passed", CamOnTest.Model);
} }
public async Task TestFailed(Button Btn, string ErrMssg) public async Task TestFailed(Button Btn, string ErrMssg)
@@ -352,15 +348,13 @@ namespace AiQ_GUI
// Indicators to the user the test has failed // Indicators to the user the test has failed
Btn.BackColor = Color.Maroon; Btn.BackColor = Color.Maroon;
Btn.Text = "Test Failed"; Btn.Text = "Test Failed";
if (!(CamOnTest.RMANum != 0)) // Yap to check if it is not a RMA if (!(CamOnTest.RMANum != 0)) // Yap to check if it is not a RMA
{
Access.Stats(["Final Tests Failed", ErrMssg], CamOnTest.Model); Access.Stats(["Final Tests Failed", ErrMssg], CamOnTest.Model);
}
else else
{
Access.Stats("RMA Final Tests Failed", CamOnTest.Model); Access.Stats("RMA Final Tests Failed", CamOnTest.Model);
}
AddToActionsList(ErrMssg); AddToActionsList(ErrMssg, Level.ERROR);
string RedLbls = string.Join(Environment.NewLine, PnlLbls.Controls string RedLbls = string.Join(Environment.NewLine, PnlLbls.Controls
.OfType<Label>() .OfType<Label>()
@@ -370,7 +364,6 @@ namespace AiQ_GUI
Access.StatsDiags(RedLbls, RhTxBxActions.Text, CamOnTest.Model); // Log to Access database Access.StatsDiags(RedLbls, RhTxBxActions.Text, CamOnTest.Model); // Log to Access database
if (await DisplayQuestion("Test failed, appeal?" + Environment.NewLine + "See Actions textbox for details.")) if (await DisplayQuestion("Test failed, appeal?" + Environment.NewLine + "See Actions textbox for details."))
{ {
if (CbBxUserName.Text == "Bradley") if (CbBxUserName.Text == "Bradley")
@@ -387,9 +380,6 @@ namespace AiQ_GUI
// Joins the actions box to any red labels to use as a full failed text // Joins the actions box to any red labels to use as a full failed text
Logging.LogErrorMessage(FullFailureValues); Logging.LogErrorMessage(FullFailureValues);
Logging.LogErrorMessage(FullFailureValues);
IList<IList<object>> values = GoogleAPI.service.Spreadsheets.Values.Get(GoogleAPI.spreadsheetId_ModelInfo, "'Approval'!A1:A").Execute().Values; IList<IList<object>> values = GoogleAPI.service.Spreadsheets.Values.Get(GoogleAPI.spreadsheetId_ModelInfo, "'Approval'!A1:A").Execute().Values;
if (values?.Count > 0) if (values?.Count > 0)
@@ -402,15 +392,13 @@ namespace AiQ_GUI
List<object> oblistCD = [CamOnTest.Model, FullFailureValues]; List<object> oblistCD = [CamOnTest.Model, FullFailureValues];
GoogleAPI.WriteToSS(oblistCD, "'Approval'!C" + nextRow + ":D" + nextRow, GoogleAPI.spreadsheetId_ModelInfo); GoogleAPI.WriteToSS(oblistCD, "'Approval'!C" + nextRow + ":D" + nextRow, GoogleAPI.spreadsheetId_ModelInfo);
//await Teams.SendMssg(Convert.ToString(nextRow), CbBxUserName.Text); await Teams.SendMssg(Convert.ToString(nextRow), CbBxUserName.Text);
GoogleAPI.EmailApproval(Convert.ToString(nextRow), CbBxUserName.Text);
string Approved = ""; string Approved = "";
while (Approved != "TRUE") while (Approved != "TRUE")
{ {
await Task.Delay(1000); await Task.Delay(1000);
values = GoogleAPI.service.Spreadsheets.Values.Get(GoogleAPI.spreadsheetId_ModelInfo, "'Approval'!B" + nextRow).Execute().Values; values = GoogleAPI.service.Spreadsheets.Values.Get(GoogleAPI.spreadsheetId_ModelInfo, "'Approval'!B" + nextRow).Execute().Values;
if (values?.Count > 0) if (values?.Count > 0)
@@ -442,14 +430,11 @@ namespace AiQ_GUI
PnlQuestion.Visible = true; PnlQuestion.Visible = true;
BtnNo.Visible = false; BtnNo.Visible = false;
Logging.LogMessage("Pre Test Passed"); Logging.LogMessage("Pre Test Passed");
if (CamOnTest.RMANum == 0) // Yap to check if it is not a RMA if (CamOnTest.RMANum == 0) // Yap to check if it is not a RMA
{
Access.Stats("Pre Tests Passed", CamOnTest.Model); Access.Stats("Pre Tests Passed", CamOnTest.Model);
}
else else
{
Access.Stats("RMA Pre Tests Passed", CamOnTest.Model); Access.Stats("RMA Pre Tests Passed", CamOnTest.Model);
}
if (await DisplayQuestion("Test passed, restart?")) if (await DisplayQuestion("Test passed, restart?"))
Helper.RestartApp(); Helper.RestartApp();
@@ -464,13 +449,9 @@ namespace AiQ_GUI
Logging.LogMessage("Pre Test Failed"); Logging.LogMessage("Pre Test Failed");
if (CamOnTest.RMANum == 0) // Yap to check if it is not a RMA if (CamOnTest.RMANum == 0) // Yap to check if it is not a RMA
{
Access.Stats(["Pre Tests Failed", ErrMssg], CamOnTest.Model); Access.Stats(["Pre Tests Failed", ErrMssg], CamOnTest.Model);
}
else else
{
Access.Stats("RMA Pre Tests Failed", CamOnTest.Model); Access.Stats("RMA Pre Tests Failed", CamOnTest.Model);
}
string RedLbls = string.Join(Environment.NewLine, PnlLbls.Controls string RedLbls = string.Join(Environment.NewLine, PnlLbls.Controls
.OfType<Label>() .OfType<Label>()
@@ -522,10 +503,13 @@ namespace AiQ_GUI
else if (RegexCache.MACRegex().IsMatch(DiagsAPI.MAC)) // Is a valid MAC, but not NVIDIA else if (RegexCache.MACRegex().IsMatch(DiagsAPI.MAC)) // Is a valid MAC, but not NVIDIA
{ {
lblMac.ForeColor = Color.Red; lblMac.ForeColor = Color.Red;
AddToActionsList($"{DiagsAPI.MAC} not recognised as NVIDIA MAC address"); AddToActionsList($"{DiagsAPI.MAC} not recognised as NVIDIA MAC address", Level.ERROR);
} }
else else
{
lblMac.ForeColor = Color.Red; lblMac.ForeColor = Color.Red;
AddToActionsList($"{DiagsAPI.MAC} not recognised as a MAC address", Level.ERROR);
}
// Check timestamp // Check timestamp
DateTime dateTime = new(1970, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc); DateTime dateTime = new(1970, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc);
@@ -588,7 +572,7 @@ namespace AiQ_GUI
if (DiagsAPI.modelNumber != CamOnTest.Model) if (DiagsAPI.modelNumber != CamOnTest.Model)
{ {
AddToActionsList("Model number in camera doesn't match what has been selected"); AddToActionsList("Model number in camera doesn't match what has been selected", Level.WARNING);
lblModel.ForeColor = Color.Red; lblModel.ForeColor = Color.Red;
} }
else if (!GoogleAPI.CheckWIP(DiagsAPI.serialNumber, CameraAccessInfo.SpreadsheetID)) // Check WIP column in serial number register, if not ticked then RMA else if (!GoogleAPI.CheckWIP(DiagsAPI.serialNumber, CameraAccessInfo.SpreadsheetID)) // Check WIP column in serial number register, if not ticked then RMA
@@ -608,15 +592,13 @@ namespace AiQ_GUI
CamOnTest.RMANum = Convert.ToInt32(await DisplayInput("What is the RMA number?")); CamOnTest.RMANum = Convert.ToInt32(await DisplayInput("What is the RMA number?"));
if (CamOnTest.RMANum == -1) // Means they chose the 'I don't know' option if (CamOnTest.RMANum == -1) // Means they chose the 'I don't know' option
{
await TestFailed(BtnStartTest, "Please get RMA number from operations team before continuing"); await TestFailed(BtnStartTest, "Please get RMA number from operations team before continuing");
}
} }
} }
} }
else else
{ {
AddToActionsList("Camera has not been given a valid serial and model number, suggest you run through pre test again and check serial number register for any issues."); AddToActionsList("Camera has not been given a valid serial and model number, suggest you run through pre test again and check serial number register for any issues.", Level.ERROR);
} }
} }
catch { } catch { }
@@ -649,11 +631,7 @@ namespace AiQ_GUI
double CPUround = Math.Round(DiagsAPI.CPUusage); // Check CPU usage isn't near max double CPUround = Math.Round(DiagsAPI.CPUusage); // Check CPU usage isn't near max
LblCPUusage.Text += CPUround + "%"; LblCPUusage.Text += CPUround + "%";
if (CPUround < 98 && CPUround > 50) if (CPUround <= 50)
{
LblCPUusage.ForeColor = Color.LightGreen;
}
else if (CPUround <= 50)
{ {
LblCPUusage.Text += " Unexpectedly low CPU usage"; LblCPUusage.Text += " Unexpectedly low CPU usage";
LblCPUusage.ForeColor = Color.Red; LblCPUusage.ForeColor = Color.Red;
@@ -663,6 +641,10 @@ namespace AiQ_GUI
LblCPUusage.Text += " Unexpectedly high CPU usage"; LblCPUusage.Text += " Unexpectedly high CPU usage";
LblCPUusage.ForeColor = Color.Red; LblCPUusage.ForeColor = Color.Red;
} }
else
{
LblCPUusage.ForeColor = Color.LightGreen;
}
// Check Vaxtor if it doesn't need or have license OR has and wants one then pass // Check Vaxtor if it doesn't need or have license OR has and wants one then pass
if (CameraAccessInfo.VaxtorLic == false && DiagsAPI.licenses.raptorKeyID == "Not Licensed" || CameraAccessInfo.VaxtorLic == true && DiagsAPI.licenses.raptorKeyID != "Not Licensed") if (CameraAccessInfo.VaxtorLic == false && DiagsAPI.licenses.raptorKeyID == "Not Licensed" || CameraAccessInfo.VaxtorLic == true && DiagsAPI.licenses.raptorKeyID != "Not Licensed")
@@ -712,12 +694,12 @@ namespace AiQ_GUI
if (NewSerial.Contains("ERROR")) if (NewSerial.Contains("ERROR"))
{ {
AddToActionsList(NewSerial + Environment.NewLine + "Please remove any information that was put into the serial number mistake, change the camera model/serial to 'N/A' and retry the test."); AddToActionsList(NewSerial + Environment.NewLine + "Please remove any information that was put into the serial number mistake, change the camera model/serial to 'N/A' and retry the test.", Level.ERROR);
return; return;
} }
else if (NewSerial == "Last serial number not found") else if (NewSerial == "Last serial number not found")
{ {
AddToActionsList("Last serial number not found in spreadsheet. Please check spreadsheet is in correct format before retrying."); AddToActionsList("Last serial number not found in spreadsheet. Please check spreadsheet is in correct format before retrying.", Level.WARNING);
return; return;
} }
@@ -727,7 +709,7 @@ namespace AiQ_GUI
if (!JSONResponse.Contains(NewSerial) || !JSONResponse.Contains(CamOnTest.Model)) if (!JSONResponse.Contains(NewSerial) || !JSONResponse.Contains(CamOnTest.Model))
{ {
AddToActionsList("Could not set model or serial numbers into camera."); AddToActionsList("Could not set model or serial numbers into camera.",Level.ERROR);
await PreTestFailed("Failed To Set Model Or Serial Number"); await PreTestFailed("Failed To Set Model Or Serial Number");
} }
@@ -738,7 +720,6 @@ namespace AiQ_GUI
lblSerial.Text += DiagsAPI.serialNumber; lblSerial.Text += DiagsAPI.serialNumber;
Printer.ZebraIP = localDataStore.ZebraIP; Printer.ZebraIP = localDataStore.ZebraIP;
Printer.PrintGBLbl(); // Print GB label Printer.PrintGBLbl(); // Print GB label
Printer.PrintSerialLbl(CamOnTest.Model, NewSerial, CameraAccessInfo.Processor); // Print model/serial label Printer.PrintSerialLbl(CamOnTest.Model, NewSerial, CameraAccessInfo.Processor); // Print model/serial label
} }
@@ -782,7 +763,7 @@ namespace AiQ_GUI
private async void BtnFindCams_Click(object sender, EventArgs e) private async void BtnFindCams_Click(object sender, EventArgs e)
{ {
CbBxFoundCams.Text = "Searching"; CbBxFoundCams.Text = "Searching";
BtnFindCams.Enabled = BtnSetAll211.Enabled = BtnSoak.Enabled = BtnSet211.Enabled = BtnSetGodMode.Enabled = BtnUploadBlob.Enabled = SetGodModeAll.Enabled = BtnFactoryDefault.Enabled = false; BtnFindCams.Enabled = BtnSetAll211.Enabled = BtnSoak.Enabled = BtnSet211.Enabled = BtnSetGodMode.Enabled = BtnUploadBlob.Enabled = SetGodModeAll.Enabled = BtnFactoryDefault.Enabled = BtnUploadWonwooSetIR.Enabled = BtnUploadWonwooSetOV.Enabled = false;
BtnSetGodMode.BackColor = BtnUploadBlob.BackColor = BtnFactoryDefault.BackColor = BtnSetAll211.BackColor = BtnColour; BtnSetGodMode.BackColor = BtnUploadBlob.BackColor = BtnFactoryDefault.BackColor = BtnSetAll211.BackColor = BtnColour;
CbBxFoundCams.Items.Clear(); CbBxFoundCams.Items.Clear();
soakCameraList.Clear(); soakCameraList.Clear();
@@ -815,7 +796,9 @@ namespace AiQ_GUI
foreach (Camera soakInfo in soakCameraList) foreach (Camera soakInfo in soakCameraList)
{ {
TabSoak.Controls.Add(SoakTest.MakeNewCheckbox(soakInfo, YLoc)); CheckBox chkBox = SoakTest.MakeNewCheckbox(soakInfo, YLoc);
TabSoak.Controls.Add(chkBox);
soakInfo.CheckBox = chkBox;
YLoc += 24; YLoc += 24;
} }
@@ -880,7 +863,7 @@ namespace AiQ_GUI
if (Vers == null) // If failed to get versions then return. Flexi most likely not running yet. if (Vers == null) // If failed to get versions then return. Flexi most likely not running yet.
{ {
AddToActionsList("Failed to get API from camera. Flexi not running yet or not an AiQ"); AddToActionsList("Failed to get API from camera. Flexi not running yet or not an AiQ", Level.WARNING);
return; return;
} }
@@ -898,7 +881,7 @@ namespace AiQ_GUI
else else
{ {
CbBxFoundCams.BackColor = Color.Red; CbBxFoundCams.BackColor = Color.Red;
BtnSecret.Enabled = BtnOpenWebpage.Enabled = BtnSet211.Enabled = BtnSetGodMode.Enabled = false; BtnSecret.Enabled = BtnOpenWebpage.Enabled = BtnSet211.Enabled = BtnSetGodMode.Enabled = BtnUploadWonwooSetIR.Enabled = BtnUploadWonwooSetOV.Enabled = false;
} }
TestStartConditions(); TestStartConditions();
@@ -937,26 +920,43 @@ namespace AiQ_GUI
try try
{ {
await FlexiAPI.HTTP_Update("Internal Config", CamOnTest.IP, GOD_JSON); await FlexiAPI.HTTP_Update("GLOBAL--FlexiApplication", CamOnTest.IP, GOD_JSON);
BtnSetGodMode.Text = newGodModeValue == "true" ? "Set God Mode Off" : "Set God Mode On"; BtnSetGodMode.Text = newGodModeValue == "true" ? "Set God Mode Off" : "Set God Mode On";
BtnSetGodMode.BackColor = Color.Green; BtnSetGodMode.BackColor = Color.Green;
} }
catch (Exception ex) catch (Exception ex)
{ {
AddToActionsList($"Failed to set God mode for camera {CamOnTest.IP}. Reason: {ex.Message}"); AddToActionsList($"Failed to set God mode for camera {CamOnTest.IP}. Reason: {ex.Message}", Level.ERROR);
} }
} }
// ***** Helper functions ***** // ***** Helper functions *****
public void AddToActionsList(string Mssg, bool IsErr = true) public void AddToActionsList(string Mssg, Level Lvl = Level.LOG)
{ {
if (IsErr) if (Lvl == Level.ERROR)
{
Logging.LogErrorMessage(Mssg); Logging.LogErrorMessage(Mssg);
else RhTxBxActions.SelectionColor = Color.IndianRed;
}
else if (Lvl == Level.WARNING)
{
Logging.LogWarningMessage(Mssg);
RhTxBxActions.SelectionColor = Color.Orange;
}
else if (Lvl == Level.LOG)
{
Logging.LogMessage(Mssg); Logging.LogMessage(Mssg);
RhTxBxActions.SelectionColor = Color.White;
}
else if (Lvl == Level.Success)
{
Logging.LogMessage(Mssg);
RhTxBxActions.SelectionColor = Color.LightGreen;
}
RhTxBxActions.AppendText(Mssg + Environment.NewLine); RhTxBxActions.AppendText(Mssg + Environment.NewLine);
RhTxBxActions.SelectionStart = RhTxBxActions.Text.Length; RhTxBxActions.SelectionStart = RhTxBxActions.Text.Length;
RhTxBxActions.SelectionColor = SystemColors.Control;
RhTxBxActions.ScrollToCaret(); RhTxBxActions.ScrollToCaret();
} }
@@ -983,7 +983,7 @@ namespace AiQ_GUI
if (CbBxFoundCams.BackColor != BtnColour || CbBxFoundCams.Text.Contains("Found")) if (CbBxFoundCams.BackColor != BtnColour || CbBxFoundCams.Text.Contains("Found"))
TSC = SetInvalid("Select camera IP address."); TSC = SetInvalid("Select camera IP address.");
else else
BtnOpenWebpage.Enabled = BtnSet211.Enabled = BtnSetGodMode.Enabled = BtnZoom8000.Enabled = BtnZoomWide.Enabled = true; // Allow user to go to camera webpage & change DHCP/211 BtnOpenWebpage.Enabled = BtnSet211.Enabled = BtnSetGodMode.Enabled = BtnZoom8000.Enabled = BtnZoomWide.Enabled = BtnUploadWonwooSetIR.Enabled = BtnUploadWonwooSetOV.Enabled = true; // Allow user to go to camera webpage & change DHCP/211
// Name chosen // Name chosen
if (CbBxUserName.Text == "Select Operator to Begin Test" || CbBxUserName.Text.Length < 2) if (CbBxUserName.Text == "Select Operator to Begin Test" || CbBxUserName.Text.Length < 2)
@@ -1132,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. bool Online = await FlexiAPI.ChangeNetwork211(CamOnTest.IP); // Change camera IP to 192.168.1.211 and hardware reboot. Waits for camera to come back for 50s.
if (!Online) if (!Online)
AddToActionsList("Could not find camera at 192.168.1.211. Please check manually"); AddToActionsList("Could not find camera at 192.168.1.211. Please check manually", Level.ERROR);
else else
BtnSet211.Text = "Set to DHCP"; BtnSet211.Text = "Set to DHCP";
} }
@@ -1157,11 +1157,11 @@ namespace AiQ_GUI
{ {
Network.Initialize("developer", SCL.DevPass); // Ensure network is initialized to the right camera Network.Initialize("developer", SCL.DevPass); // Ensure network is initialized to the right camera
await FlexiAPI.HTTP_Update("GLOBAL--NetworkConfig", SCL.IP, Network_JSON); await FlexiAPI.HTTP_Update("GLOBAL--NetworkConfig", SCL.IP, Network_JSON);
Instance.AddToActionsList($"Setting 211 for camera {SCL.IP}", false); Instance.AddToActionsList($"Setting 211 for camera {SCL.IP}", Level.LOG);
} }
catch (Exception ex) catch (Exception ex)
{ {
AddToActionsList("Failed to set all cameras to 211. Reason: " + ex.Message); // In case non AiQ's get caught up AddToActionsList("Failed to set all cameras to 211. Reason: " + ex.Message, Level.ERROR); // In case non AiQ's get caught up
} }
} }
@@ -1183,12 +1183,12 @@ namespace AiQ_GUI
try try
{ {
Network.Initialize("developer", SCL.DevPass); // Ensure network is initialized to the right camera Network.Initialize("developer", SCL.DevPass); // Ensure network is initialized to the right camera
string RESP = await FlexiAPI.HTTP_Update("Internal Config", SCL.IP, GOD_JSON); string RESP = await FlexiAPI.HTTP_Update("GLOBAL--FlexiApplication", SCL.IP, GOD_JSON);
Instance.AddToActionsList($"Setting God mode for camera {SCL.IP} to {newGodModeValue}", false); Instance.AddToActionsList($"Setting God mode for camera {SCL.IP} to {newGodModeValue}", Level.LOG);
} }
catch (Exception ex) catch (Exception ex)
{ {
AddToActionsList($"Failed to set God mode for camera {SCL.IP}. Reason: {ex.Message}"); AddToActionsList($"Failed to set God mode for camera {SCL.IP}. Reason: {ex.Message}", Level.ERROR);
} }
} }
@@ -1316,7 +1316,7 @@ namespace AiQ_GUI
} }
catch catch
{ {
AddToActionsList("Failed to communicate with camera, please check network and try again."); AddToActionsList("Failed to communicate with camera, please check network and try again.", Level.ERROR);
return; return;
} }
@@ -1324,12 +1324,16 @@ namespace AiQ_GUI
{ {
VaxtorLicResp = JsonConvert.DeserializeObject<VaxtorLic>(ALresponse); VaxtorLicResp = JsonConvert.DeserializeObject<VaxtorLic>(ALresponse);
if (VaxtorLicResp.protectionKeyId != string.Empty) if (VaxtorLicResp.error != string.Empty)
{
AddToActionsList(VaxtorLicResp.error, Level.ERROR);
}
else if (VaxtorLicResp.protectionKeyId != string.Empty)
{ {
string err = GoogleAPI.UpdateSpreadSheetVaxtor(VaxtorLicResp, Vers.Serial, CamOnTest.Model); string err = GoogleAPI.UpdateSpreadSheetVaxtor(VaxtorLicResp, Vers.Serial, CamOnTest.Model);
if (err != string.Empty) // If there is an error message, display it if (err != string.Empty) // If there is an error message, display it
AddToActionsList("Failed to update Vaxtor spreadsheet: " + err); AddToActionsList("Failed to update Vaxtor spreadsheet: " + err, Level.ERROR);
RhTxBxCode.AppendText("Licencing Success, Key ID: " + VaxtorLicResp.protectionKeyId + Environment.NewLine + "Waiting for files to save"); RhTxBxCode.AppendText("Licencing Success, Key ID: " + VaxtorLicResp.protectionKeyId + Environment.NewLine + "Waiting for files to save");
@@ -1338,18 +1342,14 @@ namespace AiQ_GUI
await DisplayOK("Please wait at least a minute before turning off the unit."); await DisplayOK("Please wait at least a minute before turning off the unit.");
} }
else if (VaxtorLicResp.error != string.Empty)
{
RhTxBxCode.AppendText(VaxtorLicResp.error);
}
else else
{ {
AddToActionsList($"Error reading JSON - {ALresponse}"); AddToActionsList($"Error reading JSON - {ALresponse}", Level.ERROR);
} }
} }
catch catch
{ {
AddToActionsList($"Error reading JSON - {ALresponse}"); AddToActionsList($"Error reading JSON - {ALresponse}", Level.ERROR);
return; return;
} }
} }
@@ -1500,6 +1500,16 @@ namespace AiQ_GUI
Process.Start(psi); Process.Start(psi);
} }
private async void UploadWonwooSetOV_Click(object sender, EventArgs e)
{
await FlexiAPI.UploadWonwooSet(CbBxFoundCams.Text, false); // false = Colour
}
private async void UploadWonwooSetIR_Click(object sender, EventArgs e)
{
await FlexiAPI.UploadWonwooSet(CbBxFoundCams.Text, true); // true = Infrared
}
private void TxBxSerialPrint_Click(object sender, EventArgs e) private void TxBxSerialPrint_Click(object sender, EventArgs e)
{ {
if (TxBxSerialPrint.Text == "K ") // If at default then remove the dashes ready for user to put in number if (TxBxSerialPrint.Text == "K ") // If at default then remove the dashes ready for user to put in number
@@ -1575,8 +1585,8 @@ namespace AiQ_GUI
CancellationTokenSource cts = new(); CancellationTokenSource cts = new();
soakCtsList.Add(cts); soakCtsList.Add(cts);
soakTasks.Add(SoakTest.StartSoak(SCL, cts.Token)); soakTasks.Add(SoakTest.StartSoak(SCL, SCL.CheckBox, cts.Token));
await Task.Delay(5000); await Task.Delay(10000);
} }
} }
else else
@@ -1587,14 +1597,51 @@ namespace AiQ_GUI
cts.Cancel(); cts.Cancel();
soakCtsList.Clear(); soakCtsList.Clear();
soakTasks.Clear(); soakTasks.Clear();
int i = soakCameraList.Count + 1; // Add 1 for 211 itself staying in the list
foreach (Camera SCL in soakCameraList) // Reset all cameras that were being soaked to default module settings string[,] Network_JSON = { { "propDHCP", "false" }, { "propHost", "192.168.1.211" }, { "propNetmask", "255.255.255.0" }, { "propGateway", "192.168.1.1" } };
string[,] GOD_JSON = { { "propGodMode", "false" } };
foreach (Camera SCL in soakCameraList.Where(c => c.IsChecked)) // only checked cameras
{ {
if (!SCL.IsChecked) if (!SCL.IsChecked)
continue; continue;
try
{
AddToActionsList($"Setting 211 & God Mode off for camera {SCL.IP}", Level.LOG);
Network.Initialize("developer", SCL.DevPass); // Ensure network is initialized to the right camera
await CameraModules.FactoryResetModules(SCL.IP); // Reset camera modules
Network.Initialize("developer", SCL.DevPass); // Ensure network is initialized to the right camera, cannot be done in soak test finally becuase of this. string GOD = await FlexiAPI.HTTP_Update("GLOBAL--FlexiApplication", SCL.IP, GOD_JSON);
await CameraModules.FactoryResetModules(SCL.IP); if (GOD.Contains("Error"))
throw new Exception("Could not set God mode off");
// Update GLOBAL--NetworkConfig with fixed IP and turn off DHCP
await FlexiAPI.HTTP_Update("GLOBAL--NetworkConfig", SCL.IP, Network_JSON);
i--; // Decriment count becuase they will stack into 211
}
catch (Exception ex)
{
AddToActionsList("Failed to set all cameras to 211 and god mode off. Reason: " + ex.Message, Level.ERROR); // In case non AiQ's get caught up
}
if (SCL.CheckBox.ForeColor == Color.Red)
{
DialogResult DR = MessageBox.Show($"CAMERA {SCL.Serial} {SCL.Model} DID NOT PASS SOAK TEST. Check soak test report to see error. Do you want to open the test report", "SOAK TEST FAILED", MessageBoxButtons.YesNo, MessageBoxIcon.Warning);
if (DR == DialogResult.Yes)
File.Open(SCL.TestReportLoc, FileMode.Open);
}
}
await Task.Delay(5000); // Wait for 5 seconds to allow the camera to restart
IList<string> FoundCams = await Network.SearchForCams(); // Have to check via broadcast becuase Ping sometimes fails across subnets
if ((FoundCams.Count == i && FoundCams.Contains("192.168.1.211")) || FoundCams.Count == 1)
{
AddToActionsList("All cameras successfully changed to 211.", Level.Success);
}
else
{
AddToActionsList($"Some cameras failed: Found {FoundCams.Count}, expected {i}.", Level.ERROR);
} }
} }
} }
@@ -1642,59 +1689,7 @@ namespace AiQ_GUI
private async void BtnUploadBlob_Click(object sender, EventArgs e) private async void BtnUploadBlob_Click(object sender, EventArgs e)
{ {
BtnUploadBlob.BackColor = BtnColour; BtnUploadBlob.BackColor = BtnColour;
const string networkFolderPath = @"G:\Shared drives\MAV Production\MAV_146_AiQ_Mk2\Flexi"; FlexiAPI.UploadBlob(soakCameraList);
string fileToUpload = null;
if (await DisplayQuestion("Do you want the latest Flexi version from the MAV Production folder?"))
{
fileToUpload = Directory.GetFiles(networkFolderPath, "*.blob").OrderByDescending(File.GetLastWriteTime).FirstOrDefault();
if (fileToUpload == null)
{
AddToActionsList("No .blob file found in the directory.", false);
return;
}
}
else
{
using OpenFileDialog openFileDialog1 = new()
{
InitialDirectory = networkFolderPath,
Filter = "Blob files (*.blob)|*.blob",
FilterIndex = 0
};
if (openFileDialog1.ShowDialog() == DialogResult.OK)
fileToUpload = openFileDialog1.FileName;
else
{
AddToActionsList("File selection cancelled.", false);
return;
}
}
string fileName = Path.GetFileName(fileToUpload);
AddToActionsList($"Selected file to upload: {fileToUpload}", false);
foreach (Camera? cam in soakCameraList.Where(c => c.IsChecked))
{
string apiUrl = $"http://{cam.IP}/upload/software-update/2";
Network.Initialize("developer", cam.DevPass);
AddToActionsList($"Uploading to {cam.IP}...", false);
string result = await FlexiAPI.SendBlobFileUpload(apiUrl, fileToUpload, fileName);
// Retry once on transient errors
if (result.Contains("Error while copying content to a stream") || result.Contains("Timeout"))
{
AddToActionsList($"Retrying upload to {cam.IP}...", false);
await Task.Delay(1000);
result = await FlexiAPI.SendBlobFileUpload(apiUrl, fileToUpload, fileName);
}
AddToActionsList($"Upload result for {cam.IP}: {result}", false);
await Task.Delay(500);
}
BtnUploadBlob.BackColor = Color.Green; BtnUploadBlob.BackColor = Color.Green;
} }
@@ -1714,46 +1709,37 @@ namespace AiQ_GUI
} }
// ***** Test & Debug ***** // ***** Test & Debug *****
private async void BtnTest_Click(object sender, EventArgs e) private void BtnTest_Click(object sender, EventArgs e)
{ {
Stopwatch stopWatchTest = Stopwatch.StartNew(); Stopwatch stopWatchTest = Stopwatch.StartNew();
//StatsExcel excelExporter = new(); //StatsExcel excelExporter = new();
//excelExporter.ExportDatabaseToExcel(); //excelExporter.ExportDatabaseToExcel();
// /api/config-ids - For getting all available config IDs
//FakeCamera fakeCamera = new FakeCamera(80); // Create an instance of FakeCamera // 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;
////CamOnTest.IP = CbBxFoundCams.Text; //foreach (var file in files)
//_ = fakeCamera.StartAsync(CAMTYPE.GOOD).ContinueWith(task =>
//{ //{
// //Network.Initialize("developer", "Pass123"); // if (file.Contains("SoakLog"))
// if (task.IsFaulted)
// { // {
// AddToActionsList("Error starting FakeCamera: " + task.Exception?.Message); // // File name: SoakLog_{Serial}_{Model}.log
// string[] parts = file.Split('_', '.').Select(p => p.Trim()).ToArray();
// Camera NewCam = new()
// {
// Model = parts[3],
// Serial = parts[2],
// };
// PDF.CreateSoakTestReport(NewCam, "SoakTestRig", DateTime.Now, file);
// } // }
// else //}
// {
// AddToActionsList($"FakeCamera started successfully. IP: {fakeCamera}", false);
// }
//});
//await Task.Delay(3000); // Wait for server to start
//CbBxFoundCams.Text = "localhost"; // Should force update in creds an network reinit
//CmBoFoundCams_TextChanged(sender, e);
//CbBxCameraType.SelectedIndex = CbBxCameraType.Items.Count - 1; // Selects AB12CD as model number
//await Task.Delay(3000); // Wait for server to start
//BtnPreTest_Click(sender, e);
Teams.SendMssg("10", "Test User");
stopWatchTest.Stop(); stopWatchTest.Stop();
AddToActionsList("RunTime " + stopWatchTest.Elapsed.ToString(@"hh\:mm\:ss\.ff")); AddToActionsList("RunTime " + stopWatchTest.Elapsed.ToString(@"hh\:mm\:ss\.ff"), Level.LOG);
} }
} }
} }

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -25,7 +25,7 @@ namespace AiQ_GUI
} }
catch (Exception Ex) catch (Exception Ex)
{ {
MainForm.Instance.AddToActionsList($"SSH connection failed: {Ex.Message}. Check password or network."); MainForm.Instance.AddToActionsList($"SSH connection failed: {Ex.Message}. Check password or network. {Level.WARNING}");
} }
return null; return null;
@@ -75,7 +75,7 @@ namespace AiQ_GUI
} }
catch (Exception ex) catch (Exception ex)
{ {
MainForm.Instance.AddToActionsList($"SSH connection failed: {ex.Message}. Check password or network."); MainForm.Instance.AddToActionsList($"SSH connection failed: {ex.Message}. Check password or network. {Level.WARNING}");
} }
string LogMssg = string.Join(" | ", typeof(SSHData).GetProperties().Select(p => $"{p.Name}: {p.GetValue(Data)}")); string LogMssg = string.Join(" | ", typeof(SSHData).GetProperties().Select(p => $"{p.Name}: {p.GetValue(Data)}"));
@@ -170,7 +170,7 @@ namespace AiQ_GUI
} }
catch (Exception ex) catch (Exception ex)
{ {
MainForm.Instance.AddToActionsList($"SSH connection failed: {ex.Message}. Check password or network."); MainForm.Instance.AddToActionsList($"SSH connection failed: {ex.Message}. Check password or network. {Level.WARNING}");
} }
return (string.Empty, string.Empty); return (string.Empty, string.Empty);
@@ -323,7 +323,7 @@ namespace AiQ_GUI
if (checkDevice.Result.Trim() != "OK") // Device not found if (checkDevice.Result.Trim() != "OK") // Device not found
{ {
MainForm.Instance.AddToActionsList($"Block device {device} not found."); MainForm.Instance.AddToActionsList($"Block device {device} not found. {Level.WARNING}");
return false; return false;
} }
@@ -378,7 +378,7 @@ namespace AiQ_GUI
if (checkDevice.Result.Trim().Length > 1) // Device not found if (checkDevice.Result.Trim().Length > 1) // Device not found
{ {
MainForm.Instance.AddToActionsList($"Cannot sync files to disk. Replied: {checkDevice.Result}. DO NOT TURN OFF, GET SUPERVISOR"); MainForm.Instance.AddToActionsList($"Cannot sync files to disk. Replied: {checkDevice.Result}. DO NOT TURN OFF, GET SUPERVISOR {Level.ERROR}");
return; return;
} }
@@ -387,7 +387,7 @@ namespace AiQ_GUI
} }
catch (Exception ex) catch (Exception ex)
{ {
MainForm.Instance.AddToActionsList($"Cannot sync becuase: {ex.Message}. DO NOT TURN OFF, GET SUPERVISOR"); MainForm.Instance.AddToActionsList($"Cannot sync becuase: {ex.Message}. DO NOT TURN OFF, GET SUPERVISOR {Level.ERROR}");
} }
} }
} }

View File

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

View File

@@ -116,7 +116,7 @@ namespace AiQ_GUI
if (requiresAuth) if (requiresAuth)
{ {
string authHeader = context.Request.Headers["Authorization"]; string authHeader = context.Request.Headers["Authorization"];
if (string.IsNullOrEmpty(authHeader) || !authHeader.StartsWith("Basic ")) if (string.IsNullOrEmpty(authHeader) || !authHeader.StartsWith("Basic "))
{ {
context.Response.AddHeader("WWW-Authenticate", "Basic realm=\"FakeCamera\""); context.Response.AddHeader("WWW-Authenticate", "Basic realm=\"FakeCamera\"");
@@ -293,7 +293,7 @@ namespace AiQ_GUI
{ {
await HTTPReplySetup(context, "Update successful.", 200, "text/plain"); await HTTPReplySetup(context, "Update successful.", 200, "text/plain");
} }
else if (fo.Id == "Internal Config") else if (fo.Id == "GLOBAL--FlexiApplication")
{ {
string SerialNumber = ""; string SerialNumber = "";
string ModelNumber = ""; string ModelNumber = "";

View File

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

View File

@@ -83,26 +83,26 @@ namespace AiQ_GUI
case 0: case 0:
if (!await MainForm.Instance.DisplayQuestion("Is the sleeve aligned correctly?")) if (!await MainForm.Instance.DisplayQuestion("Is the sleeve aligned correctly?"))
{ {
await MainForm.Instance.TestFailed(Btn, "Visual Test Fail - Sleeve not aligned"); await MainForm.Instance.TestFailed(Btn, $"Visual Test Fail - Sleeve not aligned{Level.ERROR}");
} }
break; break;
case 1: case 1:
if (!await MainForm.Instance.DisplayQuestion("Are all the screws fitted in the front?")) if (!await MainForm.Instance.DisplayQuestion("Are all the screws fitted in the front?"))
{ {
await MainForm.Instance.TestFailed(Btn, "Visual Test Fail - Not all front screws fitted"); await MainForm.Instance.TestFailed(Btn, $"Visual Test Fail - Not all front screws fitted{Level.ERROR}");
} }
break; break;
case 2: case 2:
if (!await MainForm.Instance.DisplayQuestion("Are all the screws fitted in the rear?")) if (!await MainForm.Instance.DisplayQuestion("Are all the screws fitted in the rear?"))
{ {
await MainForm.Instance.TestFailed(Btn, "Visual Test Fail - Not all rear screws fitted"); await MainForm.Instance.TestFailed(Btn, $"Visual Test Fail - Not all rear screws fitted{Level.ERROR}");
} }
break; break;
case 3: case 3:
if (await MainForm.Instance.DisplayQuestion("Shake unit, does it rattle?")) if (await MainForm.Instance.DisplayQuestion("Shake unit, does it rattle?"))
{ {
await MainForm.Instance.TestFailed(Btn, "Visual Test Fail - Unit rattles"); await MainForm.Instance.TestFailed(Btn, $"Visual Test Fail - Unit rattles{Level.ERROR}");
} }
break; break;
default: default:
@@ -123,7 +123,7 @@ namespace AiQ_GUI
} }
catch (Exception ex) catch (Exception ex)
{ {
MainForm.Instance.AddToActionsList($"Error fetching versions for {IPAddress}: {ex.Message}"); MainForm.Instance.AddToActionsList($"Error fetching versions for {IPAddress}: {ex.Message}{Level.ERROR}");
return null; return null;
} }
@@ -184,9 +184,9 @@ namespace AiQ_GUI
public string IP { get; set; } = string.Empty; public string IP { get; set; } = string.Empty;
public int RMANum { get; set; } = 0; public int RMANum { get; set; } = 0;
public string FlexiVersion { get; set; } = string.Empty; // Flexi version public string FlexiVersion { get; set; } = string.Empty; // Flexi version
public string Processor { get; set; } = string.Empty; // Nano, Xavier, Orin or Pi?
public string ModuleManufacturer { get; set; } = string.Empty; // KT&C or Wonwoo
public bool IsChecked { get; set; } // Is it checked for soak test? public bool IsChecked { get; set; } // Is it checked for soak test?
public CheckBox CheckBox { get; set; } = new CheckBox();
public string TestReportLoc { get; set; } = string.Empty; // Location of test report file
} }
// Static class for global flags // Static class for global flags

10
LDS.cs
View File

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

View File

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

View File

@@ -122,16 +122,14 @@ namespace AiQ_GUI
int rowsAffected = cmd.ExecuteNonQuery(); int rowsAffected = cmd.ExecuteNonQuery();
// Execute the command and get the number of rows affected // Execute the command and get the number of rows affected
//if (rowsAffected > 0) // If one or more rows were updated if (rowsAffected == 0) // If one or more rows were updated
// AddToActionsList($"Updated {TypeOfTest} for {modelNumber}"); MainForm.Instance.AddToActionsList($"No rows affected for {modelNumber}{Level.ERROR}");
//else
// AddToActionsList($"No rows found for {modelNumber}");
} }
conn.Close(); conn.Close();
} }
catch catch
{ {
MainForm.Instance.AddToActionsList("Could not access Access in Google Drive. Is it running?"); MainForm.Instance.AddToActionsList($"Could not access Access in Google Drive. Is it running?{Level.WARNING}");
return; return;
} }
} }
@@ -162,10 +160,8 @@ namespace AiQ_GUI
int rows = cmd.ExecuteNonQuery(); int rows = cmd.ExecuteNonQuery();
conn.Close(); conn.Close();
//if (rows > 0) if (rows == 0)
// AddToActionsList($"DiagsStats inserted ({rows} row) for model '{model}' on {DateTime.Now:yyyy-MM-dd}"); MainForm.Instance.AddToActionsList($"No rows inserted into DiagsStats (unexpected).{Level.ERROR}");
//else
// AddToActionsList("No rows inserted into DiagsStats (unexpected).");
} }
} }

View File

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

View File

@@ -14,104 +14,140 @@ namespace AiQ_GUI
{ {
MainForm.Instance.AddToActionsList("=== ExportDatabaseToExcel START ==="); MainForm.Instance.AddToActionsList("=== ExportDatabaseToExcel START ===");
using (OleDbConnection conn = new(Access.connString)) using OleDbConnection conn = new(Access.connString);
conn.Open();
MainForm.Instance.AddToActionsList("Connected to Access database.");
DateTime now = DateTime.Now;
// Read LastStatsRun from UniversalData (if any)
DateTime? lastStatsRun = null;
try
{ {
conn.Open(); using (OleDbCommand cmdPeriod = new("SELECT LastStatsRun FROM UniversalData", conn))
MainForm.Instance.AddToActionsList("Connected to Access database.");
DateTime now = DateTime.Now;
// ==================== MAIN STATS EXPORT ====================
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."); object res = cmdPeriod.ExecuteScalar();
return; 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}");
}
string monthName = now.ToString("MMMM"); // ==================== MAIN STATS EXPORT ====================
string sheetName = $"{monthName} Stats"; 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]";
MainForm.Instance.AddToActionsList($"Adding sheet: {sheetName}"); string query = $@"
SELECT
{selectColumns}
FROM AiQ";
XLWorkbook workbook; OleDbDataAdapter adapter = new(query, conn);
if (File.Exists(exportPath)) DataTable dataTable = new();
{ adapter.Fill(dataTable);
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)) if (dataTable.Rows.Count == 0)
{ {
workbook.Worksheet(sheetName).Delete(); MainForm.Instance.AddToActionsList("No data found in AiQ table.");
MainForm.Instance.AddToActionsList($"Deleted old sheet: {sheetName}"); return;
} }
IXLWorksheet ws = workbook.Worksheets.Add(sheetName); string monthName = now.ToString("MMMM");
ws.Cell(1, 1).InsertTable(dataTable, "AiQ_Stats", true); string sheetName = $"{monthName} Stats";
ws.Columns().AdjustToContents();
ws.Cell(1, dataTable.Columns.Count + 2).Value = "Exported:"; MainForm.Instance.AddToActionsList($"Adding sheet: {sheetName}");
ws.Cell(2, dataTable.Columns.Count + 2).Value = now.ToString("yyyy-MM-dd HH:mm:ss");
// ==================== DIAGS STATS EXPORT (CURRENT MONTH ONLY) ==================== XLWorkbook workbook;
MainForm.Instance.AddToActionsList("Exporting DiagsStats for current month..."); 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).");
}
DateTime monthStart = new(now.Year, now.Month, 1); if (workbook.Worksheets.Contains(sheetName))
DateTime nextMonth = monthStart.AddMonths(1); {
workbook.Worksheet(sheetName).Delete();
MainForm.Instance.AddToActionsList($"Deleted old sheet: {sheetName}");
}
string diagsQuery = @" 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 SELECT
[Date], [Date],
[Model], [Model],
[Red Diags Labels], [Red Diags Labels],
[RhTxBxActions Contents] [RhTxBxActions Contents],
[IsRma],
[RMA]
FROM DiagsStats FROM DiagsStats
WHERE [Date] >= ? AND [Date] < ?"; WHERE [Date] >= ? AND [Date] < ?";
OleDbDataAdapter diagsAdapter = new(diagsQuery, conn); using (OleDbCommand diagsCmd = new(diagsQuery, conn))
diagsAdapter.SelectCommand.Parameters.Add(new OleDbParameter { OleDbType = OleDbType.Date, Value = monthStart }); {
diagsAdapter.SelectCommand.Parameters.Add(new OleDbParameter { OleDbType = OleDbType.Date, Value = nextMonth }); 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(); DataTable diagsTable = new();
diagsAdapter.Fill(diagsTable); diagsAdapter.Fill(diagsTable);
if (diagsTable.Rows.Count > 0) if (diagsTable.Rows.Count > 0)
{ {
int startRow = ws.LastRowUsed().RowNumber() + 3; int startRow = (ws.LastRowUsed()?.RowNumber() ?? 0) + 3;
ws.Cell(startRow, 1).Value = "Diags Stats (Current Month)"; ws.Cell(startRow, 1).Value = "Diags Stats (Current Month)";
ws.Cell(startRow, 1).Style.Font.Bold = true; ws.Cell(startRow, 1).Style.Font.Bold = true;
@@ -122,49 +158,45 @@ namespace AiQ_GUI
MainForm.Instance.AddToActionsList($"Added DiagsStats ({diagsTable.Rows.Count} rows) starting at row {startRow}."); MainForm.Instance.AddToActionsList($"Added DiagsStats ({diagsTable.Rows.Count} rows) starting at row {startRow}.");
} }
else }
{
MainForm.Instance.AddToActionsList("No DiagsStats data found for current month.");
}
// ==================== SAVE MAIN SHEET ==================== // ==================== SAVE MAIN SHEET ====================
workbook.SaveAs(exportPath); workbook.SaveAs(exportPath);
MainForm.Instance.AddToActionsList($"Added {sheetName} to workbook: {exportPath}"); MainForm.Instance.AddToActionsList($"Added {sheetName} to workbook: {exportPath}");
workbook.Dispose();
workbook.Dispose(); // ==================== BACKUP TABLE (ALWAYS, BEFORE RESET) ====================
string ts = now.ToString("yyyyMMdd_HHmmss");
string backupTableName = $"Ztats_{ts}";
// ==================== BACKUP TABLE (ALWAYS, BEFORE RESET) ==================== if (backupTableName.Length > 64)
string ts = now.ToString("yyyyMMdd_HHmmss"); backupTableName = backupTableName.Substring(0, 64);
string backupTableName = $"Ztats_{ts}";
if (backupTableName.Length > 64) string backupSql = $@"
backupTableName = backupTableName.Substring(0, 64);
string backupSql = $@"
SELECT SELECT
{selectColumns} {selectColumns}
INTO [{backupTableName}] INTO [{backupTableName}]
FROM AiQ"; 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 try
{ {
using (OleDbCommand cmdBackup = new(backupSql, conn)) const string resetSql = @"
{
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
{
string resetSql = @"
UPDATE AiQ SET UPDATE AiQ SET
[Total Tests Run] = 0, [Total Tests Run] = 0,
[Pre Tests Passed] = 0, [Pre Tests Passed] = 0,

View File

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

View File

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

13
PDF.cs
View File

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

View File

@@ -19,7 +19,7 @@ namespace AiQ_GUI
} }
catch (WebDriverTimeoutException) catch (WebDriverTimeoutException)
{ {
MainForm.Instance.AddToActionsList("Could not load web page " + url + "1. Check camera has no password set. " + Environment.NewLine + "2. If unable to fix speak to supervisor."); MainForm.Instance.AddToActionsList($"Could not load web page {Level.ERROR}" + url + $"1. Check camera has no password set. {Level.ERROR}" + Environment.NewLine + $"2. If unable to fix speak to supervisor.{Level.ERROR}");
} }
} }
@@ -39,6 +39,47 @@ namespace AiQ_GUI
return elementID; 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 // 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) public static void ClickElementByID(string elementID, ChromeDriver driver, bool tryagain = true)
{ {
@@ -58,23 +99,46 @@ namespace AiQ_GUI
ClickElementByID(elementID, driver, false); ClickElementByID(elementID, driver, false);
} }
MainForm.Instance.AddToActionsList("Could not click " + elementID); MainForm.Instance.AddToActionsList($"Could not click {Level.WARNING}" + elementID);
} }
} }
// Initialises and opens a ChromeDriver with specific options // Initialises and opens a ChromeDriver with specific options
public static ChromeDriver OpenDriver() public static ChromeDriver OpenDriver()
{ {
ChromeDriverService chromeDriverService = ChromeDriverService.CreateDefaultService(); string tempProfile = null;
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);
ChromeDriver driver = new(chromeDriverService, options); try
driver.Manage().Timeouts().PageLoad = TimeSpan.FromSeconds(5); {
return driver; 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 // 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 await Task.Delay(500); // Wait for the change to take effect
if (!await Checkflashline(driver, CamAct)) if (!await Checkflashline(driver, CamAct))
{
Logging.LogWarningMessage("Bad flashline after changing: " + fullId, SoakLogFile); Logging.LogWarningMessage("Bad flashline after changing: " + fullId, SoakLogFile);
MainForm.Instance.AddToActionsList("Bad flashline after changing: " + fullId);
}
} }
// Monitors the flashline element's color to determine success (green) or failure (red) of a camera // Monitors the flashline element's color to determine success (green) or failure (red) of a camera

View File

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