33 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
30bd2fe73c AddToAction List colours 2025-12-02 12:59:40 +00:00
4c624d7e29 V4.6 2025-12-02 11:02:24 +00:00
e29d104d47 V4.5 2025-11-26 14:39:07 +00:00
791cee91d2 V4.4 with modified god mode and 211 at end of soak test. 2025-11-11 14:17:31 +00:00
00b225a3db V4.4 merged 2025-11-11 13:58:29 +00:00
e94a1f1f5c Find cams timeout extended 2025-11-11 13:49:42 +00:00
a91c3b846f V4.4 Selenium fix 2025-11-11 13:18:02 +00:00
42a4778555 V4.3 release 2025-11-04 14:29:38 +00:00
3d71ab7e9b V4.3 changes and merge 2025-11-04 13:43:20 +00:00
78c440ab76 Updated other uses of Internal Config to GLOBAL--FlexiApplication 2025-11-04 13:16:07 +00:00
dd8e87258f V4.3 Changes - Bradley B 2025-11-04 12:56:16 +00:00
1c89cf2847 V4.2 with stats and vaxtor settings changes. Fixed merge problems 2025-10-29 16:17:59 +00:00
f95e958ffb V4.1 with excel stats changes 2025-10-28 16:13:05 +00:00
95fb5d6ab8 V4.1 with excel stats changes 2025-10-28 16:12:53 +00:00
c18514802c Merge branch 'master' of https://mavportal.com:3000/BradleyRelyea/AiQ_GUI 2025-10-28 12:35:55 +00:00
f78e44903b V4.1 changes BR 2025-10-28 12:35:28 +00:00
ed121fa3db GUI Version 4.1 CHanges are GB sticker and Updated API call from Internal Config to GLOBAL--FlexiApplication. 2025-10-27 11:14:20 +00:00
aa28a43347 V4.0 2025-10-21 15:46:28 +01:00
cfe8a57a9d Merged ready for V4 2025-10-21 14:28:45 +01:00
b59d023887 In FlexiAPI SetVaxtorMinMaxPlate() Has been added + Other small changes 2025-10-21 14:04:59 +01:00
a2f86625be Final Changes to Stats Excel - This has been tested via Fake Cam Test all scenearios and published and tested 2025-10-20 10:31:58 +01:00
f4081515be This Commit contains all the changes for the new feature for S.L in regards to ExcelStats of the GUI 2025-10-17 12:06:36 +01:00
34 changed files with 2646 additions and 1332 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);
}
}
}

883
AiQ_GUI.Designer.cs generated

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

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

View File

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

View File

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

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

View File

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

View File

@@ -9,7 +9,24 @@
{
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
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
double LowerThreshold = ExpVorI * 0.8;
@@ -18,8 +35,17 @@
// Check median is within 20% of the expected value
if (medianVorI < LowerThreshold || medianVorI > UpperThreshold)
{
lblVorI.Text += $" Median away from excepted {ExpVorI}{VormA}";
lblVorI.ForeColor = Color.Red;
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;
});
return;
}
@@ -35,17 +61,35 @@
// If there are no single channels outside the threshold then green, else red
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)
{
lblVorI.Text += "error on " + string.Join(", ", outOfRangeVoltageChannels); // Join all problem channels together to present on form
lblVorI.ForeColor = Color.Red;
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;
});
}
}
catch (Exception ex)
{
MainForm.Instance.AddToActionsList($"Error checking LEDs: {ex.Message}");
MainForm.Instance.AddToActionsList($"Error checking LEDs: {ex.Message}", Level.ERROR);
}
}
}

View File

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

View File

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

View File

