This commit is contained in:
2025-11-26 14:39:07 +00:00
parent 791cee91d2
commit e29d104d47
15 changed files with 304 additions and 268 deletions

View File

@@ -5,6 +5,13 @@ using System.Reflection;
namespace AiQ_GUI namespace AiQ_GUI
{ {
public enum Level
{
ERROR,
WARNING,
LOG
}
public partial class MainForm : Form public partial class MainForm : Form
{ {
// Classes // Classes
@@ -19,8 +26,6 @@ namespace AiQ_GUI
private List<CancellationTokenSource> soakCtsList = []; private List<CancellationTokenSource> soakCtsList = [];
private List<Task> soakTasks = []; private List<Task> soakTasks = [];
public const string GoogleDrivePath = @"G:\Shared drives\MAV Production GUI's\AiQ\GUI's\";
// Colours // Colours
public static readonly Color BtnColour = Color.FromArgb(70, 65, 80); public static readonly Color BtnColour = Color.FromArgb(70, 65, 80);
public static readonly Color TxBxColour = Color.FromArgb(53, 51, 64); public static readonly Color TxBxColour = Color.FromArgb(53, 51, 64);
@@ -36,20 +41,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
{ {
@@ -71,6 +74,7 @@ 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!");
@@ -89,9 +93,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)
@@ -209,8 +210,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
@@ -253,8 +254,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
@@ -286,8 +287,6 @@ namespace AiQ_GUI
{ {
await PreTestFailed("Diagnostic Failure"); await PreTestFailed("Diagnostic Failure");
} }
} }
// ***** Pass/Fails ***** // ***** Pass/Fails *****
@@ -336,15 +335,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)
@@ -354,13 +349,10 @@ namespace AiQ_GUI
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);
string RedLbls = string.Join(Environment.NewLine, PnlLbls.Controls string RedLbls = string.Join(Environment.NewLine, PnlLbls.Controls
@@ -400,7 +392,6 @@ namespace AiQ_GUI
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 = "";
@@ -514,7 +505,10 @@ namespace AiQ_GUI
AddToActionsList($"{DiagsAPI.MAC} not recognised as NVIDIA MAC address"); AddToActionsList($"{DiagsAPI.MAC} not recognised as NVIDIA MAC address");
} }
else else
{
lblMac.ForeColor = Color.Red; lblMac.ForeColor = Color.Red;
AddToActionsList($"{DiagsAPI.MAC} not recognised as a MAC address");
}
// 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);
@@ -597,9 +591,7 @@ 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");
}
} }
} }
} }
@@ -638,11 +630,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;
@@ -652,6 +640,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")
@@ -727,7 +719,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
} }
@@ -804,7 +795,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;
} }
@@ -937,15 +930,27 @@ namespace AiQ_GUI
} }
// ***** 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.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();
} }
@@ -1146,7 +1151,7 @@ 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)
{ {
@@ -1173,7 +1178,7 @@ 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
string RESP = await FlexiAPI.HTTP_Update("GLOBAL--FlexiApplication", 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)
{ {
@@ -1313,7 +1318,11 @@ 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);
@@ -1327,18 +1336,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;
} }
} }
@@ -1574,7 +1579,7 @@ 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(10000); await Task.Delay(10000);
} }
} }
@@ -1596,7 +1601,7 @@ namespace AiQ_GUI
continue; continue;
try try
{ {
AddToActionsList($"Setting 211 & God Mode off for camera {SCL.IP}", false); 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 Network.Initialize("developer", SCL.DevPass); // Ensure network is initialized to the right camera
await CameraModules.FactoryResetModules(SCL.IP); // Reset camera modules await CameraModules.FactoryResetModules(SCL.IP); // Reset camera modules
@@ -1612,6 +1617,13 @@ namespace AiQ_GUI
{ {
AddToActionsList("Failed to set all cameras to 211 and god mode off. Reason: " + ex.Message); // In case non AiQ's get caught up AddToActionsList("Failed to set all cameras to 211 and god mode off. Reason: " + ex.Message); // 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 await Task.Delay(5000); // Wait for 5 seconds to allow the camera to restart
@@ -1619,11 +1631,11 @@ namespace AiQ_GUI
if ((FoundCams.Count == i && FoundCams.Contains("192.168.1.211")) || FoundCams.Count == 1) if ((FoundCams.Count == i && FoundCams.Contains("192.168.1.211")) || FoundCams.Count == 1)
{ {
AddToActionsList("All cameras successfully changed to 211.", false); AddToActionsList("All cameras successfully changed to 211.", Level.LOG);
} }
else else
{ {
AddToActionsList($"Some cameras failed: Found {FoundCams.Count}, expected {i}.", true); AddToActionsList($"Some cameras failed: Found {FoundCams.Count}, expected {i}.", Level.ERROR);
} }
} }
} }
@@ -1671,59 +1683,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);
await Task.Delay(1000); // Gives extra time to allow for Network to initialize
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;
} }
@@ -1743,19 +1703,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();
string ans = await FlexiAPI.HTTP_Fetch("GLOBAL--Device", "192.168.0.71");
AddToActionsList(ans);
// /api/config-ids - For getting all available config IDs // /api/config-ids - For getting all available config IDs
// Make every log file in the soak log directory into a soak test report PDF
var files = from file in Directory.EnumerateFiles("C:\\ProgramData\\MAV\\AiQ_GUI") select file;
foreach (var file in files)
{
if (file.Contains("SoakLog"))
{
// File name: SoakLog_{Serial}_{Model}.log
string[] parts = file.Split('_', '.').Select(p => p.Trim()).ToArray();
Camera NewCam = new()
{
Model = parts[3],
Serial = parts[2],
};
PDF.CreateSoakTestReport(NewCam, "SoakTestRig", DateTime.Now, file);
}
}
stopWatchTest.Stop(); stopWatchTest.Stop();
AddToActionsList("RunTime " + stopWatchTest.Elapsed.ToString(@"hh\:mm\:ss\.ff")); AddToActionsList("RunTime " + stopWatchTest.Elapsed.ToString(@"hh\:mm\:ss\.ff"), Level.LOG);
} }
} }
} }

