12 Commits

Author SHA1 Message Date
4dace8edef refactor(gui,api,logging): UI layout tweaks, cleanup, and improved diagnostics
- CbBxCamType now spans the full width of CbBxCameraModel
- BtnTest_Click moved to the bottom of AiQ_GUI.cs for easier testing/navigation
- Unused using directives removed across files
- Login credentials factored out and reused in MobileAPI.cs
- VLC.cs Capture function reviewed and superseded by TakeSnapshot
- InsertCamTab implementation updated to avoid use of `var`
- Misleading '// Non-ONVIF cameras' comment corrected
- Added Level.Debug logging, accessible only to developers, for detailed diagnostics
2026-01-15 16:36:17 +00:00
4c74e237c2 • Class rename: MobilePreTest → MobileTests
• New method added: RunFinalTestAsync() - Performs final mobile tests including SSH verification of setup files
• Calls SSH.MobiletxtCheck() to verify /home/mav/Mobile-Setup-configuration-marker.txt exists on device
• Updates UI with "MobileSetup.sh" pass/fail status
• Mobile Tests/Mobile API.cs (New File Created)
New Method Added: MobiletxtCheck(string IPAddress) - Boolean method to verify setup marker file
• Checks if /home/mav/Mobile-Setup-configuration-marker.txt exists on device via SSH
• Returns exit status 0 (true) if file exists, false otherwise
• Used in RunFinalTestAsync() for setup verification
2026-01-14 11:49:32 +00:00
a4c51b0af0 UI Components/Features
• Added BtnVlcPlay WHich toggles between Vlc.Play and VLC.Stop
2026-01-14 10:36:58 +00:00
d1d90f17fc Mobile API Refactoring
• Introduced new Mobile API.cs module
• Encapsulates firmware checks, day/night mode switching, and snapshot operations
• Methods: CheckFirmwareAsync(), SetDayModeAsync(), SetNightModeAsync()
• Improves code maintainability and separates API logic from test execution

UI Components
• DayImgPcbx PictureBox: Displays day snapshot
• NightImgPcbx PictureBox: Displays night snapshot
• DayImage Label: Shows day luminance percentage
• NightImage Label: Shows night luminance percentage

Features Implemented
• Luminance-based validation: Uses ImageProcessing.GetMeanLuminance() to quantitatively analyze day and night snapshots
• Validation logic: Day luminance must exceed night luminance to pass the test
• Real-time luminance display: Appends calculated luminance percentages (0-100%) to day and night image labels
• Enhanced error reporting: Logs detailed luminance values when test fails for debugging purposes

• Captures day mode snapshot → Analyzes luminance
• Captures night mode snapshot → Analyzes luminance
• Compares values and updates UI labels with percentages
• Result: "Day/Night Mode = Passed" (green) or "Day/Night Mode = Failed" (red) with error details
2026-01-13 15:31:50 +00:00
4a2e874b9d - Replaced single TabPage CurrentTab with List<TabPage> CurrentTabs to support multiple tabs.
- Added new tab called Video

- Removed unused tabs on load (Soak, Mobile, Video, Images).

- Refactored InsertCamTab():

- Uses CurrentTabs to manage multiple tabs per camera type.

Mobile → inserts Video + Mobile tabs.
AiQ → inserts Images + Soak tabs.

-Cleans up previous tabs before inserting new ones.

-Updated BtnTest_Click():
Added 5-second delay before snapshot.
Takes snapshot after video starts playing.

- New file created VLC.cs
Streams the given IP into the Video tab and takes a snapshot
2026-01-12 16:30:04 +00:00
90b3f7caff • MobileTests.cs
- Pre test now checks firmware and adds the label dynamically to the panel

• AiQ_GUI.Designer.cs
  - Added CbBxCameraModel.SelectedIndexChanged to properly wire UI element.

• Microsoft/StatsExcel.cs
  - Improved workbook loading error handling to distinguish between corruption and file access issues.
2026-01-09 15:49:54 +00:00
693da58fcb • Camera/LED.cs
• Added null-checks so labels are created dynamically when lblVorI is null (uses MainForm.Instance.AddLabelToPanel).
• Camera/CameraModules.cs
• Added null-label handling to create dynamic module labels with the correct content (OK or the error message).
• AiQ_GUI.cs
• Increased dynamic label width in MakeNewLabel from 220 → 700 to avoid truncated messages.
• Succesfully ran through a pre-test with dynamic labels
2026-01-05 12:35:28 +00:00
36f9639baa Dynamic labels & Changed color to light green
- Filesystem size
- Flexi version
- Flexi Revision
- MAC
- Timestamp
- Temperature
- ZoomLock
- Serial and model Number
- Licenses
- CPU usage
2025-12-23 13:01:15 +00:00
872be2e105 Changes:
- Code refactored, DIagsPt1 and 2 moved into there own folder called DIagnostics
- Pre tests and final tests moved into a AiQ tests
- Mobile Pre Tests  added
- Mobile pre test - Added firmware check for the SRZ
2025-12-22 14:37:43 +00:00
7aba890514 Beginning of the Implementing multple cameras into the AiQ GUI
Features added:
- Onvif discoverable
- New Camera Type combo box
- Access has be changed to be more scalable and dynamic in preparation for more cameras
2025-12-19 16:14:13 +00:00
760987fa75 Merged colours 2025-12-02 13:08:08 +00:00
4c624d7e29 V4.6 2025-12-02 11:02:24 +00:00
26 changed files with 1709 additions and 1150 deletions

13
AiQ Tests/AiQLiteTests.cs Normal file
View File

@@ -0,0 +1,13 @@
using System;
using System.Collections.Generic;
using System.Text;
namespace AiQ_GUI.AiQ_Tests
{
internal class AiQLiteTests
{
// Tests to be written here later
}
}

158
AiQ Tests/AiQTests.cs Normal file
View File