@@ -9,7 +9,7 @@ namespace AiQ_GUI
public const string SSHPassword = "mavPA$$";
public const string SSHPasswordNEW = "#!mavsoftMESA19"; // New password for SSH after last one got leaked
private static SshClient SshConnect(string IPAddress)
public static SshClient SshConnect(string IPAddress)
{
SshClient client = new(IPAddress, SSHUsername, SSHPasswordNEW);
try
@@ -25,7 +25,7 @@ namespace AiQ_GUI
}
catch (Exception Ex)
{
MainForm.Instance.AddToActionsList($"SSH connection failed: {Ex.Message}. Check password or network.");
MainForm.Instance.AddToActionsList($"SSH connection failed: {Ex.Message}. Check password or network. ", Level.WARNING);
}
return null;
@@ -40,6 +40,13 @@ namespace AiQ_GUI
{
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
{
Data.packages = GetVaxtorPackages(client);
@@ -75,12 +82,9 @@ namespace AiQ_GUI
}
catch (Exception ex)
{
MainForm.Instance.AddToActionsList($"SSH connection failed: {ex.Message}. Check password or network.");
MainForm.Instance.AddToActionsList($"SSH connection failed: {ex.Message}. Check password or network. ", Level.WARNING);
}
string LogMssg = string.Join(" | ", typeof(SSHData).GetProperties().Select(p => $"{p.Name}: {p.GetValue(Data)}"));
Logging.LogMessage(LogMssg); // Log all of Data
return Data;
}
@@ -170,7 +174,7 @@ namespace AiQ_GUI
}
catch (Exception ex)
{
MainForm.Instance.AddToActionsList($"SSH connection failed: {ex.Message}. Check password or network.");
MainForm.Instance.AddToActionsList($"SSH connection failed: {ex.Message}. Check password or network. ", Level.WARNING);
}
return (string.Empty, string.Empty);
@@ -234,6 +238,14 @@ namespace AiQ_GUI
const double Deviation = 20.0; // ±20GB
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";
if (Math.Abs(GoodSize - currentSize) < Deviation)
@@ -280,7 +292,8 @@ namespace AiQ_GUI
{
try
{
if (string.IsNullOrWhiteSpace(rootSize)) return 0;
if (string.IsNullOrWhiteSpace(rootSize))
return 0;
// Extract value & unit
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
{
MainForm.Instance.AddToActionsList($"Block device {device} not found.");
MainForm.Instance.AddToActionsList($"Block device {device} not found. ", Level.WARNING);
return false;
}
@@ -365,6 +378,24 @@ namespace AiQ_GUI
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)
{
@@ -378,7 +409,7 @@ namespace AiQ_GUI
if (checkDevice.Result.Trim().Length > 1) // Device not found
{
MainForm.Instance.AddToActionsList($"Cannot sync files to disk. Replied: {checkDevice.Result}. DO NOT TURN OFF, GET SUPERVISOR");
MainForm.Instance.AddToActionsList($"Cannot sync files to disk. Replied: {checkDevice.Result}. DO NOT TURN OFF, GET SUPERVISOR", Level.ERROR);
return;
}
@@ -387,7 +418,7 @@ namespace AiQ_GUI
}
catch (Exception ex)
{
MainForm.Instance.AddToActionsList($"Cannot sync becuase: {ex.Message}. DO NOT TURN OFF, GET SUPERVISOR");
MainForm.Instance.AddToActionsList($"Cannot sync becuase: {ex.Message}. DO NOT TURN OFF, GET SUPERVISOR", Level.ERROR);
}
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

10
LDS.cs
View File

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

View File

@@ -43,7 +43,7 @@ namespace AiQ_GUI
}
catch (Exception ex)
{
Debug.WriteLine($"Error logging message: {ex.Message}");
MessageBox.Show($"Error logging message: {ex.Message}");
}
}
}

View File