View File

@@ -123,11 +123,8 @@
<metadata name="ToolTipClipboard.TrayLocation" type="System.Drawing.Point, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a"> <metadata name="ToolTipClipboard.TrayLocation" type="System.Drawing.Point, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
<value>159, 17</value> <value>159, 17</value>
</metadata> </metadata>
<metadata name="ToolTipAvailable.TrayLocation" type="System.Drawing.Point, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
<value>17, 17</value>
</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

@@ -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.4.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>

View File

@@ -4,7 +4,7 @@ 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)
{ {
@@ -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");
}
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

View File

@@ -378,7 +378,7 @@ namespace AiQ_GUI
using OpenFileDialog openFileDialog1 = new() using OpenFileDialog openFileDialog1 = new()
{ {
InitialDirectory = MainForm.GoogleDrivePath, InitialDirectory = GoogleAPI.GoogleDrivePath,
Filter = "CSV files (*.csv)|*.csv", Filter = "CSV files (*.csv)|*.csv",
FilterIndex = 0 FilterIndex = 0
}; };
@@ -387,7 +387,7 @@ namespace AiQ_GUI
fileToUpload = openFileDialog1.FileName; fileToUpload = openFileDialog1.FileName;
else else
{ {
MainForm.Instance.AddToActionsList("File selection cancelled.", false); MainForm.Instance.AddToActionsList("File selection cancelled.", Level.WARNING);
return; return;
} }
@@ -396,7 +396,7 @@ namespace AiQ_GUI
if ((isIR && !filename.Contains("IR")) || (!isIR && !filename.Contains("OV"))) if ((isIR && !filename.Contains("IR")) || (!isIR && !filename.Contains("OV")))
{ {
MainForm.Instance.AddToActionsList($"Incorrect file selected. Expected {(isIR ? "IR" : "OV")} file", false); MainForm.Instance.AddToActionsList($"Incorrect file selected. Expected {(isIR ? "IR" : "OV")} file", Level.WARNING);
return; return;
} }
@@ -408,7 +408,7 @@ namespace AiQ_GUI
if (parts.Length < 3) if (parts.Length < 3)
{ {
MainForm.Instance.AddToActionsList($"Invalid row format at line {i + 1}", false); MainForm.Instance.AddToActionsList($"Invalid row format at line {i + 1}", Level.WARNING);
continue; continue;
} }
@@ -419,21 +419,78 @@ namespace AiQ_GUI
// VISCA format check // VISCA format check
if (!RegexCache.VISCAAPIRegex().IsMatch(command)) if (!RegexCache.VISCAAPIRegex().IsMatch(command))
{ {
MainForm.Instance.AddToActionsList($"{name}: Invalid VISCA command ({command})", false); MainForm.Instance.AddToActionsList($"{name}: Invalid VISCA command ({command})", Level.WARNING);
continue; // do not send it if bad continue; // do not send it if bad
} }
string result = await APIHTTPVISCA(ipAddress, command, isIR); string result = await APIHTTPVISCA(ipAddress, command, isIR);
if (result.Contains(expectedResponse)) if (result.Contains(expectedResponse))
MainForm.Instance.AddToActionsList($"{name}: Success ({(isIR ? "IR" : "Colour")})", true); MainForm.Instance.AddToActionsList($"{name}: Success ({(isIR ? "IR" : "Colour")})", Level.LOG);
else else
MainForm.Instance.AddToActionsList($"{name}: Unexpected response ({result})", false); MainForm.Instance.AddToActionsList($"{name}: Unexpected response ({result})", Level.ERROR);
await Task.Delay(150); await Task.Delay(150);
} }
MainForm.Instance.AddToActionsList($"Upload complete ({(isIR ? "IR" : "Colour")}).", true); MainForm.Instance.AddToActionsList($"Upload complete ({(isIR ? "IR" : "Colour")}).", Level.LOG);
}
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.ERROR);
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}", Level.LOG);
await Task.Delay(500);
}
} }
} }
@@ -443,10 +500,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.")]
@@ -510,13 +565,5 @@ namespace AiQ_GUI
public class Property public class Property
{ {
public string Value { get; set; } = string.Empty; public string Value { get; set; } = string.Empty;
public string datatype { get; set; }
}
public class VaxtorConfig
{
public string id { get; set; }
public long configHash { get; set; }
public Property propMinCharHeight { get; set; }
public Property propMinGlobalConfidence { get; set; }
} }
} }