@@ -0,0 +1,158 @@
using AiQ_GUI;
using AiQ_GUI.AiQ_Tests;
public class AiQTests
{
public static async Task AiQPreTest()
{
if (!await CameraModules.SetZoomLockOn(MainForm.Instance.CamOnTest.IP))
Helper.RestartApp();
string LEDreply = await FlexiAPI.APIHTTPLED(MainForm.Instance.CamOnTest.IP, LEDPOWER.MID); // Set LED's to medium (0x30)
if (!LEDreply.Contains("Power levels set successfully"))
MainForm.Instance.AddToActionsList($"LED level could not be set: {LEDreply}", Level.ERROR);
await CameraModules.FactoryResetModules(MainForm.Instance.CamOnTest.IP); // Reset both modules and double check
MainForm.Instance.sshData = SSH.CollectSSHData(MainForm.Instance.CamOnTest.IP); // SSH into camera to get Vaxtor packages, filesystem size and if tailscale is installed.
await SSH.CheckFSSize(MainForm.Instance.CamOnTest.IP, MainForm.Instance.LblFilesystemSize, MainForm.Instance.sshData); // Check Filesystem size is between 100GB & 150GB
Helper.DCPowerCheck(MainForm.Instance.LblDC); // If the camera is DC powered check it is within limits
// Requests, deserialises and checks the diagnostics API is correct
await TestingFunctions.CheckDiagsAPIPt1();
// Check module has gone to default config
CameraModules.CheckCamModule(TestingFunctions.DiagsAPI.IRmodule, MainForm.Instance.LblIRModule, MainForm.Instance.CamOnTest); // IR
CameraModules.CheckCamModule(TestingFunctions.DiagsAPI.OVmodule, MainForm.Instance.LblOVModule, MainForm.Instance.CamOnTest); // OV
// Check voltage and current are OK.
LED.CheckLEDs(TestingFunctions.DiagsAPI.LedVoltage, MainForm.Instance.LblLEDV, "V", CameraAccessInfo.LED_V); // Voltage
LED.CheckLEDs(TestingFunctions.DiagsAPI.LedCurrent, MainForm.Instance.LblLEDI, "mA", CameraAccessInfo.LED_I); // Current
MainForm.Instance.Refresh(); // Make sure all labels are updated before checking them
// 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 (MainForm.Instance.RhTxBxActions.Text.Length < 2 && MainForm.Instance.PnlLbls.Controls.OfType<Label>().Any(c => c.ForeColor == Color.Red) == false)
{
// If camera already has a model or serial then ask if it is new
if (RegexCache.SerialRegex().IsMatch(TestingFunctions.DiagsAPI.serialNumber) || RegexCache.ModelRegex().IsMatch(TestingFunctions.DiagsAPI.modelNumber))
{
if (await MainForm.Instance.DisplayQuestion($"Would you like to allocate a serial number to this camera?"))
await MainForm.Instance.AllocateSerial();
else if (GoogleAPI.UpdateSpreadSheetRePreTest(CameraAccessInfo.SpreadsheetID, MainForm.Instance.Vers) != "OK") // If rerun might be different values so update SS
MainForm.Instance.AddToActionsList("Failed to write to spreadsheet, please check manually", Level.WARNING);
// else if (Excel.UpdateSpreadSheetPreTest(CameraAccessInfo.SpreadsheetID, Vers, CamOnTest.GetCamDesc(), CamOnTest.Model) != "OK")
// AddToActionsList("Failed to write to spreadsheet, please check manually");
}
else // No serial or model so allocate one
await MainForm.Instance.AllocateSerial();
if (MainForm.Instance.RhTxBxActions.Text.Length < 2 && MainForm.Instance.PnlLbls.Controls.OfType<Label>().Any(c => c.ForeColor == Color.Red) == false)
await MainForm.Instance.PreTestPassed();
}
else
{
await MainForm.Instance.PreTestFailed("Diagnostic Failure");
}
}
public static async Task AiQFinalTest()
{
if (MainForm.Instance.LblTestTubePing.Text == "❌") // No test tube so test like an IQ
{
string LEDreply = await FlexiAPI.APIHTTPLED(MainForm.Instance.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"))
MainForm.Instance.AddToActionsList($"LED level could not be set: {LEDreply}", Level.ERROR);
}
//else if (!await TestTube.CheckInTestTube(MainForm.Instance.CamOnTest.IP)) // Sets LED's to medium power after checking it is in the test tube
// await MainForm.Instance.TestFailed(MainForm.Instance.BtnStartTest, "Camera not in test tube");
Task VisCheck = Helper.VisualCheck(MainForm.Instance.BtnStartTest);
if (!await CameraModules.ZoomModules("1F40", MainForm.Instance.CamOnTest.IP)) // Zoom to 8000 (1F40h) at the same time.
await MainForm.Instance.TestFailed(MainForm.Instance.BtnStartTest, "Could not zoom modules to 8000");
if (!await CameraModules.SetZoomLockOn(MainForm.Instance.CamOnTest.IP))
Helper.RestartApp();
await Task.Delay(1000); // Without sleep it kept failing the factory reset as camera modules were not ready yet
await CameraModules.FactoryResetModules(MainForm.Instance.CamOnTest.IP); // Reset both modules and double check
string VISCAReply = await FlexiAPI.APIHTTPVISCA(MainForm.Instance.CamOnTest.IP, "8101043903FF", true); // Manual mode to be able to manipulate the SIG settings.
if (VISCAReply != "9041FF9051FF")
MainForm.Instance.AddToActionsList("Couldn't set to manual mode", Level.ERROR);
await CameraModules.SetSIG(MainForm.Instance.CbBxShutter, MainForm.Instance.CbBxIris, MainForm.Instance.CbBxGain, MainForm.Instance.CamOnTest.IP); // Set SIG according to the drop downs in settings for a good picture ready for image check
await ImageProcessing.ImageCheck(MainForm.Instance.PicBxOV, MainForm.Instance.PicBxIRF2, MainForm.Instance.PicBxIRF16, MainForm.Instance.LblIRImageF2, MainForm.Instance.LblIRImageF16, MainForm.Instance.CamOnTest); // Populates the picture boxes and checks iris changes
await VisCheck; // Before changing UI elements wait for user to finish Visual check
MainForm.Instance.TabImagesandSettings.SelectedIndex = 2; // Swaps to the images tab
MainForm.Instance.Refresh(); // Show user things are happening by displaying images taken
// TODO - Force expire sighting.
Task Wait = Task.Delay(5000); // Wait for 5 seconds to allow the camera to zoom in, set settings and capture some plates before auto trim
if (CameraAccessInfo.HardwareExtras.Contains("GPS")) // Check GPS if the hardware has it
await FlexiAPI.GPSFix(MainForm.Instance.CamOnTest.IP);
// While waiting do the SSH tasks.
MainForm.Instance.sshData = SSH.CollectSSHData(MainForm.Instance.CamOnTest.IP); // SSH into camera to get Vaxtor packages, filesystem size and if tailscale is installed.
await SSH.CheckFSSize(MainForm.Instance.CamOnTest.IP, MainForm.Instance.LblFilesystemSize, MainForm.Instance.sshData); // Check Filesystem size is between 100GB & 150GB
Helper.DCPowerCheck(MainForm.Instance.LblDC); // If the camera is DC powered check it is within limits
if (CameraAccessInfo.HardwareExtras.Contains("4G")) // If it is a router camera then test the router.
{
MainForm.Instance.LblRouter.Visible = true;
if (Router.CheckRouter(Router.GetRouterInfo()))
MainForm.Instance.LblRouter.Text += "OK";
else
MainForm.Instance.LblRouter.Text += "Error with router";
}
await Wait; // Finished to 5s wait
await FlexiAPI.SetTrim(MainForm.Instance.CamOnTest.IP, MainForm.Instance.LblTestTubePing.Text); // Auto trims the cameras, some plates should have been captured in the meantime
if (!await CameraModules.ZoomModules("0000", MainForm.Instance.CamOnTest.IP)) // Zoom to full wide
await MainForm.Instance.TestFailed(MainForm.Instance.BtnStartTest, "Could not zoom modules to full wide");
await Task.Delay(1000); // Wait to be sure cameras are zoomed out.
await CameraModules.FactoryResetModules(MainForm.Instance.CamOnTest.IP); // Reset both modules and double check
if (MainForm.Instance.LblTestTubePing.Text == "❌") // Set LED's to MID in prep for diagnostics API
{
string LEDreply = await FlexiAPI.APIHTTPLED(MainForm.Instance.CamOnTest.IP, LEDPOWER.MID); // Set LED's to medium (0x30)
if (!LEDreply.Contains("Power levels set successfully"))
MainForm.Instance.AddToActionsList($"LED level could not be set: {LEDreply}", Level.ERROR);
}
await FlexiAPI.SetVaxtorMinMaxPlate(MainForm.Instance.CamOnTest.IP);
DateTime PCTime = DateTime.Now; // Grab PC time as close to the API as possible to pass onto PDF later
await TestingFunctions.CheckDiagsAPIPt1(); // Requests, deserialises and checks the diagnostics API is correct
await TestingFunctions.CheckDiagsAPIPt2(); // For only final test parts
// Check module has gone to default config
CameraModules.CheckCamModule(TestingFunctions.DiagsAPI.IRmodule, MainForm.Instance.LblIRModule, MainForm.Instance.CamOnTest); // IR
CameraModules.CheckCamModule(TestingFunctions.DiagsAPI.OVmodule, MainForm.Instance.LblOVModule, MainForm.Instance.CamOnTest); // OV
// Check voltage and current are OK.
LED.CheckLEDs(TestingFunctions.DiagsAPI.LedVoltage, MainForm.Instance.LblLEDV, "V", CameraAccessInfo.LED_V); // Voltage
LED.CheckLEDs(TestingFunctions.DiagsAPI.LedCurrent, MainForm.Instance.LblLEDI, "mA", CameraAccessInfo.LED_I); // Current
MainForm.Instance.Refresh(); // Make sure all labels are updated before checking them
// 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 (MainForm.Instance.RhTxBxActions.Text.Length > 2 || MainForm.Instance.PnlLbls.Controls.OfType<Label>().Any(c => c.ForeColor == Color.Red) == true)
await MainForm.Instance.TestFailed(MainForm.Instance.BtnStartTest, "Diagnostic Failure");// If approved then pass otherwise GUI would have restarted before getting to TestPassed.
await MainForm.Instance.TestPassed(PCTime);
}
}

208
AiQ Tests/Diagnostics.cs Normal file
View File

@@ -0,0 +1,208 @@
namespace AiQ_GUI.AiQ_Tests
{
public class TestingFunctions
{
public static Diags DiagsAPI = new();
// Colours
public static readonly Color BtnColour = Color.FromArgb(70, 65, 80);
public static readonly Color TxBxColour = Color.FromArgb(53, 51, 64);
// ***** Testing functions *****
public static async Task CheckDiagsAPIPt1() // Parts done on pre and final test
{
DiagsAPI = await FlexiAPI.GetDiagnostics(MainForm.Instance.CamOnTest.IP); // Diagnostic API request
// Check Flexi Version
string flexiVerText = "Flexi Version = " + DiagsAPI.FlexiVersion;
bool flexiVerGreen = DiagsAPI.FlexiVersion == UniversalData.ExpFlexiVer;
if (!flexiVerGreen)
flexiVerText += " Expected = " + UniversalData.ExpFlexiVer;
MainForm.Instance.AddLabelToPanel(flexiVerText, !flexiVerGreen);
// Check Flexi Revision
string flexiRevText = "Flexi Revision = " + DiagsAPI.FlexiRevision;
bool flexiRevGreen = DiagsAPI.FlexiRevision == UniversalData.ExpFlexiRev;
if (!flexiRevGreen)
flexiRevText += " Expected = " + UniversalData.ExpFlexiRev;
MainForm.Instance.AddLabelToPanel(flexiRevText, !flexiRevGreen);
// Display MAC
string macText = "MAC = " + DiagsAPI.MAC;
bool macIsRed = false;
if (RegexCache.MACRegexNVIDIA().IsMatch(DiagsAPI.MAC)) // Checks it is in the right format and is a NVIDIA registered MAC address
{
// Valid NVIDIA MAC
}
else if (RegexCache.MACRegex().IsMatch(DiagsAPI.MAC)) // Is a valid MAC, but not NVIDIA
{
macIsRed = true;
MainForm.Instance.AddToActionsList($"{DiagsAPI.MAC} not recognised as NVIDIA MAC address", Level.ERROR);
}
else
{
macIsRed = true;
MainForm.Instance.AddToActionsList($"{DiagsAPI.MAC} not recognised as a MAC address", Level.ERROR);
}
MainForm.Instance.AddLabelToPanel(macText, macIsRed);
// Check timestamp
DateTime dateTime = new(1970, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc);
dateTime = dateTime.AddSeconds(DiagsAPI.timeStamp).ToLocalTime();
long timediff = DateTimeOffset.UtcNow.ToUnixTimeSeconds() - DiagsAPI.timeStamp;
string timestampText = "Timestamp = " + dateTime;
bool timestampIsRed = false;
if (timediff > 10) // Over 10 seconds ago
{
timestampText += $" Time behind by {timediff}s";
timestampIsRed = true;
}
else if (timediff < 0) // Time is in the future.
{
timestampText += $" Time is in the future by {Math.Abs(timediff)}s";
timestampIsRed = true;
}
MainForm.Instance.AddLabelToPanel(timestampText, timestampIsRed);
// Check Temperature
string tempText = "Temperature = " + DiagsAPI.IntTemperature + "°C";
bool tempIsRed = DiagsAPI.IntTemperature <= 20 || DiagsAPI.IntTemperature >= 70;
MainForm.Instance.AddLabelToPanel(tempText, tempIsRed);
// Check Zoom Lock
string zoomLockText = "Zoom Lock = " + DiagsAPI.zoomLock;
bool zoomLockIsRed = false;
if (DiagsAPI.zoomLock == true)
{
if (DiagsAPI.IRmodule.zoom != DiagsAPI.OVmodule.zoom) // Checks if zoomlock is doing what is says it should
{
zoomLockText += $" Zoomlock on but {DiagsAPI.IRmodule.zoom}≠{DiagsAPI.OVmodule.zoom}";
zoomLockIsRed = true;
}
}
else
zoomLockIsRed = true;
MainForm.Instance.AddLabelToPanel(zoomLockText, zoomLockIsRed);
}
public static async Task CheckDiagsAPIPt2() // Parts only done on final test
{
if (MainForm.Instance.RhTxBxActions.Text.Contains("Error reading JSON")) // If failed to deserialise in part 1
return;
try // In case serial or model are blank
{
string serialText = "Serial Number = " + DiagsAPI.serialNumber;
bool serialIsRed = !RegexCache.SerialRegex().IsMatch(DiagsAPI.serialNumber);
MainForm.Instance.AddLabelToPanel(serialText, serialIsRed);
string modelText = "Model Number = " + DiagsAPI.modelNumber;
bool modelIsRed = !RegexCache.ModelRegex().IsMatch(DiagsAPI.modelNumber);
MainForm.Instance.AddLabelToPanel(modelText, modelIsRed);
string apiModel = DiagsAPI.modelNumber?.Trim();
string selectedModel = MainForm.Instance.CamOnTest.Model?.Trim();
if (string.IsNullOrWhiteSpace(selectedModel) && MainForm.Instance.CbBxCameraModel.SelectedItem != null)
selectedModel = MainForm.Instance.CbBxCameraModel.SelectedItem.ToString().Split('-')[0].Trim();
if (RegexCache.SerialRegex().IsMatch(DiagsAPI.serialNumber) && RegexCache.ModelRegex().IsMatch(DiagsAPI.modelNumber))
{
// If nothing selected yet, do not warn (matches old logic)
if (!string.IsNullOrWhiteSpace(selectedModel)
&& !string.Equals(apiModel, selectedModel, StringComparison.OrdinalIgnoreCase))
{
MainForm.Instance.AddToActionsList(
"Model number in camera doesn't match what has been selected",
Level.WARNING);
}
else if (!string.IsNullOrWhiteSpace(CameraAccessInfo.SpreadsheetID)&& !GoogleAPI.CheckWIP(DiagsAPI.serialNumber, CameraAccessInfo.SpreadsheetID)) // Check WIP column in serial number register, if not ticked then RMA
{
MainForm.Instance.CamOnTest.RMANum = GoogleAPI.CheckRMANum(DiagsAPI.serialNumber, DiagsAPI.modelNumber); // Corrected by qualifying with the type name
if (MainForm.Instance.CamOnTest.RMANum == 0) // Couldn't find RMA num in spreadsheet
{
MainForm.Instance.CamOnTest.RMANum = Convert.ToInt32(await MainForm.Instance.DisplayInput("What is the RMA number?"));
if (MainForm.Instance.CamOnTest.RMANum == -1) // Means they chose the 'I don't know' option
await MainForm.Instance.TestFailed(MainForm.Instance.BtnStartTest, "Please get RMA number from operations team before continuing");
}
else if (!await MainForm.Instance.DisplayQuestion($"Is {MainForm.Instance.CamOnTest.RMANum} the RMA Number?")) // '!' because if its not the right RMA number let the user to it manually
{
MainForm.Instance.CamOnTest.RMANum = Convert.ToInt32(await MainForm.Instance.DisplayInput("What is the RMA number?"));
if (MainForm.Instance.CamOnTest.RMANum == -1) // Means they chose the 'I don't know' option
await MainForm.Instance.TestFailed(MainForm.Instance.BtnStartTest, "Please get RMA number from operations team before continuing");
}
}
}
else
{
MainForm.Instance.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 (Exception ex)
{
MainForm.Instance.AddToActionsList($"Error in CheckDiagsAPIPt2 serial/model: {ex.Message}", Level.ERROR);
}
// Check licenses
List<string> licensesOnCam = new List<string>(); // Temporary list for licenses on camera
if (DiagsAPI.licenses.saf1) licensesOnCam.Add("SAF1");
if (DiagsAPI.licenses.saf2) licensesOnCam.Add("SAF2");
if (DiagsAPI.licenses.saf3) licensesOnCam.Add("SAF3");
if (DiagsAPI.licenses.saf4) licensesOnCam.Add("SAF4");
if (DiagsAPI.licenses.audit) licensesOnCam.Add("Audit");
if (DiagsAPI.licenses.stream) licensesOnCam.Add("Stream");
if (MainForm.Instance.sshData != null && MainForm.Instance.sshData.tailscale) licensesOnCam.Add("Tailscale");
MainForm.Instance.AddLabelToPanel(
"Licenses = " + (licensesOnCam.Count == 0 ? "None" : string.Join(", ", licensesOnCam)),
false);
double CPUround = Math.Round(DiagsAPI.CPUusage); // Check CPU usage isn't near max
MainForm.Instance.AddLabelToPanel(
"CPU Usage = " + CPUround + "%",
CPUround <= 50 || CPUround >= 98);
// Check Vaxtor license
bool vaxtorIsRed = false;
string vaxtorText = "Vaxtor Key ID = " + DiagsAPI.licenses.raptorKeyID;
if (DiagsAPI.licenses.raptorKeyID != "Not Licensed") vaxtorIsRed = false;
else if (CameraAccessInfo.VaxtorLic == false) vaxtorIsRed = false;
else if (await MainForm.Instance.DisplayQuestion("Was this camera licensed manually?"))
{
string ProdcutKeyID = await MainForm.Instance.DisplayInput("What is the Key ID?", false);
if (RegexCache.VaxtorRegex().IsMatch(ProdcutKeyID))
{
Access.Stats("Please Get A Valid Vaxtor Product Key Before Continuing", MainForm.Instance.CamOnTest.Model);
await MainForm.Instance.TestFailed(MainForm.Instance.BtnStartTest, "Please get a valid Vaxtor Product Key before continuing");
}
DiagsAPI.licenses.raptorKeyID = MainForm.Instance.TxBxProductKey.Text;
vaxtorText = "Vaxtor Key ID = " + DiagsAPI.licenses.raptorKeyID;
vaxtorIsRed = false;
}
else vaxtorIsRed = true;
MainForm.Instance.AddLabelToPanel(vaxtorText, vaxtorIsRed);
// Check trim is within 10% both horizontally and vertically
const int HMax = 96; // 5% of 1920 each way = ±96
const int VMax = 54; // 5% of 1080 each way = ±54
MainForm.Instance.AddLabelToPanel(
"Trim = H: " + DiagsAPI.trim[0] + " V: " + DiagsAPI.trim[1],
Math.Abs(DiagsAPI.trim[0]) > HMax || Math.Abs(DiagsAPI.trim[1]) > VMax);
}
}
}

839
AiQ_GUI.Designer.cs generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,7 +1,11 @@
using Newtonsoft.Json; using AiQ_GUI.AiQ_Tests;
using AiQ_GUI.Mobile_Tests;
using LibVLCSharp.Shared;
using Newtonsoft.Json;
using System.ComponentModel; using System.ComponentModel;
using System.Diagnostics; using System.Diagnostics;
using System.Reflection; using System.Reflection;
using Color = System.Drawing.Color;
namespace AiQ_GUI namespace AiQ_GUI
{ {
@@ -10,18 +14,19 @@ namespace AiQ_GUI
ERROR, ERROR,
WARNING, WARNING,
LOG, LOG,
Success Success,
DEBUG
} }
public partial class MainForm : Form public partial class MainForm : Form
{ {
// Classes // Classes
LocalDataStore localDataStore = new(); LocalDataStore localDataStore = new();
Diags DiagsAPI = new(); //public Diags DiagsAPI = new();
VaxtorLic VaxtorLicResp = new(); VaxtorLic VaxtorLicResp = new();
Versions Vers = new(); public Versions Vers = new();
readonly Camera CamOnTest = new(); public readonly Camera CamOnTest = new();
SSHData sshData = new(); public SSHData sshData = new();
private List<Camera> soakCameraList = []; private List<Camera> soakCameraList = [];
private List<CancellationTokenSource> soakCtsList = []; private List<CancellationTokenSource> soakCtsList = [];
@@ -34,6 +39,9 @@ namespace AiQ_GUI
[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
public static MainForm? Instance { get; private set; } public static MainForm? Instance { get; private set; }
private List<TabPage> CurrentTabs;
public MainForm() public MainForm()
{ {
InitializeComponent(); InitializeComponent();
@@ -48,7 +56,10 @@ namespace AiQ_GUI
Task<LocalDataStore> LDSWAIT = Task.Run(() => LDS.GetLDS()); // Get and deserialise LDS.json Task<LocalDataStore> LDSWAIT = Task.Run(() => LDS.GetLDS()); // Get and deserialise LDS.json
Task<string> guiVerTask = Task.Run(() => GUIUpdate.FindGUIVersion()); // Get GUI Version Task<string> guiVerTask = Task.Run(() => GUIUpdate.FindGUIVersion()); // Get GUI Version
Network.Initialize("admin", "admin"); // Initialise HTTP client Network.Initialize("admin", "admin"); // Initialise HTTP client with basic auth creds.
VidView.MediaPlayer = new MediaPlayer(VLC.libVLC); // Initialize VideoView's MediaPlayer
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
{ {
@@ -61,17 +72,18 @@ namespace AiQ_GUI
BtnStartTest.Text = "Offline Mode"; BtnStartTest.Text = "Offline Mode";
} }
// Hide on default so will only show based on the camera type selected later
TabImagesandSettings.TabPages.Remove(TabSoak);
TabImagesandSettings.TabPages.Remove(Mobile);
TabImagesandSettings.TabPages.Remove(Video);
TabImagesandSettings.TabPages.Remove(TabImages);
GUIUpdate.GUIVerShort = await guiVerTask; // Guess the GUI version will be first to finish GUIUpdate.GUIVerShort = await guiVerTask; // Guess the GUI version will be first to finish
this.Name = "AiQ GUI V" + GUIUpdate.GUIVerShort; this.Name = "AiQ GUI V" + GUIUpdate.GUIVerShort;
LblGUIVers.Text += GUIUpdate.GUIVerShort; LblGUIVers.Text += GUIUpdate.GUIVerShort;
await UniDataTask; // Have to wait for expected GUI version to compare to. await UniDataTask; // Have to wait for expected GUI version to compare to.
Task<string[]> modelListTask = Task.Run(() => Access.ReadCamTypes());// Get all model numbers
GUIUpdate.UpdateGUI(); // Check if a GUI update is available GUIUpdate.UpdateGUI(); // Check if a GUI update is available
string[] ArrayOfModels = await modelListTask;
if (ArrayOfModels != null) // Returns null if no models found or file doesn't exists
CbBxCameraType.Items.AddRange(await modelListTask); // Fill in the model number drop down
// 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.
@@ -84,7 +96,7 @@ namespace AiQ_GUI
Task CheckHWOnline = PingCheck(); // Async check all hardware is online Task CheckHWOnline = PingCheck(); // Async check all hardware is online
PopulateUIWithLDS(localDataStore); // Update fields that depend on LDS PopulateUIWithLDS(localDataStore); // Update fields that depend on LDS
CbBxCameraType.Text = localDataStore.LastModel; // Set last model that was tested into combobox CbBxCameraModel.Text = localDataStore.LastModel; // Set last model that was tested into combobox
BtnSave.BackColor = BtnSave.Enabled ? Color.ForestGreen : BtnSave.BackColor; // Sets the colour of the save button depending on if it is enabled. BtnSave.BackColor = BtnSave.Enabled ? Color.ForestGreen : BtnSave.BackColor; // Sets the colour of the save button depending on if it is enabled.
BtnRefreshUnix_Click(sender, e); // Reset timestamp BtnRefreshUnix_Click(sender, e); // Reset timestamp
@@ -94,6 +106,48 @@ namespace AiQ_GUI
await CheckHWOnline; await CheckHWOnline;
Flags.Start = false; Flags.Start = false;
CbBxCamType.Text = "AiQ"; // Default to AiQ cameras
}
private void InsertCamTab(string camType)
{
// Remove previous tabs
if (CurrentTabs != null)
{
foreach (TabPage tab in CurrentTabs)
TabImagesandSettings.TabPages.Remove(tab);
}
List<TabPage> tabsToInsert = new List<TabPage>();
if (camType == "Mobile")
tabsToInsert.AddRange([Video, Mobile]);
else if (camType == "AiQ")
tabsToInsert.AddRange([TabImages, TabSoak]);
int idx = Math.Min(3, TabImagesandSettings.TabPages.Count);
foreach (TabPage tab in tabsToInsert)
{
if (!TabImagesandSettings.TabPages.Contains(tab))
TabImagesandSettings.TabPages.Insert(idx++, tab);
}
CurrentTabs = tabsToInsert;
}
private async void CbBxCamType_SelectedIndexChanged(object sender, EventArgs e)
{
CbBxCameraModel.Items.Clear();
var camType = CbBxCamType.Text;
InsertCamTab(camType);
var models = await Task.Run(() => Access.ReadCamTypes(camType));
if (models != null && models.Length > 0)
CbBxCameraModel.Items.AddRange(models);
} }
private void PopulateUIWithLDS(LocalDataStore lds) private void PopulateUIWithLDS(LocalDataStore lds)
@@ -134,98 +188,15 @@ namespace AiQ_GUI
BtnPreTest.Enabled = BtnStartTest.Enabled = false; // Disable buttons to stop user rnning multiple tests at the same time. BtnPreTest.Enabled = BtnStartTest.Enabled = false; // Disable buttons to stop user rnning multiple tests at the same time.
Logging.LogMessage("Final Test Started"); Logging.LogMessage("Final Test Started");
if (LblTestTubePing.Text == "") // No test tube so test like an IQ if (CbBxCamType.Text == "AiQ")
{ {
string LEDreply = await FlexiAPI.APIHTTPLED(CamOnTest.IP, LEDPOWER.SAFE); // Set LED's to safe (0x0E) to help with eye safety and trim check. await AiQTests.AiQFinalTest();
if (!LEDreply.Contains("Power levels set successfully"))
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 (CbBxCamType.Text == "Mobile")
await TestFailed(BtnStartTest, "Camera not in test tube");
Task VisCheck = Helper.VisualCheck(BtnStartTest);
if (!await FlexiAPI.ZoomModules("1F40", CamOnTest.IP)) // Zoom to 8000 (1F40h) at the same time.
await TestFailed(BtnStartTest, "Could not zoom modules to 8000");
if (!await FlexiAPI.SetZoomLockOn(CamOnTest.IP))
Helper.RestartApp();
await Task.Delay(1000); // Without sleep it kept failing the factory reset as camera modules were not ready yet
await CameraModules.FactoryResetModules(CamOnTest.IP); // Reset both modules and double check
string VISCAReply = await FlexiAPI.APIHTTPVISCA(CamOnTest.IP, "8101043903FF", true); // Manual mode to be able to manipulate the SIG settings.
if (VISCAReply != "9041FF9051FF")
AddToActionsList("Couldn't set to manual mode",Level.ERROR);
await CameraModules.SetSIG(CbBxShutter, CbBxIris, CbBxGain, CamOnTest.IP); // Set SIG according to the drop downs in settings for a good picture ready for image check
await ImageProcessing.ImageCheck(PicBxOV, PicBxIRF2, PicBxIRF16, LblIRImageF2, LblIRImageF16, CamOnTest); // Populates the picture boxes and checks iris changes
await VisCheck; // Before changing UI elements wait for user to finish Visual check
TabImagesandSettings.SelectedIndex = 2; // Swaps to the images tab
this.Refresh(); // Show user things are happening by displaying images taken
// TODO - Force expire sighting.
Task Wait = Task.Delay(5000); // Wait for 5 seconds to allow the camera to zoom in, set settings and capture some plates before auto trim
// While waiting do the SSH tasks.
sshData = SSH.CollectSSHData(CamOnTest.IP); // SSH into camera to get Vaxtor packages, filesystem size and if tailscale is installed.
await SSH.CheckFSSize(CamOnTest.IP, LblFilesystemSize, sshData); // Check Filesystem size is between 100GB & 150GB
Helper.DCPowerCheck(LblDC); // If the camera is DC powered check it is within limits
if (CameraAccessInfo.HardwareExtras.Contains("4G")) // If it is a router camera then test the router.
{ {
LblRouter.Visible = true; await MobileTests.RunFinalTestAsync();
if (Router.CheckRouter(Router.GetRouterInfo()))
LblRouter.Text += "OK";
else
LblRouter.Text += "Error with router";
} }
await Wait; // Finished to 5s wait
await FlexiAPI.SetTrim(CamOnTest.IP, LblTestTubePing.Text); // Auto trims the cameras, some plates should have been captured in the meantime
if (!await FlexiAPI.ZoomModules("0000", CamOnTest.IP)) // Zoom to full wide
await TestFailed(BtnStartTest, "Could not zoom modules to full wide");
await Task.Delay(1000); // Wait to be sure cameras are zoomed out.
await CameraModules.FactoryResetModules(CamOnTest.IP); // Reset both modules and double check
if (LblTestTubePing.Text == "❌") // Set LED's to MID in prep for diagnostics API
{
string LEDreply = await FlexiAPI.APIHTTPLED(CamOnTest.IP, LEDPOWER.MID); // Set LED's to medium (0x30)
if (!LEDreply.Contains("Power levels set successfully"))
AddToActionsList($"LED level could not be set: {LEDreply}", 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
await CheckDiagsAPIPt1(); // Requests, deserialises and checks the diagnostics API is correct
await CheckDiagsAPIPt2(); // For only final test parts
// Check module has gone to default config
CameraModules.CheckCamModule(DiagsAPI.IRmodule, LblIRModule, CamOnTest); // IR
CameraModules.CheckCamModule(DiagsAPI.OVmodule, LblOVModule, CamOnTest); // OV
// Check voltage and current are OK.
LED.CheckLEDs(DiagsAPI.LedVoltage, LblLEDV, "V", CameraAccessInfo.LED_V); // Voltage
LED.CheckLEDs(DiagsAPI.LedCurrent, LblLEDI, "mA", CameraAccessInfo.LED_I); // Current
this.Refresh(); // Make sure all labels are updated before checking them
// If there are any actions identified then fail the test.
// If any labels are red then fail. Only labels in panel so can foreach on labels not controls
if (RhTxBxActions.Text.Length > 2 || PnlLbls.Controls.OfType<Label>().Any(c => c.ForeColor == Color.Red) == true)
await TestFailed(BtnStartTest, "Diagnostic Failure");// If approved then pass otherwise GUI would have restarted before getting to TestPassed.
await TestPassed(PCTime);
} }
private async void BtnPreTest_Click(object sender, EventArgs e) private async void BtnPreTest_Click(object sender, EventArgs e)
@@ -233,68 +204,24 @@ namespace AiQ_GUI
// Show user test has started // Show user test has started
BtnPreTest.BackColor = Color.Orange; BtnPreTest.BackColor = Color.Orange;
BtnPreTest.Text = "Pre-Test underway"; BtnPreTest.Text = "Pre-Test underway";
BtnPreTest.Enabled = BtnStartTest.Enabled = false; // Disable buttons to stop user rnning multiple tests at the same time. BtnPreTest.Enabled = BtnStartTest.Enabled = false; // Disable buttons to stop user running multiple tests at the same time.
Logging.LogMessage("Pre Test Started"); Logging.LogMessage("Pre Test Started");
if (CbBxCamType.Text == "AiQ")
if (!await FlexiAPI.SetZoomLockOn(CamOnTest.IP))
Helper.RestartApp();
string LEDreply = await FlexiAPI.APIHTTPLED(CamOnTest.IP, LEDPOWER.MID); // Set LED's to medium (0x30)
if (!LEDreply.Contains("Power levels set successfully"))
AddToActionsList($"LED level could not be set: {LEDreply}", Level.ERROR);
await CameraModules.FactoryResetModules(CamOnTest.IP); // Reset both modules and double check
sshData = SSH.CollectSSHData(CamOnTest.IP); // SSH into camera to get Vaxtor packages, filesystem size and if tailscale is installed.
await SSH.CheckFSSize(CamOnTest.IP, LblFilesystemSize, sshData); // Check Filesystem size is between 100GB & 150GB
Helper.DCPowerCheck(LblDC); // If the camera is DC powered check it is within limits
// Requests, deserialises and checks the diagnostics API is correct
await CheckDiagsAPIPt1();
// Check module has gone to default config
CameraModules.CheckCamModule(DiagsAPI.IRmodule, LblIRModule, CamOnTest); // IR
CameraModules.CheckCamModule(DiagsAPI.OVmodule, LblOVModule, CamOnTest); // OV
// Check voltage and current are OK.
LED.CheckLEDs(DiagsAPI.LedVoltage, LblLEDV, "V", CameraAccessInfo.LED_V); // Voltage
LED.CheckLEDs(DiagsAPI.LedCurrent, LblLEDI, "mA", CameraAccessInfo.LED_I); // Current
this.Refresh(); // Make sure all labels are updated before checking them
// If there are any actions identified then fail the test.
// If any labels are red then fail. Only labels in panel so can foreach on labels not controls
if (RhTxBxActions.Text.Length < 2 && PnlLbls.Controls.OfType<Label>().Any(c => c.ForeColor == Color.Red) == false)
{ {
// If camera already has a model or serial then ask if it is new await AiQTests.AiQPreTest();
if (RegexCache.SerialRegex().IsMatch(DiagsAPI.serialNumber) || RegexCache.ModelRegex().IsMatch(DiagsAPI.modelNumber))
{
if (await DisplayQuestion($"Would you like to allocate a serial number to this camera?"))
await AllocateSerial();
else if (GoogleAPI.UpdateSpreadSheetRePreTest(CameraAccessInfo.SpreadsheetID, Vers) != "OK") // If rerun might be different values so update SS
AddToActionsList("Failed to write to spreadsheet, please check manually",Level.WARNING);
// else if (Excel.UpdateSpreadSheetPreTest(CameraAccessInfo.SpreadsheetID, Vers, CamOnTest.GetCamDesc(), CamOnTest.Model) != "OK")
// AddToActionsList("Failed to write to spreadsheet, please check manually");
} }
else // No serial or model so allocate one else if (CbBxCamType.Text == "Mobile")
await AllocateSerial(); {
await MobileTests.RunPreTestAsync();
if (RhTxBxActions.Text.Length < 2 && PnlLbls.Controls.OfType<Label>().Any(c => c.ForeColor == Color.Red) == false)
await PreTestPassed(); await PreTestPassed();
} }
else
{
await PreTestFailed("Diagnostic Failure");
}
} }
// ***** Pass/Fails ***** // ***** Pass/Fails *****
private async Task TestPassed(DateTime PCTime) public async Task TestPassed(DateTime PCTime)
{ {
// Updates Vaxtor versions, licenses and unticks WIP // Updates Vaxtor versions, licenses and unticks WIP
string err = GoogleAPI.UpdateSpreadSheetFinalTest(CameraAccessInfo.SpreadsheetID, DiagsAPI, sshData, CamOnTest.RMANum); string err = GoogleAPI.UpdateSpreadSheetFinalTest(CameraAccessInfo.SpreadsheetID, TestingFunctions.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, Level.ERROR); AddToActionsList("Failed to write to spreadsheet " + err, Level.ERROR);
@@ -423,7 +350,7 @@ namespace AiQ_GUI
Helper.RestartApp(); Helper.RestartApp();
} }
private async Task PreTestPassed() public async Task PreTestPassed()
{ {
BtnPreTest.BackColor = Color.ForestGreen; // Indicators to the user the test has passed BtnPreTest.BackColor = Color.ForestGreen; // Indicators to the user the test has passed
BtnPreTest.Text = "Pre Test Passed"; BtnPreTest.Text = "Pre Test Passed";
@@ -440,7 +367,7 @@ namespace AiQ_GUI
Helper.RestartApp(); Helper.RestartApp();
} }
private async Task PreTestFailed(string ErrMssg) public async Task PreTestFailed(string ErrMssg)
{ {
BtnPreTest.BackColor = Color.Maroon; // Indicators to the user the test has failed BtnPreTest.BackColor = Color.Maroon; // Indicators to the user the test has failed
BtnPreTest.Text = "Test Failed"; BtnPreTest.Text = "Test Failed";
@@ -465,232 +392,11 @@ namespace AiQ_GUI
Helper.RestartApp(); Helper.RestartApp();
} }
// ***** Testing functions ***** public async Task AllocateSerial()
private async Task CheckDiagsAPIPt1() // Parts done on pre and final test
{
DiagsAPI = await FlexiAPI.GetDiagnostics(CamOnTest.IP); // Diagnostic API request
lblFlexiVer.Text += DiagsAPI.FlexiVersion; // Check Flexi Version
if (DiagsAPI.FlexiVersion == UniversalData.ExpFlexiVer)
{
lblFlexiVer.ForeColor = Color.LightGreen;
}
else
{
lblFlexiVer.Text += " Expected = " + UniversalData.ExpFlexiVer;
lblFlexiVer.ForeColor = Color.Red;
}
lblFlexiRev.Text += DiagsAPI.FlexiRevision; // Check Flexi Revision
if (DiagsAPI.FlexiRevision == UniversalData.ExpFlexiRev)
{
lblFlexiRev.ForeColor = Color.LightGreen;
}
else
{
lblFlexiRev.Text += " Expected = " + UniversalData.ExpFlexiRev;
lblFlexiRev.ForeColor = Color.Red;
}
lblMac.Text += DiagsAPI.MAC; // Display MAC
if (RegexCache.MACRegexNVIDIA().IsMatch(DiagsAPI.MAC)) // Checks it is in the right format and is a NVIDIA registered MAC address
{
lblMac.ForeColor = Color.LightGreen;
}
else if (RegexCache.MACRegex().IsMatch(DiagsAPI.MAC)) // Is a valid MAC, but not NVIDIA
{
lblMac.ForeColor = Color.Red;
AddToActionsList($"{DiagsAPI.MAC} not recognised as NVIDIA MAC address", Level.ERROR);
}
else
{
lblMac.ForeColor = Color.Red;
AddToActionsList($"{DiagsAPI.MAC} not recognised as a MAC address", Level.ERROR);
}
// Check timestamp
DateTime dateTime = new(1970, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc);
dateTime = dateTime.AddSeconds(DiagsAPI.timeStamp).ToLocalTime();
lbltimestamp.Text += dateTime;
long timediff = DateTimeOffset.UtcNow.ToUnixTimeSeconds() - DiagsAPI.timeStamp;
if (timediff > 10) // Over 10 seconds ago
{
lbltimestamp.Text += $" Time behind by {timediff}s";
lbltimestamp.ForeColor = Color.Red;
}
else if (timediff < 0) // Time is in the future.
{
lbltimestamp.Text += $" Time is in the future by {Math.Abs(timediff)}s";
lbltimestamp.ForeColor = Color.Red;
}
else
lbltimestamp.ForeColor = Color.LightGreen;
lblTemp.Text += DiagsAPI.IntTemperature + "°C"; // Diagnostic API request
if (DiagsAPI.IntTemperature > 20 && DiagsAPI.IntTemperature < 70)
lblTemp.ForeColor = Color.LightGreen;
else
lblTemp.ForeColor = Color.Red;
lblZoomLock.Text += DiagsAPI.zoomLock;
if (DiagsAPI.zoomLock == true)
{
if (DiagsAPI.IRmodule.zoom == DiagsAPI.OVmodule.zoom) // Checks if zoomlock is doing what is says it should
{
lblZoomLock.ForeColor = Color.LightGreen;
}
else
{
lblZoomLock.Text += $" Zoomlock on but {DiagsAPI.IRmodule.zoom}≠{DiagsAPI.OVmodule.zoom}";
lblZoomLock.ForeColor = Color.Red;
}
}
else
lblZoomLock.ForeColor = Color.Red;
}
private async Task CheckDiagsAPIPt2() // Parts only done on final test
{
if (RhTxBxActions.Text.Contains("Error reading JSON")) // If failed to deserialise in part 1
return;
try // In case serial or model are blank
{
lblModel.Text += DiagsAPI.modelNumber; // Update labels with serial and model
lblSerial.Text += DiagsAPI.serialNumber;
if (RegexCache.SerialRegex().IsMatch(DiagsAPI.serialNumber) && RegexCache.ModelRegex().IsMatch(DiagsAPI.modelNumber))
{
lblSerial.ForeColor = lblModel.ForeColor = Color.LightGreen; // Set both to green
if (DiagsAPI.modelNumber != CamOnTest.Model)
{
AddToActionsList("Model number in camera doesn't match what has been selected", Level.WARNING);
lblModel.ForeColor = Color.Red;
}
else if (!GoogleAPI.CheckWIP(DiagsAPI.serialNumber, CameraAccessInfo.SpreadsheetID)) // Check WIP column in serial number register, if not ticked then RMA
{
CamOnTest.RMANum = GoogleAPI.CheckRMANum(DiagsAPI.serialNumber, DiagsAPI.modelNumber); // Corrected by qualifying with the type name
if (CamOnTest.RMANum == 0) // Couldn't find RMA num in spreadsheet
{
CamOnTest.RMANum = Convert.ToInt32(await DisplayInput("What is the RMA number?"));
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");
}
// Found RMA num and want to verify it with user
else if (!await DisplayQuestion($"Is {CamOnTest.RMANum} the RMA Number?")) // '!' because if its not the right RMA number let the user to it manually
{
CamOnTest.RMANum = Convert.ToInt32(await DisplayInput("What is the RMA number?"));
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");
}
}
}
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.", Level.ERROR);
}
}
catch { }
// Check licenses
List<string> licensesOnCam = []; // Temporary list for licenses on camera
if (DiagsAPI.licenses.saf1)
licensesOnCam.Add("SAF1");
if (DiagsAPI.licenses.saf2)
licensesOnCam.Add("SAF2");
if (DiagsAPI.licenses.saf3)
licensesOnCam.Add("SAF3");
if (DiagsAPI.licenses.saf4)
licensesOnCam.Add("SAF4");
if (DiagsAPI.licenses.audit)
licensesOnCam.Add("Audit");
if (DiagsAPI.licenses.stream)
licensesOnCam.Add("Stream");
if (sshData.tailscale)
licensesOnCam.Add("Tailscale");
if (licensesOnCam.Count == 0) // No licenses found
lblLic.Text += "None";
else if (licensesOnCam.Count != 0) // Join them comma and space seperated for displaying
lblLic.Text += string.Join(", ", licensesOnCam);
lblLic.ForeColor = Color.LightGreen;
double CPUround = Math.Round(DiagsAPI.CPUusage); // Check CPU usage isn't near max
LblCPUusage.Text += CPUround + "%";
if (CPUround <= 50)
{
LblCPUusage.Text += " Unexpectedly low CPU usage";
LblCPUusage.ForeColor = Color.Red;
}
else if (CPUround >= 98)
{
LblCPUusage.Text += " Unexpectedly high CPU usage";
LblCPUusage.ForeColor = Color.Red;
}
else
{
LblCPUusage.ForeColor = Color.LightGreen;
}
// Check Vaxtor if it doesn't need or have license OR has and wants one then pass
if (CameraAccessInfo.VaxtorLic == false && DiagsAPI.licenses.raptorKeyID == "Not Licensed" || CameraAccessInfo.VaxtorLic == true && DiagsAPI.licenses.raptorKeyID != "Not Licensed")
{
lblVaxtor.Text += DiagsAPI.licenses.raptorKeyID;
lblVaxtor.ForeColor = Color.LightGreen;
}
else if (await DisplayQuestion("Was this camera licensed manually?"))
{
string ProdcutKeyID = await DisplayInput("What is the Key ID?", false);
if (RegexCache.VaxtorRegex().IsMatch(ProdcutKeyID)) // Means they chose the 'I don't know' option or isn't valid Key ID
{
Access.Stats("Please Get A Valid Vaxtor Product Key Before Continuing", CamOnTest.Model);
await TestFailed(BtnStartTest, "Please get a valid Vaxtor Product Key before continuing");
}
DiagsAPI.licenses.raptorKeyID = TxBxProductKey.Text;
lblVaxtor.Text += DiagsAPI.licenses.raptorKeyID;
lblVaxtor.ForeColor = Color.LightGreen;
}
else // Should have license but doesn't OR shouldn't have but does + any other unforseen circumstance then fail
{
lblVaxtor.Text += DiagsAPI.licenses.raptorKeyID;
lblVaxtor.ForeColor = Color.Red;
}
// Check trim is within 10% both horizontally and vertically, from auto set done earlier in the test
lblTrim.Text += "H: " + DiagsAPI.trim[0] + " V: " + DiagsAPI.trim[1];
// Offset accounted for in the SetTrim function, so value should be close to 0,0.
const int HMax = 96; // 5% of 1920 each way = ±96
const int VMax = 54; // 5% of 1080 each way = ±54
if (Math.Abs(DiagsAPI.trim[0]) <= HMax && Math.Abs(DiagsAPI.trim[1]) <= VMax)
lblTrim.ForeColor = Color.LightGreen;
else
lblTrim.ForeColor = Color.Red;
}
private async Task AllocateSerial()
{ {
// Update the serial number register with new cameras details // Update the serial number register with new cameras details
// Cam description is in model drop down 6 digit model num + 3 for " - " // Cam description is in model drop down 6 digit model num + 3 for " - "
string NewSerial = GoogleAPI.UpdateSpreadSheetPreTest(CameraAccessInfo.SpreadsheetID, Vers, CbBxCameraType.Text.Substring(9), CamOnTest.Model); string NewSerial = GoogleAPI.UpdateSpreadSheetPreTest(CameraAccessInfo.SpreadsheetID, Vers, CbBxCameraModel.Text.Substring(9), CamOnTest.Model);
if (NewSerial.Contains("ERROR")) if (NewSerial.Contains("ERROR"))
{ {
@@ -713,11 +419,11 @@ namespace AiQ_GUI
await PreTestFailed("Failed To Set Model Or Serial Number"); await PreTestFailed("Failed To Set Model Or Serial Number");
} }
DiagsAPI.modelNumber = CamOnTest.Model; // Update Diags and labels TestingFunctions.DiagsAPI.modelNumber = CamOnTest.Model; // Update Diags and labels
DiagsAPI.serialNumber = NewSerial; TestingFunctions.DiagsAPI.serialNumber = NewSerial;
lblModel.Text += DiagsAPI.modelNumber; lblModel.Text += TestingFunctions.DiagsAPI.modelNumber;
lblModel.ForeColor = lblSerial.ForeColor = Color.LightGreen; lblModel.ForeColor = lblSerial.ForeColor = Color.LightGreen;
lblSerial.Text += DiagsAPI.serialNumber; lblSerial.Text += TestingFunctions.DiagsAPI.serialNumber;
Printer.ZebraIP = localDataStore.ZebraIP; Printer.ZebraIP = localDataStore.ZebraIP;
Printer.PrintGBLbl(); // Print GB label Printer.PrintGBLbl(); // Print GB label
@@ -728,10 +434,10 @@ namespace AiQ_GUI
private void BtnClose_Click(object sender, EventArgs e) private void BtnClose_Click(object sender, EventArgs e)
{ {
// Save user settings in LDS for next time if values are valid // Save user settings in LDS for next time if values are valid
if (CbBxUserName.Text.Length > 2 && CbBxCameraType.Text.Length > 6) if (CbBxUserName.Text.Length > 2 && CbBxCameraModel.Text.Length > 6)
{ {
localDataStore.User = CbBxUserName.Text; localDataStore.User = CbBxUserName.Text;
localDataStore.LastModel = CbBxCameraType.Text; localDataStore.LastModel = CbBxCameraModel.Text;
LDS.SetLDS(localDataStore); LDS.SetLDS(localDataStore);
} }
@@ -817,12 +523,6 @@ namespace AiQ_GUI
Flags.No = true; Flags.No = true;
} }
private void CmBxCameraType_SelectedIndexChanged(object sender, EventArgs e)
{
CamOnTest.Model = CbBxCameraType.Text.Substring(0, 6); // Model on test is the first 6 characters of the textbox
Access.ReadModelRow(CamOnTest.Model); // Fill in all info about current model number
TestStartConditions();
}
private void CmBoBoxUserName_TextChanged(object sender, EventArgs e) private void CmBoBoxUserName_TextChanged(object sender, EventArgs e)
{ {
@@ -840,53 +540,74 @@ namespace AiQ_GUI
timerTypeIP.Enabled = false; // Stop this triggering again timerTypeIP.Enabled = false; // Stop this triggering again
CamOnTest.DevPass = TxBxOutput.Text = RhTxBxActions.Text = ""; // Blank password & Clear actions box as it is now a different camera CamOnTest.DevPass = TxBxOutput.Text = RhTxBxActions.Text = ""; // Blank password & Clear actions box as it is now a different camera
if (RegexCache.RegexIPPattern().IsMatch(CbBxFoundCams.Text)) // Check IP address is valid string selectedText = CbBxFoundCams.Text.Trim();
string ipOnly = selectedText.Split(' ', StringSplitOptions.RemoveEmptyEntries)[0];
bool isOnvif = selectedText.Contains("Onvif", StringComparison.OrdinalIgnoreCase);
if (RegexCache.RegexIPPattern().IsMatch(ipOnly)) // Check IP address is valid
{ {
if (!await Network.PingIP(CbBxFoundCams.Text)) CamOnTest.IP = ipOnly; // Always store clean IP
CbBxFoundCams.BackColor = BtnColour;
// ONVIF cameras: webpage only
if (isOnvif)
{
BtnOpenWebpage.Enabled = true;
TestStartConditions();
return;
}
// AiQ cameras
if (!await Network.PingIP(ipOnly))
{ {
CbBxFoundCams.BackColor = Color.Red; CbBxFoundCams.BackColor = Color.Red;
return; return;
} }
CamOnTest.IP = CbBxFoundCams.Text; // Set the IP address under test
CbBxFoundCams.BackColor = BtnColour;
BtnSecret.Enabled = true; BtnSecret.Enabled = true;
Vers = await FlexiAPI.GetVersions(CamOnTest.IP); Vers = await FlexiAPI.GetVersions(ipOnly);
// Wont be filled out before the pre test but needed for final test // Wont be filled out before the pre test but needed for final test
if (RegexCache.SerialRegex().IsMatch(CamOnTest.Serial) && RegexCache.ModelRegex().IsMatch(CamOnTest.Model)) if (RegexCache.SerialRegex().IsMatch(CamOnTest.Serial) &&
RegexCache.ModelRegex().IsMatch(CamOnTest.Model))
{ {
CamOnTest.Serial = Vers.Serial; // Set the serial number from the versions API CamOnTest.Serial = Vers.Serial;
CamOnTest.Model = Vers.Model; // Set the serial number from the versions API CamOnTest.Model = Vers.Model;
} }
if (Vers == null) // If failed to get versions then return. Flexi most likely not running yet. if (Vers == null) // Flexi not running or not AiQ
{ {
AddToActionsList("Failed to get API from camera. Flexi not running yet or not an AiQ", Level.WARNING); AddToActionsList("Failed to get API from camera. Flexi not running yet or not an AiQ", Level.WARNING);
return; return;
} }
Lics.DisplayDevPassword(Vers, CamOnTest); // Generate and display secret for use later Lics.DisplayDevPassword(Vers, CamOnTest);
string networkConfigText = await FlexiAPI.ProcessNetworkConfig(CamOnTest.IP); string networkConfigText = await FlexiAPI.ProcessNetworkConfig(ipOnly);
BtnSet211.Text = string.IsNullOrEmpty(networkConfigText) ? "Set to 211" : networkConfigText; BtnSet211.Text = string.IsNullOrEmpty(networkConfigText) ? "Set to 211" : networkConfigText;
ShowToolTip(BtnSecret); // Set dev password to Tooltip and clipboard ShowToolTip(BtnSecret);
} }
else if (CbBxFoundCams.Text.Contains("Found")) else if (selectedText.Contains("Found"))
{ {
CbBxFoundCams.BackColor = BtnColour; CbBxFoundCams.BackColor = BtnColour;
} }
else else
{ {
CbBxFoundCams.BackColor = Color.Red; CbBxFoundCams.BackColor = Color.Red;
BtnSecret.Enabled = BtnOpenWebpage.Enabled = BtnSet211.Enabled = BtnSetGodMode.Enabled = BtnUploadWonwooSetIR.Enabled = BtnUploadWonwooSetOV.Enabled = false; BtnSecret.Enabled =
BtnOpenWebpage.Enabled =
BtnSet211.Enabled =
BtnSetGodMode.Enabled =
BtnUploadWonwooSetIR.Enabled =
BtnUploadWonwooSetOV.Enabled = false;
} }
TestStartConditions(); TestStartConditions();
} }
private void btnPsuOn_Click(object sender, EventArgs e) private void btnPsuOn_Click(object sender, EventArgs e)
{ {
PSU.PSU_ON(PSU.PSUIP); PSU.PSU_ON(PSU.PSUIP);
@@ -933,33 +654,45 @@ namespace AiQ_GUI
// ***** Helper functions ***** // ***** Helper functions *****
public void AddToActionsList(string Mssg, Level Lvl = Level.LOG) public void AddToActionsList(string Mssg, Level Lvl = Level.LOG)
{ {
if (Lvl == Level.ERROR) // DEBUG messages only visible to Bradley
{ if (Lvl == Level.DEBUG && CbBxUserName.Text != "Bradley")
Logging.LogErrorMessage(Mssg); return;
RhTxBxActions.SelectionColor = Color.IndianRed;
}
else if (Lvl == Level.WARNING)
{
Logging.LogWarningMessage(Mssg);
RhTxBxActions.SelectionColor = Color.Orange;
}
else if (Lvl == Level.LOG)
{
Logging.LogMessage(Mssg);
RhTxBxActions.SelectionColor = Color.White;
}
else if (Lvl == Level.Success)
{
Logging.LogMessage(Mssg);
RhTxBxActions.SelectionColor = Color.LightGreen;
}
if (Lvl == Level.ERROR)
Logging.LogErrorMessage(Mssg);
else if (Lvl == Level.WARNING)
Logging.LogWarningMessage(Mssg);
else if (Lvl == Level.DEBUG)
Logging.LogMessage("[DEBUG]" + Mssg);
else
Logging.LogMessage(Mssg);
RhTxBxActions.SelectionColor = Lvl switch
{
Level.ERROR => Color.IndianRed,
Level.WARNING => Color.Orange,
Level.DEBUG => Color.LightBlue,
Level.Success => Color.LightGreen,
_ => Color.White
};
if (Lvl == Level.DEBUG)
RhTxBxActions.AppendText("[DEBUG] " + Mssg + Environment.NewLine);
else
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.SelectionColor = SystemColors.Control;
RhTxBxActions.ScrollToCaret(); RhTxBxActions.ScrollToCaret();
} }
private void CbBxCameraModel_SelectedIndexChanged(object sender, EventArgs e)
{
TestStartConditions();
}
private async void TestStartConditions() private async void TestStartConditions()
{ {
if (Flags.Start) if (Flags.Start)
@@ -973,7 +706,7 @@ namespace AiQ_GUI
if (await Network.PingIP("8.8.8.8")) // Ping to find if we are online if (await Network.PingIP("8.8.8.8")) // Ping to find if we are online
{ {
Flags.Offline = false; // Ping succeeded Flags.Offline = false; // Ping succeeded
CbBxCameraType.Enabled = true; CbBxCameraModel.Enabled = true;
} }
else else
TSC = SetInvalid("Offline Mode, could not connect to the internet."); // Ping failed TSC = SetInvalid("Offline Mode, could not connect to the internet."); // Ping failed
@@ -990,7 +723,7 @@ namespace AiQ_GUI
TSC = SetInvalid("Select Username."); TSC = SetInvalid("Select Username.");
// Model number selected // Model number selected
if (CbBxCameraType.SelectedIndex == -1) if (CbBxCameraModel.SelectedIndex == -1)
TSC = SetInvalid("Select Model number."); TSC = SetInvalid("Select Model number.");
// Settings IP addresses filled in // Settings IP addresses filled in
@@ -1052,7 +785,7 @@ namespace AiQ_GUI
} }
// Display the input panel for either RMA number input (default) or Vaxtor Key ID input // Display the input panel for either RMA number input (default) or Vaxtor Key ID input
private async Task<string> DisplayInput(string Request, bool RMAorVaxtor = true) public async Task<string> DisplayInput(string Request, bool RMAorVaxtor = true)
{ {
RMANumBox.Visible = BtnRerun.Visible = RMAorVaxtor; RMANumBox.Visible = BtnRerun.Visible = RMAorVaxtor;
TxBxProductKey.Visible = !RMAorVaxtor; TxBxProductKey.Visible = !RMAorVaxtor;
@@ -1471,7 +1204,7 @@ namespace AiQ_GUI
private async void BtnZoomWide_Click(object sender, EventArgs e) private async void BtnZoomWide_Click(object sender, EventArgs e)
{ {
if (await FlexiAPI.ZoomModules("0000", CamOnTest.IP)) if (await CameraModules.ZoomModules("0000", CamOnTest.IP))
BtnZoomWide.BackColor = Color.Green; BtnZoomWide.BackColor = Color.Green;
else else
BtnZoomWide.BackColor = Color.Red; BtnZoomWide.BackColor = Color.Red;
@@ -1481,7 +1214,7 @@ namespace AiQ_GUI
private async void BtnZoom8000_Click(object sender, EventArgs e) private async void BtnZoom8000_Click(object sender, EventArgs e)
{ {
if (await FlexiAPI.ZoomModules("1F40", CamOnTest.IP)) if (await CameraModules.ZoomModules("1F40", CamOnTest.IP))
BtnZoom8000.BackColor = Color.Green; BtnZoom8000.BackColor = Color.Green;
else else
BtnZoom8000.BackColor = Color.Red; BtnZoom8000.BackColor = Color.Red;
@@ -1491,15 +1224,19 @@ namespace AiQ_GUI
private void BtnOpenWebpage_Click(object sender, EventArgs e) private void BtnOpenWebpage_Click(object sender, EventArgs e)
{ {
// Cut off anything after the first space (e.g. " - Onvif")
string ip = CamOnTest.IP.Split(' ')[0];
ProcessStartInfo psi = new() ProcessStartInfo psi = new()
{ {
FileName = $"http://{CamOnTest.IP}", // Just the URL FileName = $"http://{ip}",
UseShellExecute = true // Lets the OS decide how to open it UseShellExecute = true
}; };
Process.Start(psi); Process.Start(psi);
} }
private async void UploadWonwooSetOV_Click(object sender, EventArgs e) private async void UploadWonwooSetOV_Click(object sender, EventArgs e)
{ {
await FlexiAPI.UploadWonwooSet(CbBxFoundCams.Text, false); // false = Colour await FlexiAPI.UploadWonwooSet(CbBxFoundCams.Text, false); // false = Colour
@@ -1540,7 +1277,7 @@ namespace AiQ_GUI
{ {
BtnPrintGB.Enabled = true; BtnPrintGB.Enabled = true;
if (CbBxCameraType.SelectedIndex != -1 && RegexCache.SerialRegex().IsMatch(TxBxSerialPrint.Text)) // Check model and serial are known if (CbBxCameraModel.SelectedIndex != -1 && RegexCache.SerialRegex().IsMatch(TxBxSerialPrint.Text)) // Check model and serial are known
{ {
Printer.ZebraIP = localDataStore.ZebraIP; Printer.ZebraIP = localDataStore.ZebraIP;
BtnPrintAiQ.Enabled = true; BtnPrintAiQ.Enabled = true;
@@ -1585,7 +1322,7 @@ namespace AiQ_GUI
CancellationTokenSource cts = new(); CancellationTokenSource cts = new();
soakCtsList.Add(cts); soakCtsList.Add(cts);
soakTasks.Add(SoakTest.StartSoak(SCL, SCL.CheckBox, cts.Token)); soakTasks.Add(SoakTest.StartSoak(SCL, cts));
await Task.Delay(10000); await Task.Delay(10000);
} }
} }
@@ -1707,39 +1444,66 @@ namespace AiQ_GUI
} }
BtnFactoryDefault.BackColor = Color.Green; BtnFactoryDefault.BackColor = Color.Green;
} }
public static Label MakeNewLabel(string text, bool isRed, int yLoc)
{
return new Label
{
Location = new System.Drawing.Point(5, yLoc),
Height = 20,
Width = 700,
ForeColor = isRed ? Color.Red : Color.LightGreen,
Text = text,
Name = "Lbl_" + Guid.NewGuid(),
AutoSize = false
};
}
public void AddLabelToPanel(string text, bool isRed)
{
int yLoc = PnlLbls.Controls
.OfType<Label>()
.Count() * 22; // 20 height + 2px spacing
Label lbl = MakeNewLabel(text, isRed, yLoc);
PnlLbls.Controls.Add(lbl);
}
private void BtnPlayVLC_Click(object sender, EventArgs e)
{
if (string.IsNullOrEmpty(CamOnTest.IP))
{
AddToActionsList("No Camera Has Been Selected, Please Select A Camera.", Level.WARNING);
}
else if (VLC.IsPLaying(VidView))
{
BtnPlayVLC.Text = "Play Video";
VLC.Stop(VidView);
}
else
{
BtnPlayVLC.Text = "Stop Video";
VLC.Play(VidView);
}
}
// ***** Test & Debug ***** // ***** Test & Debug *****
private void BtnTest_Click(object sender, EventArgs e) private async 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();
//await MobileTests.CheckFirmwareAsync();
//FlexiAPI.GetVersions(CamOnTest.IP).Wait();
//VLC.Play(VidView);
//await Task.Delay(5000);
//VLC.TakeSnapshot(VidView);
SSH.MobiletxtCheck("100.118.196.113");
// /api/config-ids - For getting all available config IDs // await MobileAPI.SetDayModeAsync();
// Make every log file in the soak log directory into a soak test report PDF AddToActionsList("RunTime " + stopWatchTest.Elapsed.ToString(@"hh\:mm\:ss\.ff"), Level.DEBUG);
//var files = from file in Directory.EnumerateFiles("C:\\ProgramData\\MAV\\AiQ_GUI") select file; }
//foreach (var file in files)
//{
// if (file.Contains("SoakLog"))
// {
// // File name: SoakLog_{Serial}_{Model}.log
// string[] parts = file.Split('_', '.').Select(p => p.Trim()).ToArray();
// Camera NewCam = new()
// {
// Model = parts[3],
// Serial = parts[2],
// };
// PDF.CreateSoakTestReport(NewCam, "SoakTestRig", DateTime.Now, file);
// }
//}
stopWatchTest.Stop();
AddToActionsList("RunTime " + stopWatchTest.Elapsed.ToString(@"hh\:mm\:ss\.ff"), Level.LOG);
}
} }
} }

View File

@@ -2,7 +2,7 @@
<PropertyGroup> <PropertyGroup>
<OutputType>WinExe</OutputType> <OutputType>WinExe</OutputType>
<TargetFramework>net9.0-windows10.0.17763.0</TargetFramework> <TargetFramework>net10.0-windows7.0</TargetFramework>
<Nullable>enable</Nullable> <Nullable>enable</Nullable>
<UseWindowsForms>true</UseWindowsForms> <UseWindowsForms>true</UseWindowsForms>
<ImplicitUsings>enable</ImplicitUsings> <ImplicitUsings>enable</ImplicitUsings>
@@ -16,7 +16,7 @@
<Product>AiQ GUI</Product> <Product>AiQ GUI</Product>
<Authors>MAV Systems Ltd</Authors> <Authors>MAV Systems Ltd</Authors>
<PackageId>AiQ GUI</PackageId> <PackageId>AiQ GUI</PackageId>
<Version>4.5.0</Version> <Version>4.6.0</Version>
<Description>A GUI to control and test the AiQ</Description> <Description>A GUI to control and test the AiQ</Description>
<Copyright>MAV Systems Ltd 2025</Copyright> <Copyright>MAV Systems Ltd 2025</Copyright>
<PackageIcon>MAV - Plain - Blue.png</PackageIcon> <PackageIcon>MAV - Plain - Blue.png</PackageIcon>
@@ -55,16 +55,22 @@
<PackageReference Include="ClosedXML" Version="0.105.0" /> <PackageReference Include="ClosedXML" Version="0.105.0" />
<PackageReference Include="Emgu.CV" Version="4.12.0.5764" /> <PackageReference Include="Emgu.CV" Version="4.12.0.5764" />
<PackageReference Include="Emgu.CV.runtime.windows" Version="4.12.0.5764" /> <PackageReference Include="Emgu.CV.runtime.windows" Version="4.12.0.5764" />
<PackageReference Include="Google.Apis.Auth" Version="1.72.0" /> <PackageReference Include="FlaUI.Core" Version="5.0.0" />
<PackageReference Include="Google.Apis.Gmail.v1" Version="1.70.0.3833" /> <PackageReference Include="FlaUI.UIA3" Version="5.0.0" />
<PackageReference Include="Google.Apis.Sheets.v4" Version="1.70.0.3819" /> <PackageReference Include="Google.Apis.Auth" Version="1.73.0" />
<PackageReference Include="Google.Apis.Sheets.v4" Version="1.72.0.3966" />
<PackageReference Include="LibVLCSharp" Version="3.9.5" />
<PackageReference Include="LibVLCSharp.WinForms" Version="3.9.5" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.4" /> <PackageReference Include="Newtonsoft.Json" Version="13.0.4" />
<PackageReference Include="PDFsharp-MigraDoc-gdi" Version="6.2.2" /> <PackageReference Include="Newtonsoft.Json.Bson" Version="1.0.3" />
<PackageReference Include="OnvifDiscovery" Version="2.1.1" />
<PackageReference Include="PDFsharp-MigraDoc-gdi" Version="6.2.3" />
<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="142.0.7444.6100" /> <PackageReference Include="Selenium.WebDriver.ChromeDriver" Version="142.0.7444.17500" />
<PackageReference Include="SSH.NET" Version="2025.1.0" /> <PackageReference Include="SSH.NET" Version="2025.1.0" />
<PackageReference Include="System.Data.OleDb" Version="10.0.0" /> <PackageReference Include="System.Data.OleDb" Version="10.0.0" />
<PackageReference Include="VideoLAN.LibVLC.Windows" Version="3.0.21" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>

View File

@@ -6,9 +6,9 @@ namespace AiQ_GUI
// 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, Camera CamOnTest) public static void CheckCamModule(Module CamMod, Label Lbl, Camera CamOnTest)
{ {
if (CamMod == null || Lbl == null) if (CamMod == null)
{ {
MainForm.Instance.AddToActionsList("Camera module or label was null in CheckCamModule.", Level.ERROR); MainForm.Instance.AddToActionsList("Camera module was null in CheckCamModule.", Level.ERROR);
return; return;
} }
@@ -28,29 +28,48 @@ namespace AiQ_GUI
MainForm.Instance.AddToActionsList($"{CamMod.firmwareVer} or {UniversalData.WonwooFirmware} could not be converted to a double", Level.ERROR); MainForm.Instance.AddToActionsList($"{CamMod.firmwareVer} or {UniversalData.WonwooFirmware} could not be converted to a double", Level.ERROR);
} }
if (CamOnTest.RMANum > 0 && LessTanOrEqualTo) if (CamOnTest.RMANum > 0 && !LessTanOrEqualTo)
errMssg += $"Firmware: {CamMod.firmwareVer} should be less than or equal to {UniversalData.WonwooFirmware} for RMA {CamOnTest.RMANum}"; 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) else if (CamOnTest.RMANum < 1 && CamMod.firmwareVer != UniversalData.WonwooFirmware)
errMssg += $"Firmware: {CamMod.firmwareVer} should be {UniversalData.WonwooFirmware} "; errMssg += $"Firmware: {CamMod.firmwareVer} should be {UniversalData.WonwooFirmware} ";
if (CamMod.expMode != 0) // Auto 0=0x00 if (CamMod.expMode != 0) // Auto 0=0x00
errMssg += $"Exp mode not set: {CamMod.expMode} "; errMssg += $"Exp mode not set: {CamMod.expMode} ";
// Determine display result
string displayResult = string.IsNullOrWhiteSpace(errMssg) ? "OK" : errMssg;
bool isError = !string.IsNullOrWhiteSpace(errMssg);
// Create label dynamically if not provided
if (Lbl == null)
{
string moduleName;
try
{
if (object.ReferenceEquals(CamMod, AiQ_GUI.AiQ_Tests.TestingFunctions.DiagsAPI.IRmodule))
moduleName = "IR Module";
else if (object.ReferenceEquals(CamMod, AiQ_GUI.AiQ_Tests.TestingFunctions.DiagsAPI.OVmodule))
moduleName = "OV Module";
else
moduleName = CamMod.firmwareVer == null ? "OV Module" : (CamMod.zoom < 5000 ? "IR Module" : "OV Module");
}
catch
{
moduleName = CamMod.firmwareVer == null ? "OV Module" : (CamMod.zoom < 5000 ? "IR Module" : "OV Module");
}
MainForm.Instance.AddLabelToPanel($"{moduleName} = {displayResult}", isError);
return;
}
// Update existing label
try try
{ {
Lbl.Invoke(() => Lbl.Invoke(() =>
{ {
if (string.IsNullOrWhiteSpace(errMssg)) Lbl.Text = Lbl.Text.TrimEnd('=', ' ') + " = " + displayResult;
{ Lbl.ForeColor = isError ? Color.Red : Color.LimeGreen;
Lbl.Text += "OK";
Lbl.ForeColor = Color.LightGreen;
}
else
{
Lbl.Text += errMssg;
Lbl.ForeColor = Color.Red;
}
}); });
} }
catch (Exception ex) catch (Exception ex)
@@ -107,5 +126,33 @@ namespace AiQ_GUI
// Build the final VISCA command string using the input characters split as p and q // Build the final VISCA command string using the input characters split as p and q
return $"8101044{command}00000{hex[0]}0{hex[1]}FF"; return $"8101044{command}00000{hex[0]}0{hex[1]}FF";
} }
public static async Task<bool> SetZoomLockOn(string IP)
{
// Set Zoomlock on and if it fails ask user to set it manually
if (!(await FlexiAPI.APIHTTPRequest("/api/zoomLock?enable=true", IP)).Contains("Zoom lock enabled.")
&& !await MainForm.Instance.DisplayQuestion("Could not set zoomlock on" + Environment.NewLine + "Set Zoomlock to on then click YES. Click NO to restart."))
{
return false;
}
return true;
}
public static async Task<bool> ZoomModules(string VISCAInput, string IPAddress)
{
// Populate the VISCA command with the four zoom characters
string VISCA = $"810104470{VISCAInput[0]}0{VISCAInput[1]}0{VISCAInput[2]}0{VISCAInput[3]}FF";
Task<string> TS1 = FlexiAPI.APIHTTPVISCA(IPAddress, VISCA, true);
Task<string> TS2 = FlexiAPI.APIHTTPVISCA(IPAddress, VISCA, false);
await Task.WhenAll(TS1, TS2);
const string ExpReply = "9041FF9051FF";
if (TS1.Result == ExpReply && TS1.Result == ExpReply)
return true;
return false;
}
} }
} }

View File

@@ -24,7 +24,7 @@ namespace AiQ_GUI
} }
catch (Exception ex) catch (Exception ex)
{ {
return $"Error during GET request: {ex.Message}{Level.ERROR}"; return $"Error during GET request: {ex.Message}";
} }
} }
@@ -42,7 +42,7 @@ namespace AiQ_GUI
} }
catch (Exception ex) catch (Exception ex)
{ {
return $"Error in HTTP_Update: {ex.Message}{Level.ERROR}"; return $"Error in HTTP_Update: {ex.Message}";
} }
} }
@@ -56,7 +56,7 @@ namespace AiQ_GUI
} }
catch (Exception ex) catch (Exception ex)
{ {
return $"Error in HTTP_Fetch: {ex.Message}{Level.ERROR}"; return $"Error in HTTP_Fetch: {ex.Message}";
} }
} }
@@ -101,22 +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}{Level.ERROR}"; return $"Server returned {(int)response.StatusCode}: {response.ReasonPhrase}. Details: {responseBody}";
return responseBody; return responseBody;
} }
catch (TaskCanceledException) catch (TaskCanceledException)
{ {
return $"Timeout uploading to {url}.{Level.ERROR}"; return $"Timeout uploading to {url}.";
} }
catch (HttpRequestException ex) catch (HttpRequestException ex)
{ {
return $"HTTP error uploading to {url}: {ex.Message}{Level.ERROR}"; return $"HTTP error uploading to {url}: {ex.Message}";
} }
catch (Exception ex) catch (Exception ex)
{ {
return $"Unexpected error uploading to {url}: {ex.Message} {(ex.InnerException?.Message ?? string.Empty)} {Level.ERROR}"; return $"Unexpected error uploading to {url}: {ex.Message} {(ex.InnerException?.Message ?? string.Empty)}";
} }
} }
@@ -146,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?{Level.WARNING}" + Environment.NewLine + JSON); MainForm.Instance.AddToActionsList($"Error talking to Flexi, are you sure this is an AiQ?" + Environment.NewLine + JSON, Level.WARNING);
return null; return null;
} }
@@ -174,13 +174,13 @@ namespace AiQ_GUI
// Treat "operation was canceled" as a successful apply // Treat "operation was canceled" as a successful apply
if (response.Contains("The operation was canceled", StringComparison.OrdinalIgnoreCase)) if (response.Contains("The operation was canceled", StringComparison.OrdinalIgnoreCase))
{ {
Logging.LogMessage($"SetVaxtorMinMaxPlate: Camera applied config but closed connection early (safe to ignore).{Level.WARNING}"); Logging.LogMessage($"SetVaxtorMinMaxPlate: Camera applied config but closed connection early (safe to ignore).");
return true; return true;
} }
if (response.Contains("error", StringComparison.OrdinalIgnoreCase)) if (response.Contains("error", StringComparison.OrdinalIgnoreCase))
{ {
MainForm.Instance.DisplayQuestion($"SetVaxtorMinMaxPlate: failed - Please set manually{Level.WARNING}"); MainForm.Instance.DisplayQuestion($"SetVaxtorMinMaxPlate: failed - Please set manually");
return false; return false;
} }
@@ -188,37 +188,21 @@ namespace AiQ_GUI
} }
catch (Exception ex) catch (Exception ex)
{ {
MainForm.Instance.AddToActionsList($"Could not set Vaxtor Plate height and min confidence: {ex.Message}{Level.ERROR}"); MainForm.Instance.AddToActionsList($"Could not set Vaxtor Plate height and min confidence: {ex.Message}", Level.ERROR);
return false; return false;
} }
} }
public static async Task<bool> SetZoomLockOn(string IP) public static async Task GPSFix(string IPAddress)
{ {
// Set Zoomlock on and if it fails ask user to set it manually string sysstatus = await APIHTTPRequest("/sysstatus", IPAddress, 5);
if (!(await APIHTTPRequest("/api/zoomLock?enable=true", IP)).Contains("Zoom lock enabled.") SysStatus status = JsonConvert.DeserializeObject<SysStatus>(sysstatus);
&& !await MainForm.Instance.DisplayQuestion($"Could not set zoomlock on{Level.WARNING}" + Environment.NewLine + $"Set Zoomlock to on then click YES. Click NO to restart. {Level.WARNING}"))
if (status.gpsState == 0 || status.gpsPresent == "Not Fitted")
{ {
return false; MainForm.Instance.AddToActionsList($"GPS not present in camera. State: {status.gpsState} & Status: {status.gpsPresent}");
return;
} }
return true;
}
public static async Task<bool> ZoomModules(string VISCAInput, string IPAddress)
{
// Populate the VISCA command with the four zoom characters
string VISCA = $"810104470{VISCAInput[0]}0{VISCAInput[1]}0{VISCAInput[2]}0{VISCAInput[3]}FF";
Task<string> TS1 = APIHTTPVISCA(IPAddress, VISCA, true);
Task<string> TS2 = APIHTTPVISCA(IPAddress, VISCA, false);
await Task.WhenAll(TS1, TS2);
const string ExpReply = "9041FF9051FF";
if (TS1.Result == ExpReply && TS1.Result == ExpReply)
return true;
return false;
} }
public static async Task SetTrim(string IPAddress, string LblTxt, int RetryCount = 0) // Sets trim by getting plate postion as metric public static async Task SetTrim(string IPAddress, string LblTxt, int RetryCount = 0) // Sets trim by getting plate postion as metric
@@ -233,7 +217,7 @@ namespace AiQ_GUI
} }
catch catch
{ {
MainForm.Instance.AddToActionsList($"Error reading trim JSON - {Level.ERROR}" + trimData); MainForm.Instance.AddToActionsList($"Error reading trim JSON - " + trimData, Level.ERROR);
return; return;
} }
@@ -242,7 +226,7 @@ namespace AiQ_GUI
{ {
if (RetryCount >= 3) if (RetryCount >= 3)
{ {
await MainForm.Instance.DisplayOK($"Please align trim in webpage then click OK.{Level.WARNING}"); // Awaited till OK has been clicked await MainForm.Instance.DisplayOK($"Please align trim in webpage then click OK."); // Awaited till OK has been clicked
return; return;
} }
@@ -270,11 +254,11 @@ 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 {Level.WARNING}"); // Awaited till OK has been clicked await MainForm.Instance.DisplayOK($"Please centralise plate in view THEN press OK "); // Awaited till OK has been clicked
if (RetryCount >= 3) if (RetryCount >= 3)
{ {
await MainForm.Instance.DisplayOK($"Please align trim in webpage then click OK. {Level.WARNING}"); // Awaited till OK has been clicked await MainForm.Instance.DisplayOK($"Please align trim in webpage then click OK. "); // 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
@@ -291,7 +275,7 @@ namespace AiQ_GUI
string TrimResp = await HTTP_Update("SightingCreator", IPAddress, Trim_JSON); string TrimResp = await HTTP_Update("SightingCreator", IPAddress, Trim_JSON);
if (!TrimResp.Contains($"\"propInterCameraOffsetX\": {{\"value\": \"{Convert.ToString(TrimX)}\", \"datatype\": \"int\"}}, \"propInterCameraOffsetY\": {{\"value\": \"{Convert.ToString(TrimY)}\", \"datatype\": \"int\"}},")) if (!TrimResp.Contains($"\"propInterCameraOffsetX\": {{\"value\": \"{Convert.ToString(TrimX)}\", \"datatype\": \"int\"}}, \"propInterCameraOffsetY\": {{\"value\": \"{Convert.ToString(TrimY)}\", \"datatype\": \"int\"}},"))
MainForm.Instance.AddToActionsList($"Could not set camera trim{Level.ERROR}"); 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
@@ -365,7 +349,7 @@ namespace AiQ_GUI
if (FoundCams.Contains("192.168.1.211")) if (FoundCams.Contains("192.168.1.211"))
{ {
MainForm.Instance.AddToActionsList($"Could not set camera to DHCP please check camera.{Level.ERROR}"); MainForm.Instance.AddToActionsList($"Could not set camera to DHCP please check camera.", Level.ERROR);
return false; return false;
} }
@@ -558,6 +542,12 @@ namespace AiQ_GUI
public int colourY { get; set; } public int colourY { get; set; }
} }
public class SysStatus
{
public int gpsState { get; set; } = 0;
public string gpsPresent { get; set; } = string.Empty;
}
public class NetworkConfig public class NetworkConfig
{ {
public Property propDHCP { get; set; } = new Property(); public Property propDHCP { get; set; } = new Property();

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}{Level.ERROR}"); 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}{Level.ERROR}"); 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.{Level.ERROR}"); 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}{Level.ERROR}"); 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} {Level.ERROR}"); 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} {Level.ERROR}"); 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} {Level.ERROR}"); MainForm.Instance.AddToActionsList($"Error calculating luminance: {ex.Message}",Level.ERROR);
return; return;
} }
} }