@@ -4,14 +4,32 @@ namespace AiQ_GUI
{
class Access
{
const string connString = @"Provider=Microsoft.ACE.OLEDB.12.0;Data Source=G:\Shared drives\MAV Production GUI's\AiQ\GUI's\AiQ_Final_Test.accdb;Persist Security Info=False;OLE DB Services=-1;";
public const string connString =
@"Provider=Microsoft.ACE.OLEDB.12.0;Data Source=G:\Shared drives\MAV Production GUI's\AiQ\GUI's\AiQ_Final_Test.accdb;Persist Security Info=False;OLE DB Services=-1;";
// Reads camera model numbers and descriptions from the database, sorts them alphabetically by model number (except "AB12CD", which appears last), and formats each entry as "ModelNumber - Description".
public static string[] ReadCamTypes()
// 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);
// Attempt to open the database
try
{
conn.Open();
@@ -22,31 +40,36 @@ namespace AiQ_GUI
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 OleDbDataReader reader = cmd.ExecuteReader();
// Read all models from the selected table
while (reader.Read())
{
// Extract each model number and description, using empty string if null
string modelNumber = reader["ModelNumber"] as string ?? string.Empty;
string description = reader["Description"] as string ?? string.Empty;
modelTuples.Add(Tuple.Create(modelNumber.Trim(), description.Trim()));
}
// Sort: push "AB12CD" to the bottom, then sort remaining items alphabetically
IOrderedEnumerable<Tuple<string, string>> sorted = modelTuples.OrderBy(t => t.Item1.Equals("AB12CD", StringComparison.OrdinalIgnoreCase) ? 1 : 0)
.ThenBy(t => t.Item1, StringComparer.OrdinalIgnoreCase);
// Sort models, pushing AB12CD to the bottom
var sorted = modelTuples
.OrderBy(t => t.Item1.Equals("AB12CD", StringComparison.OrdinalIgnoreCase) ? 1 : 0)
.ThenBy(t => t.Item1, StringComparer.OrdinalIgnoreCase);
conn.Close();
// Format the sorted tuples as "ModelNumber - Description" strings and return as array
// Format for combo box display
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()
{
using OleDbConnection conn = new(connString);
try
{
conn.Open();
@@ -57,24 +80,28 @@ namespace AiQ_GUI
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 OleDbDataReader reader = cmd.ExecuteReader();
// UniversalData is expected to contain a single row
reader.Read();
UniversalData.ExpFlexiVer = Convert.ToString(reader["FlexiVersion"]);
UniversalData.ExpFlexiRev = Convert.ToString(reader["FlexiRevision"]);
UniversalData.WonwooFirmware = Convert.ToString(reader["WonwooFirmware"]);
UniversalData.LatestVersion = Convert.ToString(reader["AiQGUIVersion"]);
UniversalData.PowerConsumption = Convert.ToInt16(reader["PowerConsumption"]);
UniversalData.LicencingServerURL = Convert.ToString(reader["LicencingServerURL"]);
conn.Close();
UniversalData.SRZFirmware = Convert.ToString(reader["SRZFirmware"]);
}
// Knowing the model number on test, this function reads the database and populates the Camera class with the values.
public static void ReadModelRow(string ModelOnTest)
// Populates CameraAccessInfo dynamically based on available columns
public static void ReadModelRow(string camType, string ModelOnTest)
{
using OleDbConnection conn = new(connString);
try
{
conn.Open();
@@ -85,25 +112,105 @@ namespace AiQ_GUI
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 OleDbDataReader reader = cmd.ExecuteReader();
reader.Read();
cmd.Parameters.AddWithValue("?", ModelOnTest);
// Populate the CameraAccessInfo class with the values from the database
CameraAccessInfo.Processor = Convert.ToString(reader["Processor"]);
CameraAccessInfo.VaxtorLic = Convert.ToBoolean(reader["Vaxtor"]);
CameraAccessInfo.HardwareExtras = Convert.ToString(reader["HardwareExtras"]);
CameraAccessInfo.PowerType = Convert.ToString(reader["PowerType"]);
CameraAccessInfo.LED_V = Convert.ToDouble(reader["LEDVoltage"]);
CameraAccessInfo.LED_I = Convert.ToInt32(reader["LEDCurrent"]);
CameraAccessInfo.SpreadsheetID = Convert.ToString(reader["SSID"]);
conn.Close();
using OleDbDataReader reader = cmd.ExecuteReader();
// No matching model found
if (!reader.Read())
return;
// Populate CameraAccessInfo only if columns exist
CameraAccessInfo.Processor =
HasColumn(reader, "Processor") ? Convert.ToString(reader["Processor"]) : string.Empty;
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)
{
Stats([TypeOfTest], modelNumber);
}
public static void Stats(string[] TypeOfTest, string modelNumber)
{
using OleDbConnection conn = new(connString);
try
{
conn.Open();
foreach (string type in TypeOfTest)
{
string query =
$"UPDATE AiQ SET [{type}] = [{type}] + 1 WHERE [ModelNumber] = ?";
using OleDbCommand cmd = new(query, conn);
cmd.Parameters.AddWithValue("?", modelNumber);
int rowsAffected = cmd.ExecuteNonQuery();
if (rowsAffected == 0)
MainForm.Instance.AddToActionsList($"No rows affected for {modelNumber}", Level.ERROR);
}
}
catch
{
MainForm.Instance.AddToActionsList(
"Could not access Access in Google Drive. Is it running?", Level.WARNING);
}
}
public static void StatsDiags(string redDiagLabels, string RhTxBxActionsText, string ModelNumber)
{
using OleDbConnection conn = new(connString);
conn.Open();
// Replace null or empty values
string redVal = string.IsNullOrWhiteSpace(redDiagLabels) ? "-" : redDiagLabels;
string actVal = string.IsNullOrWhiteSpace(RhTxBxActionsText) ? "-" : RhTxBxActionsText;
string model = string.IsNullOrWhiteSpace(ModelNumber) ? "-" : ModelNumber;
const string sql =
@"INSERT INTO DiagsStats ([Date], [Model], [Red Diags Labels], [RhTxBxActions Contents])
VALUES (?, ?, ?, ?)";
using OleDbCommand cmd = new(sql, conn);
cmd.Parameters.Add(new OleDbParameter
{
OleDbType = OleDbType.Date,
Value = DateTime.Now
});
cmd.Parameters.AddWithValue("?", model);
cmd.Parameters.AddWithValue("?", redVal);
cmd.Parameters.AddWithValue("?", actVal);
int rows = cmd.ExecuteNonQuery();
if (rows == 0)
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 static string ExpFlexiVer { get; set; } = string.Empty;
@@ -112,9 +219,9 @@ namespace AiQ_GUI
public static string LatestVersion { get; set; } = string.Empty;
public static int PowerConsumption { get; set; } = 0;
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 static string Processor { get; set; } = string.Empty;

View File

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

289
Microsoft/StatsExcel.cs Normal file
View File

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

View File

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

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.Sockets;
using System.Text;
@@ -10,7 +12,7 @@ namespace AiQ_GUI
public static HttpClient? SingleHTTPClient;
public static HttpClient Client => SingleHTTPClient ?? throw new InvalidOperationException("Client not initialized.");
public static void Initialize(string username, string password)
public async static void Initialize(string username, string password)
{
HttpClientHandler handler = new()
{
@@ -23,6 +25,19 @@ namespace AiQ_GUI
{
Timeout = TimeSpan.FromSeconds(20)
};
bool UP = false;
int i = 0;
while (!UP)
{
UP = await PingIP("10.10.10.137");
await Task.Delay(500);
if (i >= 5)
return; // Try 5 times max
i++;
}
}
// Handles get and post to the camera API
@@ -74,9 +89,16 @@ namespace AiQ_GUI
{
const int sendPort = 6666;
const int receivePort = 6667;
IList<string> FoundCams = [];
const int discoveryTimeoutMs = 1000;
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)
{
@@ -87,41 +109,42 @@ namespace AiQ_GUI
sender.Send(discoveryPacket, discoveryPacket.Length);
}
using UdpClient receiver = new(receivePort); // Listen for replies on fixed port
receiver.Client.ReceiveTimeout = 750;
using UdpClient receiver = new(receivePort);
receiver.Client.ReceiveTimeout = discoveryTimeoutMs;
DateTime timeout = DateTime.Now.AddMilliseconds(750);
try
DateTime timeout = DateTime.Now.AddMilliseconds(discoveryTimeoutMs);
while (DateTime.Now < timeout)
{
while (DateTime.Now < timeout)
if (receiver.Available > 0)
{
if (receiver.Available > 0)
UdpReceiveResult result = await receiver.ReceiveAsync();
byte[] recvBuffer = result.Buffer;
if (recvBuffer.Length >= 52)
{
UdpReceiveResult result = await receiver.ReceiveAsync();
byte[] recvBuffer = result.Buffer;
byte[] ipBytes = recvBuffer
.Skip(recvBuffer.Length - 52)
.Take(4)
.Reverse()
.ToArray();
if (recvBuffer.Length >= 52) // Safety check
{
byte[] ipBytes = recvBuffer.Skip(recvBuffer.Length - 52).Take(4).Reverse().ToArray();
string ipToAdd = string.Join(".", ipBytes);
string ip = string.Join(".", ipBytes);
if (!FoundCams.Contains(ipToAdd))
FoundCams.Add(ipToAdd);
}
if (discoveredIPs.Add(ip))
FoundCams.Add(ip);
}
await Task.Delay(50); // brief wait to allow data in
}
}
catch (SocketException ex) when (ex.SocketErrorCode == SocketError.TimedOut)
{
// No data received in time — normal case
await Task.Delay(50);
}
}
// Get first IPv4 interface (non-loopback)
foreach (IPAddress ip in Dns.GetHostEntry(Dns.GetHostName()).AddressList)
{
if (ip.AddressFamily != AddressFamily.InterNetwork)
continue;
try
{
await SendAndListen(ip);
@@ -129,9 +152,31 @@ namespace AiQ_GUI
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;
}
// 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)
{
@@ -143,9 +188,9 @@ namespace AiQ_GUI
PingReply reply = await myPing.SendPingAsync(ipAddress, 1000); // Timeout is 1s
return reply.Status == IPStatus.Success;
}
catch (PingException ex) { MainForm.Instance.AddToActionsList($"PingException: Unable to ping {ipAddress}. Reason: {ex.Message}"); }
catch (SocketException ex) { MainForm.Instance.AddToActionsList($"SocketException: Network error while pinging {ipAddress}. Reason: {ex.Message}"); }
catch (Exception ex) { MainForm.Instance.AddToActionsList($"Unexpected error while pinging {ipAddress}: {ex.Message}"); }
catch (PingException ex) { MainForm.Instance.AddToActionsList($"PingException: Unable to ping {ipAddress}. Reason: {ex.Message}{Level.WARNING}"); }
catch (SocketException ex) { MainForm.Instance.AddToActionsList($"SocketException: Network error while pinging {ipAddress}. Reason: {ex.Message}{Level.ERROR}"); }
catch (Exception ex) { MainForm.Instance.AddToActionsList($"Unexpected error while pinging {ipAddress}: {ex.Message}{Level.ERROR}"); }
}
return false;

13
PDF.cs
View File

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

View File

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

View File

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