View File

@@ -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()
@@ -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(); // 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

@@ -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

8
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();
@@ -37,7 +32,6 @@ namespace AiQ_GUI
{ {
MainForm.Instance.AddToActionsList("Error loading Local Data Store"); MainForm.Instance.AddToActionsList("Error loading Local Data Store");
return null; // Return null to indicate failure return null; // Return null to indicate failure
} }
} }

View File

@@ -1,6 +1,4 @@
using System.Diagnostics; namespace AiQ_GUI
namespace AiQ_GUI
{ {
internal class Logging internal class Logging
{ {
@@ -43,7 +41,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}");
} }
} }
} }

View File

@@ -122,10 +122,8 @@ 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}");
//else
// AddToActionsList($"No rows found for {modelNumber}");
} }
conn.Close(); conn.Close();
} }
@@ -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).");
//else
// AddToActionsList("No rows inserted into DiagsStats (unexpected).");
} }
} }

View File

@@ -23,7 +23,7 @@ namespace AiQ_GUI
DateTime? lastStatsRun = null; DateTime? lastStatsRun = null;
try try
{ {
using (OleDbCommand cmdPeriod = new OleDbCommand("SELECT LastStatsRun FROM UniversalData", conn)) using (OleDbCommand cmdPeriod = new("SELECT LastStatsRun FROM UniversalData", conn))
{ {
object res = cmdPeriod.ExecuteScalar(); object res = cmdPeriod.ExecuteScalar();
if (res != null && res != DBNull.Value) if (res != null && res != DBNull.Value)
@@ -37,7 +37,7 @@ namespace AiQ_GUI
} }
// ==================== MAIN STATS EXPORT ==================== // ==================== MAIN STATS EXPORT ====================
string selectColumns = @" const string selectColumns = @"
ModelNumber, ModelNumber,
[Total Tests Run], [Total Tests Run],
[Pre Tests Passed], [Pre Tests Passed],
@@ -125,7 +125,7 @@ namespace AiQ_GUI
DateTime monthStart = new(now.Year, now.Month, 1); DateTime monthStart = new(now.Year, now.Month, 1);
DateTime nextMonth = monthStart.AddMonths(1); DateTime nextMonth = monthStart.AddMonths(1);
string diagsQuery = @" const string diagsQuery = @"
SELECT SELECT
[Date], [Date],
[Model], [Model],
@@ -136,36 +136,33 @@ namespace AiQ_GUI
FROM DiagsStats FROM DiagsStats
WHERE [Date] >= ? AND [Date] < ?"; WHERE [Date] >= ? AND [Date] < ?";
using (OleDbCommand diagsCmd = new OleDbCommand(diagsQuery, conn)) using (OleDbCommand diagsCmd = new(diagsQuery, conn))
{ {
diagsCmd.Parameters.Add(new OleDbParameter { OleDbType = OleDbType.Date, Value = monthStart }); diagsCmd.Parameters.Add(new OleDbParameter { OleDbType = OleDbType.Date, Value = monthStart });
diagsCmd.Parameters.Add(new OleDbParameter { OleDbType = OleDbType.Date, Value = nextMonth }); diagsCmd.Parameters.Add(new OleDbParameter { OleDbType = OleDbType.Date, Value = nextMonth });
using (OleDbDataAdapter diagsAdapter = new OleDbDataAdapter(diagsCmd)) using OleDbDataAdapter diagsAdapter = new(diagsCmd);
DataTable diagsTable = new();
diagsAdapter.Fill(diagsTable);
if (diagsTable.Rows.Count > 0)
{ {
DataTable diagsTable = new(); int startRow = (ws.LastRowUsed()?.RowNumber() ?? 0) + 3;
diagsAdapter.Fill(diagsTable);
if (diagsTable.Rows.Count > 0) ws.Cell(startRow, 1).Value = "Diags Stats (Current Month)";
{ ws.Cell(startRow, 1).Style.Font.Bold = true;
int startRow = (ws.LastRowUsed()?.RowNumber() ?? 0) + 3; ws.Cell(startRow, 1).Style.Font.FontSize = 12;
ws.Cell(startRow, 1).Value = "Diags Stats (Current Month)"; ws.Cell(startRow + 1, 1).InsertTable(diagsTable, "Diags_Stats", true);
ws.Cell(startRow, 1).Style.Font.Bold = true; ws.Columns().AdjustToContents();
ws.Cell(startRow, 1).Style.Font.FontSize = 12;
ws.Cell(startRow + 1, 1).InsertTable(diagsTable, "Diags_Stats", true); MainForm.Instance.AddToActionsList($"Added DiagsStats ({diagsTable.Rows.Count} rows) starting at row {startRow}.");
ws.Columns().AdjustToContents();
MainForm.Instance.AddToActionsList($"Added DiagsStats ({diagsTable.Rows.Count} rows) starting at row {startRow}.");
}
} }
} }
// ==================== SAVE MAIN SHEET ==================== // ==================== SAVE MAIN SHEET ====================
workbook.SaveAs(exportPath); workbook.SaveAs(exportPath);
MainForm.Instance.AddToActionsList($"Added {sheetName} to workbook: {exportPath}"); MainForm.Instance.AddToActionsList($"Added {sheetName} to workbook: {exportPath}");
workbook.Dispose(); workbook.Dispose();
// ==================== BACKUP TABLE (ALWAYS, BEFORE RESET) ==================== // ==================== BACKUP TABLE (ALWAYS, BEFORE RESET) ====================
@@ -199,7 +196,7 @@ namespace AiQ_GUI
{ {
try try
{ {
string resetSql = @" const string resetSql = @"
UPDATE AiQ SET UPDATE AiQ SET
[Total Tests Run] = 0, [Total Tests Run] = 0,
[Pre Tests Passed] = 0, [Pre Tests Passed] = 0,

View File

@@ -32,10 +32,8 @@ namespace AiQ_GUI
UP = await PingIP("10.10.10.137"); UP = await PingIP("10.10.10.137");
await Task.Delay(500); await Task.Delay(500);
if (i >= 5) if (i >= 5)
{
//MainForm.Instance.AddToActionsList($"Could not initialise new network with USER: {username} & PSWD: {password}");
return; // Try 5 times max return; // Try 5 times max
}
i++; i++;
} }
} }

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

@@ -159,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.
@@ -34,7 +34,7 @@ namespace AiQ_GUI
} }
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: {ex.Message}");
// Wait 10 seconds before trying again // Wait 10 seconds before trying again
@@ -50,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: {ex.Message}", SoakLogFile, CamInfo.CheckBox);
return; return;
} }
@@ -72,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.", 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
} }
@@ -86,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: {ex.Message}", SoakLogFile, CamInfo.CheckBox);
} }
} }
@@ -96,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: {ex.Message}", SoakLogFile, CamInfo.CheckBox);
} }
lastHour = currentHour; lastHour = currentHour;
} }
@@ -119,7 +119,7 @@ namespace AiQ_GUI
} }
catch (Exception ex) catch (Exception ex)
{ {
Logging.LogErrorMessage($"ChangeRandomDropdown failed: {ex.Message}", SoakLogFile); SoakError($"ChangeRandomDropdown failed: {ex.Message}", SoakLogFile, CamInfo.CheckBox);
} }
try try
@@ -144,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
{ {
@@ -152,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
@@ -198,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")
@@ -208,7 +210,7 @@ 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);
@@ -222,7 +224,7 @@ 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);
@@ -236,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]}",
SoakLogFile SoakLogFile,
CamInfo.CheckBox
); );
} }
else else
@@ -251,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);
@@ -259,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)
@@ -319,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