View File

@@ -9,7 +9,24 @@
{ {
VorI.Sort(); // Sort the list from lowest to highest to prepare for finding the median VorI.Sort(); // Sort the list from lowest to highest to prepare for finding the median
double medianVorI = (VorI[2] + VorI[3]) / 2.0; // Will always be even (6) number of channels therefore average the two middle elements double medianVorI = (VorI[2] + VorI[3]) / 2.0; // Will always be even (6) number of channels therefore average the two middle elements
lblVorI.Text += $"Median: {medianVorI}{VormA} "; // Display median value string medianText = $"Median: {medianVorI}{VormA}";
//Expected value of 0 means informational only (force green)
if (ExpVorI == 0)
{
if (lblVorI == null)
{
MainForm.Instance.AddLabelToPanel($"LED {VormA} = {medianText}", false);
return;
}
lblVorI.Invoke(() =>
{
lblVorI.Text = lblVorI.Text.TrimEnd('=', ' ') + " = " + medianText;
lblVorI.ForeColor = Color.LimeGreen;
});
return;
}
// Define the 20% threshold ranges // Define the 20% threshold ranges
double LowerThreshold = ExpVorI * 0.8; double LowerThreshold = ExpVorI * 0.8;
@@ -18,8 +35,17 @@
// Check median is within 20% of the expected value // Check median is within 20% of the expected value
if (medianVorI < LowerThreshold || medianVorI > UpperThreshold) if (medianVorI < LowerThreshold || medianVorI > UpperThreshold)
{ {
lblVorI.Text += $" Median away from excepted {ExpVorI}{VormA}"; medianText += $" (away from expected {ExpVorI}{VormA})";
if (lblVorI == null)
{
MainForm.Instance.AddLabelToPanel($"LED {VormA} = {medianText}", true);
return;
}
lblVorI.Invoke(() =>
{
lblVorI.Text = lblVorI.Text.TrimEnd('=', ' ') + " = " + medianText;
lblVorI.ForeColor = Color.Red; lblVorI.ForeColor = Color.Red;
});
return; return;
} }
@@ -35,17 +61,35 @@
// If there are no single channels outside the threshold then green, else red // If there are no single channels outside the threshold then green, else red
if (outOfRangeVoltageChannels.Count == 0) if (outOfRangeVoltageChannels.Count == 0)
{ {
lblVorI.ForeColor = Color.LightGreen; if (lblVorI == null)
{
MainForm.Instance.AddLabelToPanel($"LED {VormA} = {medianText}", false);
return;
}
lblVorI.Invoke(() =>
{
lblVorI.Text = lblVorI.Text.TrimEnd('=', ' ') + " = " + medianText;
lblVorI.ForeColor = Color.LimeGreen;
});
} }
else if (outOfRangeVoltageChannels.Count != 0) else if (outOfRangeVoltageChannels.Count != 0)
{ {
lblVorI.Text += "error on " + string.Join(", ", outOfRangeVoltageChannels); // Join all problem channels together to present on form string errorText = medianText + " (error on " + string.Join(", ", outOfRangeVoltageChannels) + ")";
if (lblVorI == null)
{
MainForm.Instance.AddLabelToPanel($"LED {VormA} = {errorText}", true);
return;
}
lblVorI.Invoke(() =>
{
lblVorI.Text = lblVorI.Text.TrimEnd('=', ' ') + " = " + errorText;
lblVorI.ForeColor = Color.Red; lblVorI.ForeColor = Color.Red;
});
} }
} }
catch (Exception ex) catch (Exception ex)
{ {
MainForm.Instance.AddToActionsList($"Error checking LEDs: {ex.Message} {Level.ERROR}"); 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:{Level.ERROR}" + Type; return $"Unrecognised challenge type:" + 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.{Level.ERROR}"; return $"Invalid challenge format. Challenge must be 6 characters.";
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.{Level.ERROR}"; return $"Invalid salt format. Salt must be 32 characters.";
// 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{Level.ERROR}" + ex.Message; return $"Error: Could not generate password" + ex.Message;
} }
} }
@@ -85,7 +85,7 @@ namespace AiQ_GUI
} }
catch (Exception ex) catch (Exception ex)
{ {
MainForm.Instance.AddToActionsList($"Exception in FetchDevPassword: {Level.ERROR}" + ex.Message); MainForm.Instance.AddToActionsList($"Exception in FetchDevPassword:" + ex.Message, Level.ERROR);
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?{Level.WARNING}"); 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.{Level.WARNING}"); 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.{Level.WARNING}"); 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. {Level.WARNING}"); 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. {Level.WARNING}"); 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.{Level.WARNING}"); MainForm.Instance.AddToActionsList($"Router could not ping 8.8.8.8. Please check the online connection.", Level.WARNING);
PassTest = false; PassTest = false;
} }

View File

@@ -9,7 +9,7 @@ namespace AiQ_GUI
public const string SSHPassword = "mavPA$$"; public const string SSHPassword = "mavPA$$";
public const string SSHPasswordNEW = "#!mavsoftMESA19"; // New password for SSH after last one got leaked public const string SSHPasswordNEW = "#!mavsoftMESA19"; // New password for SSH after last one got leaked
private static SshClient SshConnect(string IPAddress) public static SshClient SshConnect(string IPAddress)
{ {
SshClient client = new(IPAddress, SSHUsername, SSHPasswordNEW); SshClient client = new(IPAddress, SSHUsername, SSHPasswordNEW);
try try
@@ -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. {Level.WARNING}"); MainForm.Instance.AddToActionsList($"SSH connection failed: {Ex.Message}. Check password or network. ", Level.WARNING);
} }
return null; return null;
@@ -40,6 +40,13 @@ namespace AiQ_GUI
{ {
SshClient client = SshConnect(IPAddress); SshClient client = SshConnect(IPAddress);
// CRITICAL: Check if connection actually succeeded
if (client == null)
{
MainForm.Instance.AddToActionsList($"SSH connection failed for {IPAddress}", Level.ERROR);
return Data; // Return empty data
}
try try
{ {
Data.packages = GetVaxtorPackages(client); Data.packages = GetVaxtorPackages(client);
@@ -75,12 +82,9 @@ namespace AiQ_GUI
} }
catch (Exception ex) catch (Exception ex)
{ {
MainForm.Instance.AddToActionsList($"SSH connection failed: {ex.Message}. Check password or network. {Level.WARNING}"); 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)}"));
Logging.LogMessage(LogMssg); // Log all of Data
return Data; return Data;
} }
@@ -170,7 +174,7 @@ namespace AiQ_GUI
} }
catch (Exception ex) catch (Exception ex)
{ {
MainForm.Instance.AddToActionsList($"SSH connection failed: {ex.Message}. Check password or network. {Level.WARNING}"); MainForm.Instance.AddToActionsList($"SSH connection failed: {ex.Message}. Check password or network. ", Level.WARNING);
} }
return (string.Empty, string.Empty); return (string.Empty, string.Empty);
@@ -234,6 +238,14 @@ namespace AiQ_GUI
const double Deviation = 20.0; // ±20GB const double Deviation = 20.0; // ±20GB
double currentSize = NormaliseFSSize(sshData.FilesystemSize); double currentSize = NormaliseFSSize(sshData.FilesystemSize);
// Create label dynamically if not provided
if (LblFSSize == null)
{
MainForm.Instance.AddLabelToPanel($"Filesystem Size = {currentSize}GB", currentSize < (GoodSize - Deviation) || currentSize > (GoodSize + Deviation));
return sshData;
}
LblFSSize.Text = $"Filesystem Size = {currentSize}GB"; LblFSSize.Text = $"Filesystem Size = {currentSize}GB";
if (Math.Abs(GoodSize - currentSize) < Deviation) if (Math.Abs(GoodSize - currentSize) < Deviation)
@@ -280,7 +292,8 @@ namespace AiQ_GUI
{ {
try try
{ {
if (string.IsNullOrWhiteSpace(rootSize)) return 0; if (string.IsNullOrWhiteSpace(rootSize))
return 0;
// Extract value & unit // Extract value & unit
System.Text.RegularExpressions.Match match = RegexCache.FileSizeRegex().Match(rootSize.Trim()); System.Text.RegularExpressions.Match match = RegexCache.FileSizeRegex().Match(rootSize.Trim());
@@ -323,7 +336,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. {Level.WARNING}"); MainForm.Instance.AddToActionsList($"Block device {device} not found. ", Level.WARNING);
return false; return false;
} }
@@ -365,6 +378,24 @@ namespace AiQ_GUI
return false; return false;
} }
} }
public static bool MobiletxtCheck(string IPAddress)
{
try
{
using (SshClient client = SshConnect(IPAddress))
{
// test -f returns exit status 0 if file exists
SshCommand checkDevice = client.RunCommand("test -f /home/mav/Mobile-Setup-configuration-marker.txt"
);
return checkDevice.ExitStatus == 0;
}
}
catch (Exception ex)
{
MainForm.Instance.AddToActionsList($"Can't check txt file: Error {ex}", Level.ERROR);
return false;
}
}
public static void Sync(string IPAddress) public static void Sync(string IPAddress)
{ {
@@ -378,7 +409,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 {Level.ERROR}"); MainForm.Instance.AddToActionsList($"Cannot sync files to disk. Replied: {checkDevice.Result}. DO NOT TURN OFF, GET SUPERVISOR", Level.ERROR);
return; return;
} }
@@ -387,7 +418,7 @@ namespace AiQ_GUI
} }
catch (Exception ex) catch (Exception ex)
{ {
MainForm.Instance.AddToActionsList($"Cannot sync becuase: {ex.Message}. DO NOT TURN OFF, GET SUPERVISOR {Level.ERROR}"); MainForm.Instance.AddToActionsList($"Cannot sync becuase: {ex.Message}. DO NOT TURN OFF, GET SUPERVISOR", Level.ERROR);
} }
} }
} }

View File

@@ -107,12 +107,12 @@ namespace AiQ_GUI
} }
else else
{ {
return $"Last serial number not found{Level.ERROR}"; return $"Last serial number not found";
} }
} }
catch (Exception ex) catch (Exception ex)
{ {
return $"ERROR: {ex.Message}{Level.ERROR}"; return $"ERROR: {ex.Message}";
} }
} }
@@ -148,12 +148,12 @@ namespace AiQ_GUI
} }
else else
{ {
return $"Serial number not found{Level.ERROR}"; return $"Serial number not found";
} }
} }
catch (Exception ex) catch (Exception ex)
{ {
return $"ERROR: {ex.Message}{Level.ERROR}"; return $"ERROR: {ex.Message}";
} }
} }
@@ -196,7 +196,7 @@ namespace AiQ_GUI
} }
catch (Exception ex) catch (Exception ex)
{ {
return $"Failed to update spreadsheet data, please check manually{Level.ERROR}" + ex.Message; return $"Failed to update spreadsheet data, please check manually" + ex.Message;
} }
} }
@@ -225,7 +225,7 @@ namespace AiQ_GUI
} }
catch (Exception ex) catch (Exception ex)
{ {
return $"Failed to update spreadsheet data, please check manually{Level.WARNING}" + ex.Message; return $"Failed to update spreadsheet data, please check manually" + ex.Message;
} }
} }

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{Level.ERROR}"); await MainForm.Instance.TestFailed(Btn, $"Visual Test Fail - Sleeve not aligned");
} }
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{Level.ERROR}"); await MainForm.Instance.TestFailed(Btn, $"Visual Test Fail - Not all front screws fitted");
} }
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{Level.ERROR}"); await MainForm.Instance.TestFailed(Btn, $"Visual Test Fail - Not all rear screws fitted");
} }
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{Level.ERROR}"); await MainForm.Instance.TestFailed(Btn, $"Visual Test Fail - Unit rattles");
} }
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}{Level.ERROR}"); MainForm.Instance.AddToActionsList($"Error fetching versions for {IPAddress}: {ex.Message}", Level.ERROR);
return null; return null;
} }
@@ -187,6 +187,7 @@ namespace AiQ_GUI
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 CheckBox CheckBox { get; set; } = new CheckBox();
public string TestReportLoc { get; set; } = string.Empty; // Location of test report file public string TestReportLoc { get; set; } = string.Empty; // Location of test report file
public string Manfr { get; internal set; }
} }
// Static class for global flags // Static class for global flags

4
LDS.cs
View File

@@ -30,7 +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}"); MainForm.Instance.AddToActionsList($"Error loading Local Data Store", Level.WARNING);
return null; // Return null to indicate failure return null; // Return null to indicate failure
} }
} }
@@ -45,7 +45,7 @@ namespace AiQ_GUI
} }
catch (Exception ex) catch (Exception ex)
{ {
MainForm.Instance.AddToActionsList($"Error saving Local Data Store: {ex.Message}{Level.WARNING}"); 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)
{ {
MainForm.Instance.AddToActionsList($"Error logging message: {ex.Message}{Level.ERROR}"); MessageBox.Show($"Error logging message: {ex.Message}");
} }
} }
} }

View File

@@ -4,14 +4,32 @@ namespace AiQ_GUI
{ {
class Access class Access
{ {
public const string connString = @"Provider=Microsoft.ACE.OLEDB.12.0;Data Source=G:\Shared drives\MAV Production GUI's\AiQ\GUI's\AiQ_Final_Test.accdb;Persist Security Info=False;OLE DB Services=-1;";
// Reads camera model numbers and descriptions from the database, sorts them alphabetically by model number (except "AB12CD", which appears last), and formats each entry as "ModelNumber - Description". public const string connString =
public static string[] ReadCamTypes() @"Provider=Microsoft.ACE.OLEDB.12.0;Data Source=G:\Shared drives\MAV Production GUI's\AiQ\GUI's\AiQ_Final_Test.accdb;Persist Security Info=False;OLE DB Services=-1;";
// Allows Different Cam Types with different schemas to be handled safely
private static bool HasColumn(OleDbDataReader reader, string columnName)
{ {
List<Tuple<string, string>> modelTuples = new(30); // Preallocate list with estimated capacity to reduce internal resizing for (int i = 0; i < reader.FieldCount; i++)
if (reader.GetName(i).Equals(columnName, StringComparison.OrdinalIgnoreCase))
return true;
return false;
}
public static string[] ReadCamTypes(string camType)
{
// No camera type selected
if (string.IsNullOrWhiteSpace(camType))
return null;
// Preallocate list to reduce resizing
List<Tuple<string, string>> modelTuples = new(30);
using OleDbConnection conn = new(connString); using OleDbConnection conn = new(connString);
// Attempt to open the database
try try
{ {
conn.Open(); conn.Open();
@@ -22,31 +40,36 @@ namespace AiQ_GUI
return null; return null;
} }
const string query = "SELECT ModelNumber, Description FROM AiQ WHERE MarkNumber > 1"; // Mobile table does not have MarkNumber
// AiQ and others might do ????? - TODO ask
string query = camType == "Mobile"
? "SELECT ModelNumber, Description FROM [Mobile]"
: $"SELECT ModelNumber, Description FROM [{camType}] WHERE MarkNumber > 1";
using OleDbCommand cmd = new(query, conn); using OleDbCommand cmd = new(query, conn);
using OleDbDataReader reader = cmd.ExecuteReader(); using OleDbDataReader reader = cmd.ExecuteReader();
// Read all models from the selected table
while (reader.Read()) while (reader.Read())
{ {
// Extract each model number and description, using empty string if null
string modelNumber = reader["ModelNumber"] as string ?? string.Empty; string modelNumber = reader["ModelNumber"] as string ?? string.Empty;
string description = reader["Description"] as string ?? string.Empty; string description = reader["Description"] as string ?? string.Empty;
modelTuples.Add(Tuple.Create(modelNumber.Trim(), description.Trim())); modelTuples.Add(Tuple.Create(modelNumber.Trim(), description.Trim()));
} }
// Sort: push "AB12CD" to the bottom, then sort remaining items alphabetically // Sort models, pushing AB12CD to the bottom
IOrderedEnumerable<Tuple<string, string>> sorted = modelTuples.OrderBy(t => t.Item1.Equals("AB12CD", StringComparison.OrdinalIgnoreCase) ? 1 : 0) var sorted = modelTuples
.OrderBy(t => t.Item1.Equals("AB12CD", StringComparison.OrdinalIgnoreCase) ? 1 : 0)
.ThenBy(t => t.Item1, StringComparer.OrdinalIgnoreCase); .ThenBy(t => t.Item1, StringComparer.OrdinalIgnoreCase);
conn.Close(); // Format for combo box display
// Format the sorted tuples as "ModelNumber - Description" strings and return as array
return sorted.Select(t => $"{t.Item1} - {t.Item2}").ToArray(); return sorted.Select(t => $"{t.Item1} - {t.Item2}").ToArray();
} }
// Read the universal data table from the database and populate the UniversalData class with the values.
public static void ReadUniData() public static void ReadUniData()
{ {
using OleDbConnection conn = new(connString); using OleDbConnection conn = new(connString);
try try
{ {
conn.Open(); conn.Open();
@@ -57,24 +80,28 @@ namespace AiQ_GUI
return; return;
} }
const string query = "SELECT FlexiVersion, FlexiRevision, WonwooFirmware, AiQGUIVersion, PowerConsumption, LicencingServerURL FROM UniversalData"; // Grab the universal data const string query =
"SELECT FlexiVersion, FlexiRevision, WonwooFirmware, AiQGUIVersion, PowerConsumption, LicencingServerURL, SRZFirmware FROM UniversalData";
using OleDbCommand cmd = new(query, conn); using OleDbCommand cmd = new(query, conn);
using OleDbDataReader reader = cmd.ExecuteReader(); using OleDbDataReader reader = cmd.ExecuteReader();
// UniversalData is expected to contain a single row
reader.Read(); reader.Read();
UniversalData.ExpFlexiVer = Convert.ToString(reader["FlexiVersion"]); UniversalData.ExpFlexiVer = Convert.ToString(reader["FlexiVersion"]);
UniversalData.ExpFlexiRev = Convert.ToString(reader["FlexiRevision"]); UniversalData.ExpFlexiRev = Convert.ToString(reader["FlexiRevision"]);
UniversalData.WonwooFirmware = Convert.ToString(reader["WonwooFirmware"]); UniversalData.WonwooFirmware = Convert.ToString(reader["WonwooFirmware"]);
UniversalData.LatestVersion = Convert.ToString(reader["AiQGUIVersion"]); UniversalData.LatestVersion = Convert.ToString(reader["AiQGUIVersion"]);
UniversalData.PowerConsumption = Convert.ToInt16(reader["PowerConsumption"]); UniversalData.PowerConsumption = Convert.ToInt16(reader["PowerConsumption"]);
UniversalData.LicencingServerURL = Convert.ToString(reader["LicencingServerURL"]); UniversalData.LicencingServerURL = Convert.ToString(reader["LicencingServerURL"]);
conn.Close(); UniversalData.SRZFirmware = Convert.ToString(reader["SRZFirmware"]);
} }
// Populates CameraAccessInfo dynamically based on available columns
// Knowing the model number on test, this function reads the database and populates the Camera class with the values. public static void ReadModelRow(string camType, string ModelOnTest)
public static void ReadModelRow(string ModelOnTest)
{ {
using OleDbConnection conn = new(connString); using OleDbConnection conn = new(connString);
try try
{ {
conn.Open(); conn.Open();
@@ -85,66 +112,85 @@ namespace AiQ_GUI
return; return;
} }
string query = $"SELECT * FROM AiQ WHERE ModelNumber = '{ModelOnTest}';"; // Grab all the info for specified model // Parameterised query to prevent injection
string query = $"SELECT * FROM [{camType}] WHERE ModelNumber = ?";
using OleDbCommand cmd = new(query, conn); using OleDbCommand cmd = new(query, conn);
cmd.Parameters.AddWithValue("?", ModelOnTest);
using OleDbDataReader reader = cmd.ExecuteReader(); using OleDbDataReader reader = cmd.ExecuteReader();
reader.Read();
// Populate the CameraAccessInfo class with the values from the database // No matching model found
CameraAccessInfo.Processor = Convert.ToString(reader["Processor"]); if (!reader.Read())
CameraAccessInfo.VaxtorLic = Convert.ToBoolean(reader["Vaxtor"]); return;
CameraAccessInfo.HardwareExtras = Convert.ToString(reader["HardwareExtras"]);
CameraAccessInfo.PowerType = Convert.ToString(reader["PowerType"]); // Populate CameraAccessInfo only if columns exist
CameraAccessInfo.LED_V = Convert.ToDouble(reader["LEDVoltage"]); CameraAccessInfo.Processor =
CameraAccessInfo.LED_I = Convert.ToInt32(reader["LEDCurrent"]); HasColumn(reader, "Processor") ? Convert.ToString(reader["Processor"]) : string.Empty;
CameraAccessInfo.SpreadsheetID = Convert.ToString(reader["SSID"]);
conn.Close(); CameraAccessInfo.VaxtorLic =
HasColumn(reader, "Vaxtor") && Convert.ToString(reader["Vaxtor"]) == "Yes";
CameraAccessInfo.HardwareExtras =
HasColumn(reader, "HardwareExtras") ? Convert.ToString(reader["HardwareExtras"]) : string.Empty;
CameraAccessInfo.PowerType =
HasColumn(reader, "PowerType") ? Convert.ToString(reader["PowerType"]) : string.Empty;
CameraAccessInfo.LED_V =
HasColumn(reader, "LEDVoltage") ? Convert.ToDouble(reader["LEDVoltage"]) : 0;
CameraAccessInfo.LED_I =
HasColumn(reader, "LEDCurrent") ? Convert.ToInt32(reader["LEDCurrent"]) : 0;
CameraAccessInfo.SpreadsheetID =
HasColumn(reader, "SSID") ? Convert.ToString(reader["SSID"]) : string.Empty;
} }
public static void Stats(string TypeOfTest, string modelNumber) public static void Stats(string TypeOfTest, string modelNumber)
{ {
Stats([TypeOfTest], modelNumber); Stats([TypeOfTest], modelNumber);
} }
public static void Stats(string[] TypeOfTest, string modelNumber) public static void Stats(string[] TypeOfTest, string modelNumber)
{ {
using OleDbConnection conn = new(connString); // Opens connection to Access database using OleDbConnection conn = new(connString);
try try
{ {
conn.Open(); // Opens DB conn.Open();
foreach (string type in TypeOfTest) foreach (string type in TypeOfTest)
{ {
string query = $"UPDATE AiQ SET [{type}] = [{type}] + 1 WHERE [ModelNumber] = ?"; // Add one for every test ran of this type for this model number string query =
using OleDbCommand cmd = new(query, conn); // Create command $"UPDATE AiQ SET [{type}] = [{type}] + 1 WHERE [ModelNumber] = ?";
cmd.Parameters.AddWithValue("?", modelNumber); // Add model number to prevent injection
using OleDbCommand cmd = new(query, conn);
cmd.Parameters.AddWithValue("?", modelNumber);
int rowsAffected = cmd.ExecuteNonQuery(); int rowsAffected = cmd.ExecuteNonQuery();
// Execute the command and get the number of rows affected
if (rowsAffected == 0) // If one or more rows were updated if (rowsAffected == 0)
MainForm.Instance.AddToActionsList($"No rows affected for {modelNumber}{Level.ERROR}"); MainForm.Instance.AddToActionsList($"No rows affected for {modelNumber}", Level.ERROR);
} }
conn.Close();
} }
catch catch
{ {
MainForm.Instance.AddToActionsList($"Could not access Access in Google Drive. Is it running?{Level.WARNING}"); MainForm.Instance.AddToActionsList(
return; "Could not access Access in Google Drive. Is it running?", Level.WARNING);
} }
} }
public static void StatsDiags(string redDiagLabels, string RhTxBxActionsText, string ModelNumber) public static void StatsDiags(string redDiagLabels, string RhTxBxActionsText, string ModelNumber)
{ {
using OleDbConnection conn = new(connString); using OleDbConnection conn = new(connString);
conn.Open(); conn.Open();
// Null checks // Replace null or empty values
string redVal = string.IsNullOrWhiteSpace(redDiagLabels) ? "-" : redDiagLabels; string redVal = string.IsNullOrWhiteSpace(redDiagLabels) ? "-" : redDiagLabels;
string actVal = string.IsNullOrWhiteSpace(RhTxBxActionsText) ? "-" : RhTxBxActionsText; string actVal = string.IsNullOrWhiteSpace(RhTxBxActionsText) ? "-" : RhTxBxActionsText;
string model = string.IsNullOrWhiteSpace(ModelNumber) ? "-" : ModelNumber; string model = string.IsNullOrWhiteSpace(ModelNumber) ? "-" : ModelNumber;
const string sql = @"INSERT INTO DiagsStats ([Date], [Model], [Red Diags Labels], [RhTxBxActions Contents]) VALUES (?, ?, ?, ?)"; const string sql =
@"INSERT INTO DiagsStats ([Date], [Model], [Red Diags Labels], [RhTxBxActions Contents])
VALUES (?, ?, ?, ?)";
using OleDbCommand cmd = new(sql, conn); using OleDbCommand cmd = new(sql, conn);
@@ -153,19 +199,18 @@ namespace AiQ_GUI
OleDbType = OleDbType.Date, OleDbType = OleDbType.Date,
Value = DateTime.Now Value = DateTime.Now
}); });
cmd.Parameters.AddWithValue("?", model); cmd.Parameters.AddWithValue("?", model);
cmd.Parameters.AddWithValue("?", redVal); cmd.Parameters.AddWithValue("?", redVal);
cmd.Parameters.AddWithValue("?", actVal); cmd.Parameters.AddWithValue("?", actVal);
int rows = cmd.ExecuteNonQuery(); int rows = cmd.ExecuteNonQuery();
conn.Close();
if (rows == 0) if (rows == 0)
MainForm.Instance.AddToActionsList($"No rows inserted into DiagsStats (unexpected).{Level.ERROR}"); MainForm.Instance.AddToActionsList(
"No rows inserted into DiagsStats (unexpected).", Level.ERROR);
} }
} }
// Expected universal data for the GUI, read from the database
public class UniversalData public class UniversalData
{ {
public static string ExpFlexiVer { get; set; } = string.Empty; public static string ExpFlexiVer { get; set; } = string.Empty;
@@ -174,9 +219,9 @@ namespace AiQ_GUI
public static string LatestVersion { get; set; } = string.Empty; public static string LatestVersion { get; set; } = string.Empty;
public static int PowerConsumption { get; set; } = 0; public static int PowerConsumption { get; set; } = 0;
public static string LicencingServerURL { get; set; } = string.Empty; public static string LicencingServerURL { get; set; } = string.Empty;
public static string SRZFirmware { get; set; } = string.Empty;
} }
// One object to contain all the camera info from the model info access database
public class CameraAccessInfo public class CameraAccessInfo
{ {
public static string Processor { get; set; } = string.Empty; public static string Processor { get; set; } = string.Empty;

View File

@@ -17,7 +17,7 @@ namespace AiQ_GUI
} }
else else
{ {
MainForm.Instance.AddToActionsList($"Could not find spreadsheet :( {Level.ERROR}"); 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 :( {Level.ERROR}"); 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 :( {Level.ERROR}"); 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 :( {Level.ERROR}"); 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 :) {Level.ERROR}"); MainForm.Instance.AddToActionsList($"Could not find spreadsheet :) ", Level.ERROR);
return "Spreadsheet not found"; return "Spreadsheet not found";
} }
@@ -161,7 +161,7 @@ namespace AiQ_GUI
{ {
if (!File.Exists(FilePath)) if (!File.Exists(FilePath))
{ {
MainForm.Instance.AddToActionsList($"Could not find spreadsheet :( {Level.ERROR}"); MainForm.Instance.AddToActionsList($"Could not find spreadsheet :( ", Level.ERROR);
return "Spreadsheet not found"; return "Spreadsheet not found";
} }
@@ -213,7 +213,7 @@ namespace AiQ_GUI
{ {
if (!File.Exists(FilePath)) if (!File.Exists(FilePath))
{ {
MainForm.Instance.AddToActionsList($"Could not find spreadsheet :( {Level.ERROR}"); MainForm.Instance.AddToActionsList($"Could not find spreadsheet :( ", Level.ERROR);
return $"Spreadsheet not found{ Level.ERROR}" return $"Spreadsheet not found{ Level.ERROR}"
; ;
} }
@@ -251,7 +251,7 @@ namespace AiQ_GUI
{ {
if (!File.Exists(filePath)) if (!File.Exists(filePath))
{ {
MainForm.Instance.AddToActionsList($"Could not find RMA Control spreadsheet :({Level.ERROR}"); MainForm.Instance.AddToActionsList($"Could not find RMA Control spreadsheet :(", Level.ERROR);
return 0; return 0;
} }
@@ -293,7 +293,7 @@ namespace AiQ_GUI
{ {
if (!File.Exists(filePath)) if (!File.Exists(filePath))
{ {
MainForm.Instance.AddToActionsList($"Could not find spreadsheet :( {Level.ERROR}"); MainForm.Instance.AddToActionsList($"Could not find spreadsheet :( ", Level.ERROR);
return 0; return 0;
} }
@@ -329,7 +329,7 @@ namespace AiQ_GUI
{ {
if (!File.Exists(filePath)) if (!File.Exists(filePath))
{ {
MainForm.Instance.AddToActionsList($"Could not find spreadsheet :({Level.ERROR}"); MainForm.Instance.AddToActionsList($"Could not find spreadsheet :(", Level.ERROR);
return -1; return -1;
} }
@@ -344,7 +344,7 @@ namespace AiQ_GUI
} }
catch (Exception ex) catch (Exception ex)
{ {
MainForm.Instance.AddToActionsList($"Error checking next free row: {Level.ERROR}" + ex.Message); MainForm.Instance.AddToActionsList($"Error checking next free row:" + ex.Message, Level.ERROR);
return -1; return -1;
} }
} }

View File

@@ -83,10 +83,30 @@ namespace AiQ_GUI
XLWorkbook workbook; XLWorkbook workbook;
if (File.Exists(exportPath)) if (File.Exists(exportPath))
{
try
{ {
workbook = new XLWorkbook(exportPath); workbook = new XLWorkbook(exportPath);
MainForm.Instance.AddToActionsList("Opened existing workbook."); MainForm.Instance.AddToActionsList("Opened existing workbook.");
} }
catch (Exception loadEx)
{
// Workbook is corrupted, backup the old file and create a new one
string backupPath = exportPath + ".corrupted_" + DateTime.Now.ToString("yyyyMMdd_HHmmss");
try
{
File.Move(exportPath, backupPath, overwrite: true);
MainForm.Instance.AddToActionsList($"WARNING: Existing workbook was corrupted. Backed up to: {backupPath}");
}
catch (Exception backupEx)
{
MainForm.Instance.AddToActionsList($"WARNING: Could not backup corrupted workbook. Details: {backupEx.Message}");
}
workbook = new XLWorkbook();
MainForm.Instance.AddToActionsList("Created new workbook (previous file was corrupted).");
}
}
else else
{ {
workbook = new XLWorkbook(); workbook = new XLWorkbook();

168
MobileTests/MobileAPI.cs Normal file
View File

@@ -0,0 +1,168 @@
using AiQ_GUI;
using System;
using System.Net.Http;
using System.Net.Http.Json;
using System.Security.Cryptography;
using System.Text;
using System.Text.Json;
using System.Threading.Tasks;
namespace AiQ_GUI.Mobile_Tests
{
public static class MobileAPI
{
// Login credentials
private const string DefaultUsername = "ADMIN";
private const string DefaultPassword = "1234";
public static async Task CheckFirmwareAsync()
{
using HttpClient client = new HttpClient
{
BaseAddress = new Uri($"http://{MainForm.Instance.CamOnTest.IP}")
};
try
{
// Login and get JWT token
string token = await LoginAsync(client, DefaultUsername, DefaultPassword);
// Attach JWT to all requests
client.DefaultRequestHeaders.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", token);
// Request firmware information
HttpResponseMessage response = await client.GetAsync("/app/v1/system/firmware");
string body = await response.Content.ReadAsStringAsync();
response.EnsureSuccessStatusCode();
JsonElement json = JsonSerializer.Deserialize<JsonElement>(body);
string version = json.GetProperty("version").GetString()?.Trim();
// Compare against expected SRZFirmware from UniversalData
if (string.IsNullOrEmpty(UniversalData.SRZFirmware))
{
MainForm.Instance.AddToActionsList($"Firmware check failed: Expected SRZFirmware not loaded in UniversalData", Level.ERROR);
MainForm.Instance.AddLabelToPanel($"Firmware = {version}", true);
}
else if (version == UniversalData.SRZFirmware)
{
MainForm.Instance.AddLabelToPanel($"Firmware = {version}", false);
}
else
{
MainForm.Instance.AddLabelToPanel($"Firmware = {version}", true);
}
}
catch (Exception ex)
{
MainForm.Instance.AddToActionsList($"Firmware check failed: {ex.Message}", Level.ERROR);
MainForm.Instance.AddLabelToPanel("Firmware = Unknown", true);
}
}
public static async Task ChangeDayNightModeAsync(uint mode)
{
// mode: 0 = Auto, 1 = Day, 2 = Night
string modeName = mode switch
{
0 => "Auto",
1 => "Day",
2 => "Night",
_ => "Unknown"
};
// operatingMode required by firmware (!)
// day -> 1, night -> 0
int operatingMode = mode switch
{
1 => 1, // Day
2 => 0, // Night
_ => 1 // Auto: keep day as safe default
};
using HttpClient client = new HttpClient
{
BaseAddress = new Uri($"http://{MainForm.Instance.CamOnTest.IP}")
};
try
{
// Login
string token = await LoginAsync(client, DefaultUsername, DefaultPassword);
client.DefaultRequestHeaders.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", token);
var payload = new
{
dayNightMode = new
{
mode = mode,
dayToNightSens = 4,
nightToDaySens = 24,
transitionDelay = 20,
operatingMode = operatingMode
}
};
HttpResponseMessage response = await client.PostAsJsonAsync("/app/v1/camera/image", payload);
if (response.IsSuccessStatusCode)
{
MainForm.Instance.AddToActionsList($"Camera set to {modeName} mode successfully",Level.Success);
}
else
{
string error = await response.Content.ReadAsStringAsync();
MainForm.Instance.AddToActionsList($"Failed to set {modeName} mode: {response.StatusCode} - {error}",Level.ERROR);
}
}
catch (Exception ex)
{
MainForm.Instance.AddToActionsList($"Error changing to {modeName} mode: {ex.Message}",Level.ERROR);
}
}
public static async Task SetDayModeAsync()
{
await ChangeDayNightModeAsync(1);
}
public static async Task SetNightModeAsync()
{
await ChangeDayNightModeAsync(2);
}
public static async Task SetAutoModeAsync()
{
await ChangeDayNightModeAsync(0);
}
private static async Task<string> LoginAsync(HttpClient client, string user, string password)
{
var payload = new
{
userId = user,
userPassword = Sha256(password)
};
HttpResponseMessage response = await client.PostAsJsonAsync("/app/v1/login", payload);
response.EnsureSuccessStatusCode();
JsonElement json = await response.Content.ReadFromJsonAsync<JsonElement>();
return json.GetProperty("token").GetString();
}
private static string Sha256(string input)
{
using SHA256 sha = SHA256.Create();
byte[] bytes = sha.ComputeHash(Encoding.UTF8.GetBytes(input));
return BitConverter.ToString(bytes).Replace("-", "").ToLower();
}
}
}

View File

@@ -0,0 +1,83 @@
using LibVLCSharp.WinForms;
using System;
using System.Threading.Tasks;
namespace AiQ_GUI.Mobile_Tests
{
public static class MobileTests
{
public static async Task RunPreTestAsync()
{
await MobileAPI.CheckFirmwareAsync();// Check firmware version
VLC.Play(MainForm.Instance.VidView);// Test Live Video streaming
await Task.Delay(5000);
VLC.TakeSnapshot(MainForm.Instance.VidView);
if (!VLC.IsPLaying(MainForm.Instance.VidView))// Check Live Video streaming
{
MainForm.Instance.AddToActionsList("Live Video streaming test failed: Video is not playing.", Level.ERROR);
MainForm.Instance.AddLabelToPanel("Live Video = Not Playing", true);
}
else
{
MainForm.Instance.AddLabelToPanel("Live Video = Playing", false);
}
await TestDayNightMode();// Test day/night mode cycling
double dayLuminance = ImageProcessing.GetMeanLuminance(MainForm.Instance.DayImgPcbx.Image); // Calculates the Luminance
double nightLuminance = ImageProcessing.GetMeanLuminance(MainForm.Instance.NightImgPcbx.Image);
MainForm.Instance.DayImage.Text += $" - Luminance: {dayLuminance}%";
MainForm.Instance.NightImage.Text += $" - Luminance: {nightLuminance}%";
if (dayLuminance > nightLuminance)
{
MainForm.Instance.AddLabelToPanel("Day/Night Mode = Passed", false);
}
else
{
MainForm.Instance.AddToActionsList($"Day/Night mode test failed: Day luminance ({dayLuminance}%) is not greater than night luminance ({nightLuminance}%).", Level.ERROR);
MainForm.Instance.AddLabelToPanel("Day/Night Mode = Failed", true);
}
}
public static async Task RunFinalTestAsync()
{
// Placeholder for any final tests if needed in the future
//RunPreTestAsync().Wait();
//SSH.MobiletxtCheck(MainForm.Instance.CamOnTest.IP);// Verify mobile.txt presence
if (SSH.MobiletxtCheck(MainForm.Instance.CamOnTest.IP) == true)
{
MainForm.Instance.AddLabelToPanel("MobileSetup.sh = True", false);
}
else
{
MainForm.Instance.AddToActionsList("MobileSetup.sh test failed: mobile.txt not found on device.", Level.ERROR);
MainForm.Instance.AddLabelToPanel("MobileSetup.sh = False", true);
}
}
private static async Task TestDayNightMode()
{
await MobileAPI.SetDayModeAsync();// Set to Day mode
await Task.Delay(5000);
VLC.TakeSnapshot(MainForm.Instance.VidView, "Mobile_day_snapshot.png");// Take Day Image Snapshot
MainForm.Instance.DayImgPcbx.Image = System.Drawing.Image.FromFile(Path.Combine(LDS.MAVPath, "Mobile_day_snapshot.png"));// Display Day mode snapshot
await MobileAPI.SetNightModeAsync();// Set to Night mode
await Task.Delay(5000);
VLC.TakeSnapshot(MainForm.Instance.VidView, "Mobile_night_snapshot.png");// Take Night Image Snapshot
MainForm.Instance.NightImgPcbx.Image = System.Drawing.Image.FromFile(Path.Combine(LDS.MAVPath, "Mobile_night_snapshot.png"));// Display Night mode snapshot
}
}
}

43
MobileTests/VLC.cs Normal file
View File

@@ -0,0 +1,43 @@
using AiQ_GUI;
using Emgu.CV.Dai;
using LibVLCSharp.Shared;
using LibVLCSharp.WinForms;
using System.IO;
using System.Security.Policy;
internal class VLC
{
public static LibVLC libVLC;
static VLC()
{
// Ensure LibVLC is initialized before first use
Core.Initialize();
libVLC = new LibVLC();
}
public static string SnapshotPath = Path.Combine(LDS.MAVPath, "Mobile_ov_snapshot.png");
public static void Play(VideoView VLCVV)
{
var media = new Media(libVLC, $"rtsp://ADMIN:1234@{MainForm.Instance.CamOnTest.IP}:554/live/main", FromType.FromLocation);
VLCVV.MediaPlayer.Play(media);
}
public static bool IsPLaying(VideoView VLCVV)
{
return VLCVV.MediaPlayer.IsPlaying;
}
public static void TakeSnapshot(VideoView VLCVV, string filename = "Mobile_ov_snapshot.png")
{
string path = Path.Combine(LDS.MAVPath, filename);
VLCVV.MediaPlayer.TakeSnapshot(0, path, 1280, 720);
}
public static void Stop(VideoView VLCVV)
{
VLCVV.MediaPlayer.Stop();
}
}

View File

@@ -1,4 +1,6 @@
using System.Net; using OnvifDiscovery;
using OnvifDiscovery.Models;
using System.Net;
using System.Net.NetworkInformation; using System.Net.NetworkInformation;
using System.Net.Sockets; using System.Net.Sockets;
using System.Text; using System.Text;
@@ -71,15 +73,15 @@ namespace AiQ_GUI
} }
catch (TaskCanceledException ex) catch (TaskCanceledException ex)
{ {
return $"HTTP error calling {url}: {ex.Message}{Level.ERROR}"; return $"HTTP error calling {url}: {ex.Message}";
} }
catch (HttpRequestException ex) catch (HttpRequestException ex)
{ {
return $"HTTP error calling {url}: {ex.Message}{Level.ERROR}"; return $"HTTP error calling {url}: {ex.Message}";
} }
catch (Exception ex) catch (Exception ex)
{ {
return $"Unexpected error calling {url}: {ex.Message}{Level.ERROR}"; return $"Unexpected error calling {url}: {ex.Message}";
} }
} }
@@ -88,9 +90,15 @@ namespace AiQ_GUI
const int sendPort = 6666; const int sendPort = 6666;
const int receivePort = 6667; const int receivePort = 6667;
const int discoveryTimeoutMs = 1000; const int discoveryTimeoutMs = 1000;
IList<string> FoundCams = [];
byte[] discoveryPacket = [0x50, 0x4f, 0x4c, 0x4c, 0xaf, 0xb0, 0xb3, 0xb3, 0xb6, 0x01, 0xa8, 0xc0, 0x0b, 0x1a, 0x00, 0x00]; List<string> FoundCams = [];
HashSet<string> discoveredIPs = new(StringComparer.OrdinalIgnoreCase);
byte[] discoveryPacket =
[
0x50, 0x4f, 0x4c, 0x4c, 0xaf, 0xb0, 0xb3, 0xb3,
0xb6, 0x01, 0xa8, 0xc0, 0x0b, 0x1a, 0x00, 0x00
];
async Task SendAndListen(IPAddress localIp) async Task SendAndListen(IPAddress localIp)
{ {
@@ -101,12 +109,11 @@ namespace AiQ_GUI
sender.Send(discoveryPacket, discoveryPacket.Length); sender.Send(discoveryPacket, discoveryPacket.Length);
} }
using UdpClient receiver = new(receivePort); // Listen for replies on fixed port using UdpClient receiver = new(receivePort);
receiver.Client.ReceiveTimeout = discoveryTimeoutMs; receiver.Client.ReceiveTimeout = discoveryTimeoutMs;
DateTime timeout = DateTime.Now.AddMilliseconds(discoveryTimeoutMs); DateTime timeout = DateTime.Now.AddMilliseconds(discoveryTimeoutMs);
try
{
while (DateTime.Now < timeout) while (DateTime.Now < timeout)
{ {
if (receiver.Available > 0) if (receiver.Available > 0)
@@ -114,28 +121,30 @@ namespace AiQ_GUI
UdpReceiveResult result = await receiver.ReceiveAsync(); UdpReceiveResult result = await receiver.ReceiveAsync();
byte[] recvBuffer = result.Buffer; byte[] recvBuffer = result.Buffer;
if (recvBuffer.Length >= 52) // Safety check if (recvBuffer.Length >= 52)
{ {
byte[] ipBytes = recvBuffer.Skip(recvBuffer.Length - 52).Take(4).Reverse().ToArray(); byte[] ipBytes = recvBuffer
string ipToAdd = string.Join(".", ipBytes); .Skip(recvBuffer.Length - 52)
.Take(4)
.Reverse()
.ToArray();
if (!FoundCams.Contains(ipToAdd)) string ip = string.Join(".", ipBytes);
FoundCams.Add(ipToAdd);
if (discoveredIPs.Add(ip))
FoundCams.Add(ip);
} }
} }
await Task.Delay(50); // brief wait to allow data in await Task.Delay(50);
}
}
catch (SocketException ex) when (ex.SocketErrorCode == SocketError.TimedOut)
{
// No data received in time — normal case
} }
} }
// Get first IPv4 interface (non-loopback)
foreach (IPAddress ip in Dns.GetHostEntry(Dns.GetHostName()).AddressList) foreach (IPAddress ip in Dns.GetHostEntry(Dns.GetHostName()).AddressList)
{ {
if (ip.AddressFamily != AddressFamily.InterNetwork)
continue;
try try
{ {
await SendAndListen(ip); await SendAndListen(ip);
@@ -143,9 +152,31 @@ namespace AiQ_GUI
catch { } catch { }
} }
//ONVIF discovery
try
{
Discovery onvifDiscovery = new();
await foreach (DiscoveryDevice device in onvifDiscovery.DiscoverAsync(5))
{
string ip = device.Address;
// Already found via UDP → skip
if (!discoveredIPs.Add(ip))
continue;
// ONVIF-only camera
FoundCams.Add($"{ip} - Onvif");
}
}
catch
{
}
return FoundCams; return FoundCams;
} }
// Ping to make sure devices are connected to the network, be aware it isn't consistant across subnets. // Ping to make sure devices are connected to the network, be aware it isn't consistant across subnets.
public async static Task<bool> PingIP(string ipAddress) public async static Task<bool> PingIP(string ipAddress)
{ {

View File

@@ -19,7 +19,7 @@ namespace AiQ_GUI
} }
catch (WebDriverTimeoutException) catch (WebDriverTimeoutException)
{ {
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}"); 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.", Level.ERROR);
} }
} }
@@ -71,11 +71,11 @@ namespace AiQ_GUI
loginBtn.Click(); loginBtn.Click();
MainForm.Instance.AddToActionsList($"Switched user and logged in.{Level.Success}"); MainForm.Instance.AddToActionsList($"Switched user and logged in.", Level.Success);
} }
catch (Exception ex) catch (Exception ex)
{ {
MainForm.Instance.AddToActionsList($"SwitchUser failed: {Level.ERROR}" + ex.Message); MainForm.Instance.AddToActionsList($"SwitchUser failed:" + ex.Message,Level.ERROR);
} }
} }
@@ -99,15 +99,13 @@ namespace AiQ_GUI
ClickElementByID(elementID, driver, false); ClickElementByID(elementID, driver, false);
} }
MainForm.Instance.AddToActionsList($"Could not click {Level.WARNING}" + elementID); MainForm.Instance.AddToActionsList($"Could not click " + elementID, Level.WARNING);
} }
} }
// Initialises and opens a ChromeDriver with specific options // Initialises and opens a ChromeDriver with specific options
public static ChromeDriver OpenDriver() public static ChromeDriver OpenDriver()
{ {
string tempProfile = null;
try try
{ {
ChromeOptions options = new(); ChromeOptions options = new();
@@ -121,7 +119,7 @@ namespace AiQ_GUI
"--disable-features=BrowserAddPersonFeature,InterestFeedContentSuggestions"); "--disable-features=BrowserAddPersonFeature,InterestFeedContentSuggestions");
// Use a unique temporary profile to avoid conflicts // Use a unique temporary profile to avoid conflicts
tempProfile = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString()); string tempProfile = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString());
options.AddArguments($"--user-data-dir={tempProfile}"); options.AddArguments($"--user-data-dir={tempProfile}");
// manual driver path // manual driver path
@@ -136,7 +134,7 @@ namespace AiQ_GUI
} }
catch (Exception ex) catch (Exception ex)
{ {
MainForm.Instance.AddToActionsList($"Failed to create ChromeDriver: {Level.ERROR}" + ex.Message); MainForm.Instance.AddToActionsList($"Failed to create ChromeDriver: " + ex.Message, Level.ERROR);
throw; throw;
} }
} }

View File

@@ -7,8 +7,9 @@ namespace AiQ_GUI
internal class SoakTest internal class SoakTest
{ {
// Main soak test loop: Randomise dropdowns, run luminance test every hour, power cycle at 7am // Main soak test loop: Randomise dropdowns, run luminance test every hour, power cycle at 7am
public static async Task StartSoak(Camera CamInfo, CheckBox CkBx, CancellationToken token) public static async Task StartSoak(Camera CamInfo, CancellationTokenSource CTS)
{ {
CancellationToken token = CTS.Token;
if (CamInfo.Serial == "N/A") if (CamInfo.Serial == "N/A")
CamInfo.Serial = "UNKNOWN"; // If serial is not set, set it to UNKNOWN. Cannot have N/A in file names. CamInfo.Serial = "UNKNOWN"; // If serial is not set, set it to UNKNOWN. Cannot have N/A in file names.
@@ -18,6 +19,7 @@ namespace AiQ_GUI
try try
{ {
driver = Selenium.OpenDriver(); driver = Selenium.OpenDriver();
Logging.LogMessage("----- Soak test started -----", SoakLogFile);
await Task.Delay(1000); // Small delay to ensure driver is ready await Task.Delay(1000); // Small delay to ensure driver is ready
// Keep retrying until connected or cancelled // Keep retrying until connected or cancelled
@@ -35,7 +37,7 @@ namespace AiQ_GUI
catch (Exception ex) catch (Exception ex)
{ {
SoakError($"Initial connection failed: {ex.Message}", SoakLogFile, CamInfo.CheckBox); SoakError($"Initial connection failed: {ex.Message}", SoakLogFile, CamInfo.CheckBox);
MainForm.Instance.AddToActionsList($"[{CamInfo.IP}] Initial connection failed: {Level.ERROR}{ex.Message}"); MainForm.Instance.AddToActionsList($"[{CamInfo.IP}] Initial connection failed:{ex.Message}", Level.ERROR);
// 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);
@@ -50,7 +52,7 @@ namespace AiQ_GUI
} }
catch (Exception ex) catch (Exception ex)
{ {
SoakError($"Failed to get element IDs: {Level.ERROR} {ex.Message}", SoakLogFile, CamInfo.CheckBox); SoakError($"Failed to get element IDs: {ex.Message}", SoakLogFile, CamInfo.CheckBox);
return; return;
} }
@@ -58,6 +60,16 @@ namespace AiQ_GUI
while (!token.IsCancellationRequested) while (!token.IsCancellationRequested)
{ {
try
{
string TheString = "Selenium string: " + driver.Manage().Window.Size; // Fails out if Window doesn't exist.
}
catch
{
CTS.Cancel();
continue;
}
int currentHour = DateTime.Now.Hour; int currentHour = DateTime.Now.Hour;
// At 7am, power cycle the camera // At 7am, power cycle the camera
@@ -72,7 +84,7 @@ namespace AiQ_GUI
// Retry ping until camera responds or cancelled // Retry ping until camera responds or cancelled
while (!await Network.PingIP(CamInfo.IP) && !token.IsCancellationRequested) while (!await Network.PingIP(CamInfo.IP) && !token.IsCancellationRequested)
{ {
SoakError($"Camera did not respond after restart.{Level.ERROR}", SoakLogFile, CamInfo.CheckBox); 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 +98,7 @@ namespace AiQ_GUI
} }
catch (Exception ex) catch (Exception ex)
{ {
SoakError($"Error during power cycle: {Level.ERROR} {ex.Message}", SoakLogFile, CamInfo.CheckBox); SoakError($"Error during power cycle: {ex.Message}", SoakLogFile, CamInfo.CheckBox);
} }
} }
@@ -100,7 +112,7 @@ namespace AiQ_GUI
} }
catch (Exception ex) catch (Exception ex)
{ {
SoakError($"ImageCheck failed: {Level.ERROR}{Level.ERROR} {ex.Message}", SoakLogFile, CamInfo.CheckBox); SoakError($"ImageCheck failed: {ex.Message}", SoakLogFile, CamInfo.CheckBox);
} }
lastHour = currentHour; lastHour = currentHour;
} }
@@ -119,7 +131,7 @@ namespace AiQ_GUI
} }
catch (Exception ex) catch (Exception ex)
{ {
SoakError($"ChangeRandomDropdown failed: {Level.WARNING} {ex.Message}", SoakLogFile, CamInfo.CheckBox); SoakError($"ChangeRandomDropdown failed: {ex.Message}", SoakLogFile, CamInfo.CheckBox);
} }
try try
@@ -188,7 +200,7 @@ namespace AiQ_GUI
File.Delete(SoakTestPath); File.Delete(SoakTestPath);
} }
else else
MainForm.Instance.AddToActionsList($"Failed to link or delete PDFs {Level.ERROR}"); MainForm.Instance.AddToActionsList($"Failed to link or delete PDFs ", Level.ERROR);
} }
catch (Exception ex) catch (Exception ex)
{ {
@@ -228,7 +240,7 @@ namespace AiQ_GUI
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]}{Level.WARNING}"); MainForm.Instance.AddToActionsList($"Dark image is null for {controlType} at setting {SettingMinMax[1]}", Level.WARNING);
return; return;
} }