Files
AiQ_GUI/AiQ_GUI.cs

1727 lines
74 KiB
C#
Raw Normal View History

2025-09-02 15:32:24 +01:00
using Newtonsoft.Json;
using System.ComponentModel;
using System.Diagnostics;
using System.Reflection;
namespace AiQ_GUI
{
public partial class MainForm : Form
{
// Classes
LocalDataStore localDataStore = new();
Diags DiagsAPI = new();
VaxtorLic VaxtorLicResp = new();
Versions Vers = new();
readonly Camera CamOnTest = new();
SSHData sshData = new();
private List<Camera> soakCameraList = [];
private List<CancellationTokenSource> soakCtsList = [];
private List<Task> soakTasks = [];
// Colours
public static readonly Color BtnColour = Color.FromArgb(70, 65, 80);
public static readonly Color TxBxColour = Color.FromArgb(53, 51, 64);
[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
public static MainForm? Instance { get; private set; }
public MainForm()
{
InitializeComponent();
Instance = this;
}
private async void AiQGUI_Load(object sender, EventArgs e)
{
Stopwatch stopwatch = Stopwatch.StartNew();
Task? closeProcessesTask = Windows.CloseProcesses(); // Fire and forget closing other apps
Windows.UpdateFirewall();
Task UniDataTask = Task.Run(() => Access.ReadUniData()); // Get universal data
Task<LocalDataStore> LDSWAIT = Task.Run(() => LDS.GetLDS()); // Get and deserialise LDS.json
Task<string> guiVerTask = Task.Run(() => GUIUpdate.FindGUIVersion()); // Get GUI Version
Network.Initialize("admin", "admin"); // Initialise HTTP client
if (await Network.PingIP("8.8.8.8")) // Ping to check if we're online
{
if (!GoogleAPI.Setup())
AddToActionsList("Cannot setup Google API");
}
else
{
Flags.Offline = true;
BtnStartTest.Text = "Offline Mode";
}
GUIUpdate.GUIVerShort = await guiVerTask; // Guess the GUI version will be first to finish
this.Name = "AiQ GUI V" + GUIUpdate.GUIVerShort;
LblGUIVers.Text += GUIUpdate.GUIVerShort;
await UniDataTask; // Have to wait for expected GUI version to compare to.
Task<string[]> modelListTask = Task.Run(() => Access.ReadCamTypes());// Get all model numbers
GUIUpdate.UpdateGUI(); // Check if a GUI update is available
string[] ArrayOfModels = await modelListTask;
if (ArrayOfModels != null) // Returns null if no models found or file doesn't exists
CbBxCameraType.Items.AddRange(await modelListTask); // Fill in the model number drop down
// Load local data store
localDataStore = await LDSWAIT;
Logging.LogMessage("Opening GUI"); // Done after LDS to make sure directory exists.
if (localDataStore == null)
{
AddToActionsList("Could not deserialise LDS.json please help!");
return;
}
Task CheckHWOnline = PingCheck(); // Async check all hardware is online
PopulateUIWithLDS(localDataStore); // Update fields that depend on LDS
CbBxCameraType.Text = localDataStore.LastModel; // Set last model that was tested into combobox
BtnSave.BackColor = BtnSave.Enabled ? Color.ForestGreen : BtnSave.BackColor; // Sets the colour of the save button depending on if it is enabled.
BtnRefreshUnix_Click(sender, e); // Reset timestamp
if (RegexCache.FlexiVerRegex().IsMatch(UniversalData.ExpFlexiVer)) // Update Flexi version from universal data
TxBxFlexiVer.Text = UniversalData.ExpFlexiVer;
await CheckHWOnline;
Flags.Start = false;
stopwatch.Stop();
Debug.WriteLine("RunTime " + stopwatch.Elapsed.ToString(@"hh\:mm\:ss\.ff"));
}
private void PopulateUIWithLDS(LocalDataStore lds)
{
CbBxUserName.Text = lds.User;
TxBxPsuIP.Text = PSU.PSUIP = lds.PSUIP;
TxBxEzIP.Text = Ez.PoEPowerIP = lds.EzIP;
TxBxZebraIP.Text = Printer.ZebraIP = lds.ZebraIP;
TxBxTestTubeIP.Text = TestTube.TTPiPicoIP = lds.TestTubeIP;
CbBxShutter.SelectedIndex = lds.Shutter;
CbBxIris.SelectedIndex = lds.Iris;
CbBxGain.SelectedIndex = lds.Gain;
if (lds.User == "Bradley")
BtnTest.Visible = true;
TxBxCheckValid(TxBxPsuIP); // Set save button color if valid
}
private async void MainForm_Shown(object sender, EventArgs e) // Done on show as all UI elements are loaded in properly for find cams to use
{
BtnFindCams_Click(sender, e);
CheckPrintCapable(); // Check if the print buttons can be enabled
TestStartConditions();
await Task.Delay(1000); // Delay for UI to catch up
if (LblPSUPing.ForeColor == Color.ForestGreen) // Check state of PSU if it is connected.
Task.Run(() => PSU.DisplayState(PSU.PSUIP));
}
// ***** Test buttons *****
private async void BtnStartTest_Click(object sender, EventArgs e)
{
// Show user test has started
BtnStartTest.BackColor = Color.Orange;
BtnStartTest.Text = "Test underway";
BtnPreTest.Enabled = BtnStartTest.Enabled = false; // Disable buttons to stop user rnning multiple tests at the same time.
Logging.LogMessage("Final Test Started");
if (LblTestTubePing.Text == "❌") // No test tube so test like an IQ
{
string LEDreply = await FlexiAPI.APIHTTPLED(CamOnTest.IP, LEDPOWER.SAFE); // Set LED's to safe (0x0E) to help with eye safety and trim check.
if (!LEDreply.Contains("Power levels set successfully"))
AddToActionsList($"LED level could not be set: {LEDreply}");
}
else if (!await TestTube.CheckInTestTube(CamOnTest.IP)) // Sets LED's to medium power after checking it is in the test tube
await TestFailed(BtnStartTest, "Camera not in test tube");
Task VisCheck = Helper.VisualCheck(BtnStartTest);
if (!await FlexiAPI.ZoomModules("1F40", CamOnTest.IP)) // Zoom to 8000 (1F40h) at the same time.
await TestFailed(BtnStartTest, "Could not zoom modules to 8000");
if (!await FlexiAPI.SetZoomLockOn(CamOnTest.IP))
Helper.RestartApp();
await Task.Delay(1000); // Without sleep it kept failing the factory reset as camera modules were not ready yet
await CameraModules.FactoryResetModules(CamOnTest.IP); // Reset both modules and double check
string VISCAReply = await FlexiAPI.APIHTTPVISCA(CamOnTest.IP, "8101043903FF", true); // Manual mode to be able to manipulate the SIG settings.
if (VISCAReply != "9041FF9051FF")
AddToActionsList("Couldn't set to manual mode");
await CameraModules.SetSIG(CbBxShutter, CbBxIris, CbBxGain, CamOnTest.IP); // Set SIG according to the drop downs in settings for a good picture ready for image check
await ImageProcessing.ImageCheck(PicBxOV, PicBxIRF2, PicBxIRF16, LblIRImageF2, LblIRImageF16, CamOnTest); // Populates the picture boxes and checks iris changes
await VisCheck; // Before changing UI elements wait for user to finish Visual check
TabImagesandSettings.SelectedIndex = 2; // Swaps to the images tab
this.Refresh(); // Show user things are happening by displaying images taken
// TODO - Force expire sighting.
Task Wait = Task.Delay(5000); // Wait for 5 seconds to allow the camera to zoom in, set settings and capture some plates before auto trim
// While waiting do the SSH tasks.
sshData = SSH.CollectSSHData(CamOnTest.IP); // SSH into camera to get Vaxtor packages, filesystem size and if tailscale is installed.
await SSH.CheckFSSize(CamOnTest.IP, LblFilesystemSize, sshData); // Check Filesystem size is between 100GB & 150GB
Helper.DCPowerCheck(LblDC); // If the camera is DC powered check it is within limits
if (CameraAccessInfo.HardwareExtras.Contains("4G")) // If it is a router camera then test the router.
{
LblRouter.Visible = true;
if (Router.CheckRouter(Router.GetRouterInfo()))
LblRouter.Text += "OK";
else
LblRouter.Text += "Error with router";
}
await Wait; // Finished to 5s wait
2025-09-16 10:42:51 +01:00
await FlexiAPI.SetTrim(CamOnTest.IP, LblTestTubePing.Text); // Auto trims the cameras, some plates should have been captured in the meantime
2025-09-02 15:32:24 +01:00
if (!await FlexiAPI.ZoomModules("0000", CamOnTest.IP)) // Zoom to full wide
await TestFailed(BtnStartTest, "Could not zoom modules to full wide");
await Task.Delay(1000); // Wait to be sure cameras are zoomed out.
await CameraModules.FactoryResetModules(CamOnTest.IP); // Reset both modules and double check
if (LblTestTubePing.Text == "❌") // Set LED's to MID in prep for diagnostics API
{
string LEDreply = await FlexiAPI.APIHTTPLED(CamOnTest.IP, LEDPOWER.MID); // Set LED's to medium (0x30)
if (!LEDreply.Contains("Power levels set successfully"))
AddToActionsList($"LED level could not be set: {LEDreply}");
}
DateTime PCTime = DateTime.Now; // Grab PC time as close to the API as possible to pass onto PDF later
await CheckDiagsAPIPt1(); // Requests, deserialises and checks the diagnostics API is correct
await CheckDiagsAPIPt2(); // For only final test parts
// Check module has gone to default config
CameraModules.CheckCamModule(DiagsAPI.IRmodule, LblIRModule); // IR
CameraModules.CheckCamModule(DiagsAPI.OVmodule, LblOVModule); // OV
// Check voltage and current are OK.
LED.CheckLEDs(DiagsAPI.LedVoltage, LblLEDV, "V", CameraAccessInfo.LED_V); // Voltage
LED.CheckLEDs(DiagsAPI.LedCurrent, LblLEDI, "mA", CameraAccessInfo.LED_I); // Current
this.Refresh(); // Make sure all labels are updated before checking them
// If there are any actions identified then fail the test.
// If any labels are red then fail. Only labels in panel so can foreach on labels not controls
if (RhTxBxActions.Text.Length > 2 || PnlLbls.Controls.OfType<Label>().Any(c => c.ForeColor == Color.Red) == true)
await TestFailed(BtnStartTest);// If approved then pass otherwise GUI would have restarted before getting to TestPassed.
await TestPassed(PCTime);
}
private async void BtnPreTest_Click(object sender, EventArgs e)
{
// Show user test has started
BtnPreTest.BackColor = Color.Orange;
BtnPreTest.Text = "Pre-Test underway";
BtnPreTest.Enabled = BtnStartTest.Enabled = false; // Disable buttons to stop user rnning multiple tests at the same time.
Logging.LogMessage("Pre Test Started");
if (!await FlexiAPI.SetZoomLockOn(CamOnTest.IP))
Helper.RestartApp();
string LEDreply = await FlexiAPI.APIHTTPLED(CamOnTest.IP, LEDPOWER.MID); // Set LED's to medium (0x30)
if (!LEDreply.Contains("Power levels set successfully"))
AddToActionsList($"LED level could not be set: {LEDreply}");
await CameraModules.FactoryResetModules(CamOnTest.IP); // Reset both modules and double check
sshData = SSH.CollectSSHData(CamOnTest.IP); // SSH into camera to get Vaxtor packages, filesystem size and if tailscale is installed.
await SSH.CheckFSSize(CamOnTest.IP, LblFilesystemSize, sshData); // Check Filesystem size is between 100GB & 150GB
Helper.DCPowerCheck(LblDC); // If the camera is DC powered check it is within limits
// Requests, deserialises and checks the diagnostics API is correct
await CheckDiagsAPIPt1();
// Check module has gone to default config
CameraModules.CheckCamModule(DiagsAPI.IRmodule, LblIRModule); // IR
CameraModules.CheckCamModule(DiagsAPI.OVmodule, LblOVModule); // OV
// Check voltage and current are OK.
LED.CheckLEDs(DiagsAPI.LedVoltage, LblLEDV, "V", CameraAccessInfo.LED_V); // Voltage
LED.CheckLEDs(DiagsAPI.LedCurrent, LblLEDI, "mA", CameraAccessInfo.LED_I); // Current
this.Refresh(); // Make sure all labels are updated before checking them
// If there are any actions identified then fail the test.
// If any labels are red then fail. Only labels in panel so can foreach on labels not controls
if (RhTxBxActions.Text.Length < 2 && PnlLbls.Controls.OfType<Label>().Any(c => c.ForeColor == Color.Red) == false)
{
// If camera already has a model or serial then ask if it is new
if (RegexCache.SerialRegex().IsMatch(DiagsAPI.serialNumber) || RegexCache.ModelRegex().IsMatch(DiagsAPI.modelNumber))
{
if (await DisplayQuestion($"Would you like to allocate a serial number to this camera?"))
await AllocateSerial();
else if (GoogleAPI.UpdateSpreadSheetRePreTest(CameraAccessInfo.SpreadsheetID, Vers) != "OK") // If rerun might be different values so update SS
AddToActionsList("Failed to write to spreadsheet, please check manually");
}
else // No serial or model so allocate one
await AllocateSerial();
if (RhTxBxActions.Text.Length < 2 && PnlLbls.Controls.OfType<Label>().Any(c => c.ForeColor == Color.Red) == false)
await PreTestPassed();
}
else
await PreTestFailed();
}
// ***** Pass/Fails *****
private async Task TestPassed(DateTime PCTime)
{
// Updates Vaxtor versions, licenses and unticks WIP
string err = GoogleAPI.UpdateSpreadSheetFinalTest(CameraAccessInfo.SpreadsheetID, DiagsAPI, sshData, CamOnTest.RMANum);
if (err != string.Empty) // If there is an error message, display it
AddToActionsList("Failed to write to spreadsheet " + err);
// Purge camera of all reads
await FlexiAPI.APIHTTPRequest("/api/purge-all", CamOnTest.IP);
if (await DisplayQuestion("Do you want to set this camera to 211 and God mode?"))
{
// Turn off God mode
string[,] GOD_JSON = { { "propGodMode", "false" } };
string IntConf = await FlexiAPI.HTTP_Update("Internal Config", CamOnTest.IP, GOD_JSON);
if (!IntConf.Contains("\"propGodMode\": {\"value\": \"false\", \"datatype\": \"boolean\"},"))
AddToActionsList("Could not turn off God mode");
Thread Thr211 = new(async () =>
{
if (!await FlexiAPI.ChangeNetwork211(CamOnTest.IP)) // Change camera IP to 192.168.1.211. Waits for camera to come back.
AddToActionsList("Could not find camera at 192.168.1.211. Please check manually");
});
Thr211.IsBackground = true;
Thr211.Start();
}
this.Refresh(); // To make sure all labels are up to date before reading them
string fulltestvalues = $"GUI Version = {GUIUpdate.GUIVerShort}\n" +
string.Join("\n", PnlLbls.Controls
.OfType<Label>()
.Where(lbl => lbl.Visible) // Only include visible labels
.OrderBy(lbl => lbl.Location.Y) // Sort from top to bottom
.Select(lbl => lbl.Text)); // Get the text of each label
fulltestvalues += Helper.GetOverriddenActions(PnlLbls, RhTxBxActions);
// TODO make sure serial number is in CamOnTest before making PDF
// Serial number is either what came out of the camera under RMA or updated above as new serial number from next empty row in spreadsheet
PDF.CreateFinalTestReport(CamOnTest, CbBxUserName.Text, fulltestvalues, PCTime);
// Indicators to the user the test has passed
BtnStartTest.BackColor = Color.ForestGreen;
BtnStartTest.Text = "Test Passed";
PnlQuestion.Visible = false; // just in case this came from an override
Logging.LogMessage("Final Test Passed");
}
public async Task TestFailed(Button Btn, string ErrMssg)
{
AddToActionsList(ErrMssg);
await TestFailed(Btn);
}
private async Task TestFailed(Button Btn)
{
// Indicators to the user the test has failed
Btn.BackColor = Color.Maroon;
Btn.Text = "Test Failed";
if (await DisplayQuestion("Test failed, appeal?" + Environment.NewLine + "See Actions textbox for details."))
{
if (CbBxUserName.Text == "Bradley")
{
await TestPassed(DateTime.Now); // Debugging
return;
}
BtnYes.Visible = BtnNo.Visible = false; // Remove buttons
LblQuestion.Text = "Please ask for approval";
PnlQuestion.Visible = true;
this.Refresh(); // To make sure all labels are up to date before reading them
// Joins the actions box to any red labels to use as a full failed text
string FullFailureValues = RhTxBxActions.Text + Environment.NewLine +
string.Join(Environment.NewLine, PnlLbls.Controls
.OfType<Label>()
.Where(lbl => lbl.ForeColor == Color.Red) // Only include red labels
.Select(lbl => lbl.Text)); // Extract text
Logging.LogErrorMessage(FullFailureValues);
IList<IList<object>> values = GoogleAPI.service.Spreadsheets.Values.Get(GoogleAPI.spreadsheetId_ModelInfo, "'Approval'!A1:A").Execute().Values;
if (values?.Count > 0)
{
int nextRow = values.Count + 1;
List<object> oblistA = [CbBxUserName.Text];
GoogleAPI.WriteToSS(oblistA, "'Approval'!A" + nextRow, GoogleAPI.spreadsheetId_ModelInfo);
List<object> oblistCD = [CamOnTest.Model, FullFailureValues];
GoogleAPI.WriteToSS(oblistCD, "'Approval'!C" + nextRow + ":D" + nextRow, GoogleAPI.spreadsheetId_ModelInfo);
//await Teams.SendMssg(Convert.ToString(nextRow), CbBxUserName.Text);
GoogleAPI.EmailApproval(Convert.ToString(nextRow), CbBxUserName.Text);
string Approved = "";
while (Approved != "TRUE")
{
await Task.Delay(1000);
values = GoogleAPI.service.Spreadsheets.Values.Get(GoogleAPI.spreadsheetId_ModelInfo, "'Approval'!B" + nextRow).Execute().Values;
if (values?.Count > 0)
Approved = Convert.ToString(values[0][0]);
else
{
await Logging.LogErrorMessage("No values returned from Approval spreadsheet for row " + nextRow);
Helper.RestartApp();
}
}
}
else
Helper.RestartApp();
// Sets back to continue test
PnlQuestion.Visible = false;
BtnYes.Visible = BtnNo.Visible = true;
Btn.BackColor = Color.Orange;
Btn.Text = "Test underway";
}
else
Helper.RestartApp();
}
private async Task PreTestPassed()
{
BtnPreTest.BackColor = Color.ForestGreen; // Indicators to the user the test has passed
BtnPreTest.Text = "Pre Test Passed";
PnlQuestion.Visible = true;
BtnNo.Visible = false;
Logging.LogMessage("Pre Test Passed");
if (await DisplayQuestion("Test passed, restart?"))
Helper.RestartApp();
}
private async Task PreTestFailed()
{
BtnPreTest.BackColor = Color.Maroon; // Indicators to the user the test has failed
BtnPreTest.Text = "Test Failed";
PnlQuestion.Visible = true;
BtnNo.Visible = false;
Logging.LogMessage("Pre Test Failed");
if (await DisplayQuestion("Test failed, restart?" + Environment.NewLine + "See Actions textbox for details."))
Helper.RestartApp();
}
// ***** Testing functions *****
private async Task CheckDiagsAPIPt1() // Parts done on pre and final test
{
DiagsAPI = await FlexiAPI.GetDiagnostics(CamOnTest.IP); // Diagnostic API request
lblFlexiVer.Text += DiagsAPI.FlexiVersion; // Check Flexi Version
if (DiagsAPI.FlexiVersion == UniversalData.ExpFlexiVer)
{
lblFlexiVer.ForeColor = Color.LightGreen;
}
else
{
lblFlexiVer.Text += " Expected = " + UniversalData.ExpFlexiVer;
lblFlexiVer.ForeColor = Color.Red;
}
lblFlexiRev.Text += DiagsAPI.FlexiRevision; // Check Flexi Revision
if (DiagsAPI.FlexiRevision == UniversalData.ExpFlexiRev)
{
lblFlexiRev.ForeColor = Color.LightGreen;
}
else
{
lblFlexiRev.Text += " Expected = " + UniversalData.ExpFlexiRev;
lblFlexiRev.ForeColor = Color.Red;
}
lblMac.Text += DiagsAPI.MAC; // Display MAC
if (RegexCache.MACRegexNVIDIA().IsMatch(DiagsAPI.MAC)) // Checks it is in the right format and is a NVIDIA registered MAC address
{
lblMac.ForeColor = Color.LightGreen;
}
else if (RegexCache.MACRegex().IsMatch(DiagsAPI.MAC)) // Is a valid MAC, but not NVIDIA
{
lblMac.ForeColor = Color.Red;
AddToActionsList($"{DiagsAPI.MAC} not recognised as NVIDIA MAC address");
}
else
lblMac.ForeColor = Color.Red;
// Check timestamp
DateTime dateTime = new(1970, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc);
dateTime = dateTime.AddSeconds(DiagsAPI.timeStamp).ToLocalTime();
lbltimestamp.Text += dateTime;
long timediff = DateTimeOffset.UtcNow.ToUnixTimeSeconds() - DiagsAPI.timeStamp;
if (timediff > 10) // Over 10 seconds ago
{
lbltimestamp.Text += $" Time behind by {timediff}s";
lbltimestamp.ForeColor = Color.Red;
}
else if (timediff < 0) // Time is in the future.
{
lbltimestamp.Text += $" Time is in the future by {Math.Abs(timediff)}s";
lbltimestamp.ForeColor = Color.Red;
}
else
lbltimestamp.ForeColor = Color.LightGreen;
lblTemp.Text += DiagsAPI.IntTemperature + "°C"; // Diagnostic API request
if (DiagsAPI.IntTemperature > 20 && DiagsAPI.IntTemperature < 70)
lblTemp.ForeColor = Color.LightGreen;
else
lblTemp.ForeColor = Color.Red;
lblZoomLock.Text += DiagsAPI.zoomLock;
if (DiagsAPI.zoomLock == true)
{
if (DiagsAPI.IRmodule.zoom == DiagsAPI.OVmodule.zoom) // Checks if zoomlock is doing what is says it should
{
lblZoomLock.ForeColor = Color.LightGreen;
}
else
{
lblZoomLock.Text += $" Zoomlock on but {DiagsAPI.IRmodule.zoom}≠{DiagsAPI.OVmodule.zoom}";
lblZoomLock.ForeColor = Color.Red;
}
}
else
lblZoomLock.ForeColor = Color.Red;
}
private async Task CheckDiagsAPIPt2() // Parts only done on final test
{
if (RhTxBxActions.Text.Contains("Error reading JSON")) // If failed to deserialise in part 1
return;
try // In case serial or model are blank
{
lblModel.Text += DiagsAPI.modelNumber; // Update labels with serial and model
lblSerial.Text += DiagsAPI.serialNumber;
if (RegexCache.SerialRegex().IsMatch(DiagsAPI.serialNumber) && RegexCache.ModelRegex().IsMatch(DiagsAPI.modelNumber))
{
lblSerial.ForeColor = lblModel.ForeColor = Color.LightGreen; // Set both to green
if (DiagsAPI.modelNumber != CamOnTest.Model)
{
AddToActionsList("Model number in camera doesn't match what has been selected");
lblModel.ForeColor = Color.Red;
}
else if (!GoogleAPI.CheckWIP(DiagsAPI.serialNumber, CameraAccessInfo.SpreadsheetID)) // Check WIP column in serial number register, if not ticked then RMA
{
CamOnTest.RMANum = GoogleAPI.CheckRMANum(DiagsAPI.serialNumber, DiagsAPI.modelNumber); // Corrected by qualifying with the type name
if (CamOnTest.RMANum == 0) // Couldn't find RMA num in spreadsheet
{
CamOnTest.RMANum = Convert.ToInt32(await DisplayInput("What is the RMA number?"));
if (CamOnTest.RMANum == -1) // Means they chose the 'I don't know' option
await TestFailed(BtnStartTest, "Please get RMA number from operations team before continuing");
}
// Found RMA num and want to verify it with user
else if (!await DisplayQuestion($"Is {CamOnTest.RMANum} the RMA Number?")) // '!' because if its not the right RMA number let the user to it manually
{
CamOnTest.RMANum = Convert.ToInt32(await DisplayInput("What is the RMA number?"));
if (CamOnTest.RMANum == -1) // Means they chose the 'I don't know' option
await TestFailed(BtnStartTest, "Please get RMA number from operations team before continuing");
}
}
}
else
{
AddToActionsList("Camera has not been given a valid serial and model number, suggest you run through pre test again and check serial number register for any issues.");
}
}
catch { }
// Check licenses
List<string> licensesOnCam = []; // Temporary list for licenses on camera
if (DiagsAPI.licenses.saf1)
licensesOnCam.Add("SAF1");
if (DiagsAPI.licenses.saf2)
licensesOnCam.Add("SAF2");
if (DiagsAPI.licenses.saf3)
licensesOnCam.Add("SAF3");
if (DiagsAPI.licenses.saf4)
licensesOnCam.Add("SAF4");
if (DiagsAPI.licenses.audit)
licensesOnCam.Add("Audit");
if (DiagsAPI.licenses.stream)
licensesOnCam.Add("Stream");
if (sshData.tailscale)
licensesOnCam.Add("Tailscale");
if (licensesOnCam.Count == 0) // No licenses found
lblLic.Text += "None";
else if (licensesOnCam.Count != 0) // Join them comma and space seperated for displaying
lblLic.Text += string.Join(", ", licensesOnCam);
lblLic.ForeColor = Color.LightGreen;
double CPUround = Math.Round(DiagsAPI.CPUusage); // Check CPU usage isn't near max
LblCPUusage.Text += CPUround + "%";
if (CPUround < 98 && CPUround > 50)
{
LblCPUusage.ForeColor = Color.LightGreen;
}
else if (CPUround <= 50)
{
LblCPUusage.Text += " Unexpectedly low CPU usage";
LblCPUusage.ForeColor = Color.Red;
}
else if (CPUround >= 98)
{
LblCPUusage.Text += " Unexpectedly high CPU usage";
LblCPUusage.ForeColor = Color.Red;
}
// Check Vaxtor if it doesn't need or have license OR has and wants one then pass
if (CameraAccessInfo.VaxtorLic == false && DiagsAPI.licenses.raptorKeyID == "Not Licensed" || CameraAccessInfo.VaxtorLic == true && DiagsAPI.licenses.raptorKeyID != "Not Licensed")
{
lblVaxtor.Text += DiagsAPI.licenses.raptorKeyID;
lblVaxtor.ForeColor = Color.LightGreen;
}
else if (await DisplayQuestion("Was this camera licensed manually?"))
{
string ProdcutKeyID = await DisplayInput("What is the Key ID?", false);
if (RegexCache.VaxtorRegex().IsMatch(ProdcutKeyID)) // Means they chose the 'I don't know' option or isn't valid Key ID
await TestFailed(BtnStartTest, "Please get a valid Vaxtor Product Key before continuing");
DiagsAPI.licenses.raptorKeyID = TxBxProductKey.Text;
lblVaxtor.Text += DiagsAPI.licenses.raptorKeyID;
lblVaxtor.ForeColor = Color.LightGreen;
}
else // Should have license but doesn't OR shouldn't have but does + any other unforseen circumstance then fail
{
lblVaxtor.Text += DiagsAPI.licenses.raptorKeyID;
lblVaxtor.ForeColor = Color.Red;
}
// Check trim is within 10% both horizontally and vertically, from auto set done earlier in the test
lblTrim.Text += "H: " + DiagsAPI.trim[0] + " V: " + DiagsAPI.trim[1];
// Offset accounted for in the SetTrim function, so value should be close to 0,0.
int HMax = 96; // 5% of 1920 each way = ±96
int VMax = 54; // 5% of 1080 each way = ±54
if (Math.Abs(DiagsAPI.trim[0]) <= HMax && Math.Abs(DiagsAPI.trim[1]) <= VMax)
lblTrim.ForeColor = Color.LightGreen;
else
lblTrim.ForeColor = Color.Red;
}
private async Task AllocateSerial()
{
// Update the serial number register with new cameras details
// Cam description is in model drop down 6 digit model num + 3 for " - "
string NewSerial = GoogleAPI.UpdateSpreadSheetPreTest(CameraAccessInfo.SpreadsheetID, Vers, CbBxCameraType.Text.Substring(9), CamOnTest.Model);
if (NewSerial.Contains("ERROR"))
{
AddToActionsList(NewSerial + Environment.NewLine + "Please remove any information that was put into the serial number mistake, change the camera model/serial to 'N/A' and retry the test.");
return;
}
else if (NewSerial == "Last serial number not found")
{
AddToActionsList("Last serial number not found in spreadsheet. Please check spreadsheet is in correct format before retrying.");
return;
}
// Set serial and model into camera
string[,] TEST_JSON = { { "propSerialNumber", NewSerial }, { "propMavModelNumber", CamOnTest.Model } };
string JSONResponse = await FlexiAPI.HTTP_Update("Internal Config", CamOnTest.IP, TEST_JSON);
if (!JSONResponse.Contains(NewSerial) || !JSONResponse.Contains(CamOnTest.Model))
{
AddToActionsList("Could not set model or serial numbers into camera.");
await PreTestFailed();
}
DiagsAPI.modelNumber = CamOnTest.Model; // Update Diags and labels
DiagsAPI.serialNumber = NewSerial;
lblModel.Text += DiagsAPI.modelNumber;
lblModel.ForeColor = lblSerial.ForeColor = Color.LightGreen;
lblSerial.Text += DiagsAPI.serialNumber;
Printer.ZebraIP = localDataStore.ZebraIP;
Printer.PrintGBLbl(); // Print GB label
Printer.PrintSerialLbl(CamOnTest.Model, NewSerial, CameraAccessInfo.Processor); // Print model/serial label
}
// ***** Top right buttons *****
private void BtnClose_Click(object sender, EventArgs e)
{
// Save user settings in LDS for next time if values are valid
if (CbBxUserName.Text.Length > 2 && CbBxCameraType.Text.Length > 6)
{
localDataStore.User = CbBxUserName.Text;
localDataStore.LastModel = CbBxCameraType.Text;
LDS.SetLDS(localDataStore);
}
Environment.Exit(0); // Close app and dispose of everything, a very abrupt method
}
private void BtnMin_Click(object sender, EventArgs e)
{
this.WindowState = FormWindowState.Minimized;
}
private void BtnRestart_Click(object sender, EventArgs e)
{
Helper.RestartApp(); // Restart abruptly don't worry about anything else going on
}
// ***** Allows moving GUI by grab and dragging *****
private void Form1_MouseDown(object sender, MouseEventArgs e)
{
if (e.Button == MouseButtons.Left)
{
Helper.ReleaseCapture();
Helper.SendMessage(Handle, 0xA1, 0x2, 0);
}
}
// ***** Button actions *****
// Find cameras, sending broadcast and receiving simultaneously
private async void BtnFindCams_Click(object sender, EventArgs e)
{
CbBxFoundCams.Text = "Searching";
2025-09-16 10:42:51 +01:00
BtnFindCams.Enabled = BtnSetAll211.Enabled = BtnSoak.Enabled = BtnSet211.Enabled = BtnSetGodMode.Enabled = BtnUploadBlob.Enabled = SetGodModeAll.Enabled = BtnFactoryDefault.Enabled = false;
BtnSetGodMode.BackColor = BtnUploadBlob.BackColor = BtnFactoryDefault.BackColor = BtnSetAll211.BackColor = BtnColour;
2025-09-02 15:32:24 +01:00
CbBxFoundCams.Items.Clear();
soakCameraList.Clear();
// Deletes all the checkboxes in TabSoak
List<CheckBox> checkBoxes = TabSoak.Controls.OfType<CheckBox>().Except([CkBxTickAll]).ToList();
foreach (CheckBox? cb in checkBoxes)
{
TabSoak.Controls.Remove(cb);
cb.Dispose();
}
IList<string> FoundCams = await Network.SearchForCams();
foreach (string Cam in FoundCams)
{
CbBxFoundCams.Items.Add(Cam); // Update combo box list
if (TabImagesandSettings.SelectedIndex == 3)
{
Camera NewCam = await Helper.NewCamera(Cam);
if (NewCam != null)
soakCameraList.Add(NewCam);
}
}
// Order soakCameraList by serial
soakCameraList.Sort((a, b) => string.Compare(a.Serial, b.Serial, StringComparison.OrdinalIgnoreCase));
int YLoc = 74;
foreach (Camera soakInfo in soakCameraList)
{
TabSoak.Controls.Add(SoakTest.MakeNewCheckbox(soakInfo, YLoc));
YLoc += 24;
}
int cameraCount = CbBxFoundCams.Items.Count;
CbBxFoundCams.Text = cameraCount > 0 ? $"{cameraCount} Camera{(cameraCount == 1 ? "" : "s")} Found" : "No Cameras Found";
2025-09-16 10:42:51 +01:00
BtnFindCams.Enabled = BtnSetAll211.Enabled = SetGodModeAll.Enabled = BtnSoak.Enabled = BtnUploadBlob.Enabled = BtnFactoryDefault.Enabled = true;
2025-09-02 15:32:24 +01:00
}
private void BtnYes_Click(object sender, EventArgs e)
{
Flags.Yes = true; // Set the Yes flag to true
}
private void BtnNo_Click(object sender, EventArgs e)
{
Flags.No = true;
}
private void CmBxCameraType_SelectedIndexChanged(object sender, EventArgs e)
{
CamOnTest.Model = CbBxCameraType.Text.Substring(0, 6); // Model on test is the first 6 characters of the textbox
Access.ReadModelRow(CamOnTest.Model); // Fill in all info about current model number
TestStartConditions();
}
private void CmBoBoxUserName_TextChanged(object sender, EventArgs e)
{
TestStartConditions();
}
private void CmBoFoundCams_TextChanged(object sender, EventArgs e)
{
timerTypeIP.Enabled = false;
timerTypeIP.Enabled = true;
}
private async void CbBxFoundCams_SelectedIndexChanged(object sender, EventArgs e) // Also services the timerTypeIP_Tick
{
timerTypeIP.Enabled = false; // Stop this triggering again
CamOnTest.DevPass = TxBxOutput.Text = RhTxBxActions.Text = ""; // Blank password & Clear actions box as it is now a different camera
if (RegexCache.RegexIPPattern().IsMatch(CbBxFoundCams.Text)) // Check IP address is valid
{
if (!await Network.PingIP(CbBxFoundCams.Text))
{
CbBxFoundCams.BackColor = Color.Red;
return;
}
CamOnTest.IP = CbBxFoundCams.Text; // Set the IP address under test
CbBxFoundCams.BackColor = BtnColour;
BtnSecret.Enabled = true;
Vers = await FlexiAPI.GetVersions(CamOnTest.IP);
// Wont be filled out before the pre test but needed for final test
if (RegexCache.SerialRegex().IsMatch(CamOnTest.Serial) && RegexCache.ModelRegex().IsMatch(CamOnTest.Model))
{
CamOnTest.Serial = Vers.Serial; // Set the serial number from the versions API
CamOnTest.Model = Vers.Model; // Set the serial number from the versions API
}
if (Vers == null) // If failed to get versions then return. Flexi most likely not running yet.
{
AddToActionsList("Failed to get API from camera. Flexi not running yet or not an AiQ");
return;
}
Lics.DisplayDevPassword(Vers, CamOnTest); // Generate and display secret for use later
string networkConfigText = await FlexiAPI.ProcessNetworkConfig(CamOnTest.IP);
BtnSet211.Text = string.IsNullOrEmpty(networkConfigText) ? "Set to 211" : networkConfigText;
ShowToolTip(BtnSecret); // Set dev password to Tooltip and clipboard
}
else if (CbBxFoundCams.Text.Contains("Found"))
{
CbBxFoundCams.BackColor = BtnColour;
}
else
{
CbBxFoundCams.BackColor = Color.Red;
BtnSecret.Enabled = BtnOpenWebpage.Enabled = BtnSet211.Enabled = BtnSetGodMode.Enabled = false;
}
TestStartConditions();
}
private void btnPsuOn_Click(object sender, EventArgs e)
{
PSU.PSU_ON(PSU.PSUIP);
}
private void btnPsuOff_Click(object sender, EventArgs e)
{
PSU.PSU_OFF(PSU.PSUIP);
}
private void btnPsu48V_Click(object sender, EventArgs e)
{
PSU.PSU_OFF(PSU.PSUIP);
PSU.SendDataPsu("V1 48", PSU.PSUIP);
PSU.SendDataPsu("I1 1.5", PSU.PSUIP);
}
private void btnPsu12V_Click(object sender, EventArgs e)
{
PSU.PSU_OFF(PSU.PSUIP);
PSU.SendDataPsu("V1 12", PSU.PSUIP);
PSU.SendDataPsu("I1 3.5", PSU.PSUIP);
}
private async void BtnSetGodMode_Click(object sender, EventArgs e)
{
2025-09-16 10:42:51 +01:00
BtnSetGodMode.BackColor = BtnColour;
2025-09-02 15:32:24 +01:00
bool isGodModeCurrentlyOn = BtnSetGodMode.Text.Contains("On");
string newGodModeValue = isGodModeCurrentlyOn ? "true" : "false";
string[,] GOD_JSON = { { "propGodMode", newGodModeValue } };
try
{
await FlexiAPI.HTTP_Update("Internal Config", CamOnTest.IP, GOD_JSON);
BtnSetGodMode.Text = newGodModeValue == "true" ? "Set God Mode Off" : "Set God Mode On";
BtnSetGodMode.BackColor = Color.Green;
}
catch (Exception ex)
{
AddToActionsList($"Failed to set God mode for camera {CamOnTest.IP}. Reason: {ex.Message}");
}
}
// ***** Helper functions *****
public void AddToActionsList(string Mssg, bool IsErr = true)
{
if (IsErr)
Logging.LogErrorMessage(Mssg);
else
Logging.LogMessage(Mssg);
RhTxBxActions.AppendText(Mssg + Environment.NewLine);
RhTxBxActions.SelectionStart = RhTxBxActions.Text.Length;
RhTxBxActions.ScrollToCaret();
}
private async void TestStartConditions()
{
if (Flags.Start)
return; // If on GUI load don't evaluate
RhTxBxActions.Text = ""; // Clear actions box
bool TSC = true;
if (Flags.Offline == true) // If start found we are offline
{
if (await Network.PingIP("8.8.8.8")) // Ping to find if we are online
{
Flags.Offline = false; // Ping succeeded
CbBxCameraType.Enabled = true;
}
else
TSC = SetInvalid("Offline Mode, could not connect to the internet."); // Ping failed
}
// Camera IP selected
if (CbBxFoundCams.BackColor != BtnColour || CbBxFoundCams.Text.Contains("Found"))
TSC = SetInvalid("Select camera IP address.");
else
BtnOpenWebpage.Enabled = BtnSet211.Enabled = BtnSetGodMode.Enabled = BtnZoom8000.Enabled = BtnZoomWide.Enabled = true; // Allow user to go to camera webpage & change DHCP/211
// Name chosen
if (CbBxUserName.Text == "Select Operator to Begin Test" || CbBxUserName.Text.Length < 2)
TSC = SetInvalid("Select Username.");
// Model number selected
if (CbBxCameraType.SelectedIndex == -1)
TSC = SetInvalid("Select Model number.");
// Settings IP addresses filled in
if (BtnSave.BackColor != Color.ForestGreen)
TSC = SetInvalid("Fill in hardware accessies IP's & click 'Save & Check'.");
// All hardware accessories are on the network
//if (PanelSettings.Controls.OfType<Label>().Any(label => label.Text.Contains("❌")))
if (LblZebraPing.Text == "❌" || LblEzPing.Text == "❌" || LblPSUPing.Text == "❌") // Testtube not connected then will do 2.7m check
TSC = SetInvalid("Hardware accessories not found on network, see red X's in settings tab for details. Fill in IP addresses and press Save & Check.");
// Shutter, Iris and Gain selected
if (CbBxShutter.SelectedIndex == -1 || CbBxIris.SelectedIndex == -1 || CbBxGain.SelectedIndex == -1)
TSC = SetInvalid("Shutter, iris and gain drop downs not filled in.");
if (TSC)
BtnStartTest.Text = "Start Final test";
BtnStartTest.Enabled = BtnPreTest.Enabled = BtnLicVaxtor.Enabled = TSC; // Licensing Vaxtor requires all the info
}
private bool SetInvalid(string reason)
{
RhTxBxActions.Text += reason + Environment.NewLine;
return false;
}
public async Task<bool> DisplayOK(string QuestionString)
{
BtnNo.Visible = false;
BtnYes.Text = "OK";
bool YesNo = await DisplayQuestion(QuestionString);
BtnNo.Visible = true;
BtnYes.Text = "Yes";
return YesNo;
}
public async Task<bool> DisplayQuestion(string QuestionString)
{
Flags.Yes = Flags.No = false; // Clear flags
PnlQuestion.Visible = true;
LblQuestion.Text = QuestionString; // Show question box and give them the right to appeal
while (!Flags.Yes && !Flags.No)
await Task.Delay(100); // Check flags every 100ms
PnlQuestion.Visible = false;
if (Flags.Yes)
{
Flags.Yes = false; // Clear flag
return true;
}
else
{
Flags.No = false; // Clear flag
return false;
}
}
// Display the input panel for either RMA number input (default) or Vaxtor Key ID input
private async Task<string> DisplayInput(string Request, bool RMAorVaxtor = true)
{
RMANumBox.Visible = BtnRerun.Visible = RMAorVaxtor;
TxBxProductKey.Visible = !RMAorVaxtor;
LblRMA.Text = Request;
PnlInputValue.Visible = true;
while (Flags.Done == false) // Waiting for user input in RMA Num panel
{
await Task.Delay(100); // Check every 100ms
}
Flags.Done = false; // Reset flag
PnlInputValue.Visible = false;
if (RMAorVaxtor)
return RMANumBox.Value.ToString();
else
return TxBxProductKey.Text;
}
// ***** Settings menu *****
// Save all current user settings to disk then check devices are online
private async void BtnSave_Click(object sender, EventArgs e)
{
BtnSave.Enabled = false;
localDataStore.PSUIP = TxBxPsuIP.Text;
localDataStore.EzIP = TxBxEzIP.Text;
localDataStore.ZebraIP = TxBxZebraIP.Text;
localDataStore.TestTubeIP = TxBxTestTubeIP.Text;
localDataStore.Shutter = CbBxShutter.SelectedIndex;
localDataStore.Iris = CbBxIris.SelectedIndex;
localDataStore.Gain = CbBxGain.SelectedIndex;
LDS.SetLDS(localDataStore);
BtnSave.BackColor = Color.ForestGreen;
PSU.PSUIP = localDataStore.PSUIP;
Printer.ZebraIP = localDataStore.ZebraIP;
Ez.PoEPowerIP = localDataStore.EzIP;
TestTube.TTPiPicoIP = localDataStore.TestTubeIP;
await PingCheck();
TestStartConditions();
CheckPrintCapable();
BtnSave.Enabled = true;
}
// Revert settings to last saved
private async void BtnCancel_Click(object sender, EventArgs e)
{
TxBxPsuIP.Text = localDataStore.PSUIP;
TxBxEzIP.Text = localDataStore.EzIP;
TxBxZebraIP.Text = localDataStore.ZebraIP;
TxBxTestTubeIP.Text = localDataStore.TestTubeIP;
CbBxShutter.SelectedIndex = localDataStore.Shutter;
CbBxIris.SelectedIndex = localDataStore.Iris;
CbBxGain.SelectedIndex = localDataStore.Gain;
await PingCheck();
TestStartConditions();
}
private void BtnFirewall_Click(object sender, EventArgs e)
{
Properties.Settings.Default.FirstRun = true;
Properties.Settings.Default.Save();
Windows.UpdateFirewall();
}
private void BtnAdminStart_Click(object sender, EventArgs e)
{
string ExeLoc = Assembly.GetEntryAssembly().Location.Replace("dll", "exe"); // Sometimes trys to open the dll instead of exe
Windows.StartAsAdmin(ExeLoc);
}
// Flips between setting camera to 211 and DHCP
private async void BtnSet211_Click(object sender, EventArgs e)
{
if (BtnSet211.Text == "Set to 211")
{
bool Online = await FlexiAPI.ChangeNetwork211(CamOnTest.IP); // Change camera IP to 192.168.1.211 and hardware reboot. Waits for camera to come back for 50s.
if (!Online)
AddToActionsList("Could not find camera at 192.168.1.211. Please check manually");
else
BtnSet211.Text = "Set to DHCP";
}
else if (BtnSet211.Text == "Set to DHCP")
{
await FlexiAPI.ChangeNetworkToDHCP(CamOnTest.IP);
BtnSet211.Text = "Set to 211";
}
}
private async void BtnSetAll211_Click(object sender, EventArgs e)
{
BtnSetAll211.BackColor = BtnColour; // Reset button colour
if (await DisplayQuestion("Are you sure? This will set all checked cameras to 211."))
{
string[,] Network_JSON = { { "propDHCP", "false" }, { "propHost", "192.168.1.211" }, { "propNetmask", "255.255.255.0" }, { "propGateway", "192.168.1.1" } };
foreach (Camera SCL in soakCameraList.Where(c => c.IsChecked)) // only checked cameras
{
try
{
Network.Initialize("developer", SCL.DevPass); // Ensure network is initialized to the right camera
await FlexiAPI.HTTP_Update("GLOBAL--NetworkConfig", SCL.IP, Network_JSON);
Instance.AddToActionsList($"Setting 211 for camera {SCL.IP}", false);
}
catch (Exception ex)
{
AddToActionsList("Failed to set all cameras to 211. Reason: " + ex.Message); // In case non AiQ's get caught up
}
}
BtnSetAll211.BackColor = Color.Green;
}
}
private async void SetGodModeAll_Click(object sender, EventArgs e)
{
SetGodModeAll.BackColor = BtnColour; // Reset button colour
bool isGodModeCurrentlyOn = SetGodModeAll.Text.Contains("On");
string newGodModeValue = isGodModeCurrentlyOn ? "true" : "false";
string[,] GOD_JSON = { { "propGodMode", newGodModeValue } };
if (await DisplayQuestion("Are you sure? This will toggle God mode for all checked cameras."))
{
foreach (Camera SCL in soakCameraList.Where(c => c.IsChecked)) // only checked cameras
{
try
{
Network.Initialize("developer", SCL.DevPass); // Ensure network is initialized to the right camera
string RESP = await FlexiAPI.HTTP_Update("Internal Config", SCL.IP, GOD_JSON);
Instance.AddToActionsList($"Setting God mode for camera {SCL.IP} to {newGodModeValue}", false);
}
catch (Exception ex)
{
AddToActionsList($"Failed to set God mode for camera {SCL.IP}. Reason: {ex.Message}");
}
}
// Update the button text and flash green only if successful for all
SetGodModeAll.Text = newGodModeValue == "true" ? "Set God Mode Off" : "Set God Mode On";
SetGodModeAll.BackColor = Color.Green;
}
}
private void TxBxZebraIP_TextChanged(object sender, EventArgs e)
{
TxBxCheckValid(TxBxZebraIP);
}
private void TxBxPSUIP_TextChanged(object sender, EventArgs e)
{
TxBxCheckValid(TxBxPsuIP);
}
private void TxBxEzIP_TextChanged(object sender, EventArgs e)
{
TxBxCheckValid(TxBxEzIP);
}
private void TxBxAccBoardIP_TextChanged(object sender, EventArgs e)
{
TxBxCheckValid(TxBxTestTubeIP);
}
// Start a thread that pings each IP in the hardware accessories menu
private async Task PingCheck()
{
List<Task> tasks = // Run all ping checks in parallel
[
Network.PingAndUpdateUI(localDataStore.PSUIP, LblPSUPing, "PSU", ToolTipAvailable, [btnPsuOff, btnPsuOn,btnPsu12V,btnPsu48V]),
Network.PingAndUpdateUI(localDataStore.EzIP, LblEzPing, "Ez", ToolTipAvailable, [BtnEzOff, BtnEzOn]),
Network.PingAndUpdateUI(localDataStore.ZebraIP, LblZebraPing, "Zebra", ToolTipAvailable, [BtnPrintGB]),
Network.PingAndUpdateUI(localDataStore.TestTubeIP, LblTestTubePing, "Test Tube", ToolTipAvailable)
];
await Task.WhenAll(tasks); // Wait for all tasks to complete
}
private void TxBxCheckValid(TextBox TxBx)
{
if (RegexCache.RegexIPPattern().IsMatch(TxBx.Text)) // Check IP address is valid
{
TxBx.BackColor = TxBxColour; // Assume all is good
BtnSave.Enabled = !PanelSettings.Controls.Cast<Control>().Any(tb => tb.BackColor == Color.Red);
}
else
{
TxBx.BackColor = Color.Red;
BtnSave.Enabled = false;
BtnSave.BackColor = BtnColour;
}
TestStartConditions();
}
private void BtnGenerate_Click(object sender, EventArgs e)
{
// Return code straight into rich textbox
string LicCode = Lics.GenerateLicCode(TxBxChallenge.Text, CbBxType.Text);
RhTxBxCode.AppendText(LicCode + Environment.NewLine);
RhTxBxCode.SelectionStart = RhTxBxCode.Text.Length;
RhTxBxCode.ScrollToCaret();
Clipboard.SetText(LicCode); // Copy license code to clipboard
Button button = (Button)sender;
int yOffset = button.Height + 5; // Offset 5 pixels below the button
ToolTipClipboard.Show("Copied to clipboard!", button, 0, yOffset, 2000); // Tool tip visible to show copied
}
private void TxBxChallenge_TextChanged(object sender, EventArgs e) // Also services CboBxType_SelectedIndexChanged
{
CheckCodeFormat(TxBxChallenge.Text);
}
private void CheckCodeFormat(string ChlgCode)
{
if (RegexCache.LicCodeRegex().IsMatch(ChlgCode))
{
if (ChlgCode.Length == 7) // If length is 7 then it has the license type distingusher on front
{
string type = ChlgCode.Substring(0, 1);
TxBxChallenge.Text = ChlgCode.Substring(1, 6); // Cut down text to only the challenge code.
if (type == "F") // SAF
CbBxType.SelectedIndex = 0;
else if (type == "S") // Streaming
CbBxType.SelectedIndex = 1;
else if (type == "A") // Audit
CbBxType.SelectedIndex = 2;
BtnGenerate.Enabled = true;
}
else if (ChlgCode.Length == 6 && CbBxType.SelectedIndex != -1) // Code without distinguser and type chosen
{
TxBxChallenge.Text = ChlgCode;
BtnGenerate.Enabled = true;
}
}
else
{
BtnGenerate.Enabled = false;
}
}
// License Vaxtor button
private async void BtnLicVaxtor_Click(object sender, EventArgs e)
{
if (CameraAccessInfo.VaxtorLic) // If camera should have a Vaxtor license then license it now
{
if (await DisplayQuestion("Are you sure you want to license Vaxtor to this camera?"))
{
// Update server endpoint, username and password before licensing
string[,] Endpoint_JSON = { { "propLicenceKeyEndpointURL", UniversalData.LicencingServerURL } };
await FlexiAPI.HTTP_Update("RaptorOCR", CamOnTest.IP, Endpoint_JSON);
string ALresponse;
try
{
ALresponse = await FlexiAPI.APIHTTPRequest("/api/RaptorOCR-auto-license", CamOnTest.IP, 10); // License Vaxtor
}
catch
{
AddToActionsList("Failed to communicate with camera, please check network and try again.");
return;
}
try // Deserialise the JSON
{
VaxtorLicResp = JsonConvert.DeserializeObject<VaxtorLic>(ALresponse);
if (VaxtorLicResp.protectionKeyId != string.Empty)
{
string err = GoogleAPI.UpdateSpreadSheetVaxtor(VaxtorLicResp, Vers.Serial, CamOnTest.Model);
if (err != string.Empty) // If there is an error message, display it
AddToActionsList("Failed to update Vaxtor spreadsheet: " + err);
RhTxBxCode.AppendText("Licencing Success, Key ID: " + VaxtorLicResp.protectionKeyId + Environment.NewLine + "Waiting for files to save");
if (await DisplayQuestion("Do you want to sync to disk? (If unsure what this means press yes)"))
SSH.Sync(CamOnTest.IP); // Sync everything in RAM to the disk
await DisplayOK("Please wait at least a minute before turning off the unit.");
}
else if (VaxtorLicResp.error != string.Empty)
{
RhTxBxCode.AppendText(VaxtorLicResp.error);
}
else
{
AddToActionsList($"Error reading JSON - {ALresponse}");
}
}
catch
{
AddToActionsList($"Error reading JSON - {ALresponse}");
return;
}
}
}
else
{
RhTxBxCode.AppendText("This model shouldn't have a Vaxtor license. NOT LICENSED");
}
}
// Refresh the unix time that is displayed and used to generate developer password
private void BtnRefreshUnix_Click(object sender, EventArgs e)
{
TimeSpan t = DateTime.UtcNow - new DateTime(1970, 1, 1);
int secondsSinceEpoch = (int)t.TotalSeconds;
TxBxUnixTime.Text = Convert.ToString(secondsSinceEpoch);
}
// Check if MAC address textbox contains a valid MAC
private void TxBxMAC_TextChanged(object sender, EventArgs e)
{
if (RegexCache.MACRegex().IsMatch(TxBxMAC.Text))
{
TxBxMAC.BackColor = TxBxColour;
BtnSecretMan.Enabled = true;
}
else
{
TxBxMAC.BackColor = Color.Red;
BtnSecretMan.Enabled = false;
}
}
// Check if unix textbox contains a valid Unix time
private void TxBxUnixTime_TextChanged(object sender, EventArgs e)
{
if (long.TryParse(TxBxUnixTime.Text, out long unixTime) && unixTime >= 0 && unixTime <= 2147483647)
{
TxBxUnixTime.BackColor = TxBxColour;
BtnSecretMan.Enabled = true;
}
else
{
TxBxUnixTime.BackColor = Color.Red;
BtnSecretMan.Enabled = false;
}
}
// Check if Flexi version textbox contains a valid version
private void TxBxFlexiVer_TextChanged(object sender, EventArgs e)
{
if (RegexCache.FlexiVerRegex().IsMatch(TxBxFlexiVer.Text)) // Regex on Flexi version that should be three values dot seperated up to three digits each
{
TxBxFlexiVer.BackColor = TxBxColour;
BtnSecretMan.Enabled = true;
}
else
{
TxBxFlexiVer.BackColor = Color.Red;
BtnSecretMan.Enabled = false;
}
}
// Use the version API with connected camera to generate developer password
private async void BtnSecret_Click(object sender, EventArgs e)
{
Vers = await FlexiAPI.GetVersions(CamOnTest.IP);
Lics.DisplayDevPassword(Vers, CamOnTest);
ShowToolTip(BtnSecret);
}
// Generate password using manual inputs
private void BtnSecretMan_Click(object sender, EventArgs e)
{
CamOnTest.DevPass = Lics.GeneratePassword(TxBxMAC.Text, TxBxFlexiVer.Text, Convert.ToInt32(TxBxUnixTime.Text));
ShowToolTip(sender);
}
private void ShowToolTip(object sender) // Shows 'Copied to clipboard!' tooltip below sender button
{
try // Catches sender when it comes from combo box
{
Button button = (Button)sender;
int yOffset = button.Height + 5; // Offset 5 pixels below the button
ToolTipClipboard.Show("Copied to clipboard!", button, 0, yOffset, 2000); // Show tool tip
Clipboard.SetText(CamOnTest.DevPass); // Copy password to clipboard
}
catch { }
TxBxOutput.Text = CamOnTest.DevPass; // Done last to maintain focus on box
}
private void TxBxOutput_Click(object sender, EventArgs e)
{
TxBxOutput.SelectAll(); // Select the password in the box
TxBxOutput.Focus(); // Make it the users focus so it is in focus after generate
}
private async void BtnEzOn_Click(object sender, EventArgs e)
{
BtnEzOff.BackColor = BtnColour;
if (await Ez.EzOutletControl("ON"))
BtnEzOn.BackColor = Color.ForestGreen;
else
BtnEzOn.BackColor = Color.Maroon;
}
private async void BtnEzOff_Click(object sender, EventArgs e)
{
BtnEzOn.BackColor = BtnColour;
if (await Ez.EzOutletControl("OFF"))
BtnEzOff.BackColor = Color.ForestGreen;
else
BtnEzOff.BackColor = Color.Maroon;
}
private async void BtnZoomWide_Click(object sender, EventArgs e)
{
if (await FlexiAPI.ZoomModules("0000", CamOnTest.IP))
BtnZoomWide.BackColor = Color.Green;
else
BtnZoomWide.BackColor = Color.Red;
BtnZoom8000.BackColor = BtnColour;
}
private async void BtnZoom8000_Click(object sender, EventArgs e)
{
if (await FlexiAPI.ZoomModules("1F40", CamOnTest.IP))
BtnZoom8000.BackColor = Color.Green;
else
BtnZoom8000.BackColor = Color.Red;
BtnZoomWide.BackColor = BtnColour;
}
private void BtnOpenWebpage_Click(object sender, EventArgs e)
{
ProcessStartInfo psi = new()
{
FileName = $"http://{CamOnTest.IP}", // Just the URL
UseShellExecute = true // Lets the OS decide how to open it
};
Process.Start(psi);
}
private void TxBxSerialPrint_Click(object sender, EventArgs e)
{
if (TxBxSerialPrint.Text == "K ") // If at default then remove the dashes ready for user to put in number
{
TxBxSerialPrint.Text = "K";
TxBxSerialPrint.SelectionStart = 1;
}
}
private void BtnPrintAiQ_Click(object sender, EventArgs e)
{
Printer.PrintSerialLbl(CamOnTest.Model, TxBxSerialPrint.Text, CameraAccessInfo.Processor); // Print model/serial label
}
private void BtnPrintGB_Click(object sender, EventArgs e)
{
Printer.PrintGBLbl(); // Print GB label
}
private void TxBxSerialPrint_TextChanged(object sender, EventArgs e)
{
CheckPrintCapable(); // Check if the print buttons can be enabled
}
private void CheckPrintCapable()
{
if (LblZebraPing.Text == "✔") // Check the IP address is ok
{
BtnPrintGB.Enabled = true;
if (CbBxCameraType.SelectedIndex != -1 && RegexCache.SerialRegex().IsMatch(TxBxSerialPrint.Text)) // Check model and serial are known
{
Printer.ZebraIP = localDataStore.ZebraIP;
BtnPrintAiQ.Enabled = true;
}
}
}
// ***** RMA Panel *****
private void BtnDone_Click(object sender, EventArgs e)
{
CamOnTest.RMANum = Convert.ToInt16(RMANumBox.Value);
Flags.Done = true;
}
private void BtnDont_Click(object sender, EventArgs e)
{
CamOnTest.RMANum = -1;
Flags.Done = true;
}
private void BtnRerun_Click(object sender, EventArgs e)
{
CamOnTest.RMANum = 0;
Flags.Done = true;
}
// ***** Soak *****
// Sets the soak test running for all cameras that are ticked.
private async void BtnSoak_Click(object sender, EventArgs e)
{
if (BtnSoak.Text == "Start Soak")
{
BtnSoak.BackColor = Color.Orange;
BtnSoak.Text = "Stop Soak";
soakCtsList.Clear();
soakTasks.Clear();
foreach (Camera SCL in soakCameraList)
{
if (!SCL.IsChecked)
continue;
CancellationTokenSource cts = new();
soakCtsList.Add(cts);
soakTasks.Add(SoakTest.StartSoak(SCL, cts.Token));
await Task.Delay(5000);
}
}
else
{
BtnSoak.BackColor = BtnColour; // Reset button colour
BtnSoak.Text = "Start Soak";
foreach (CancellationTokenSource cts in soakCtsList)
cts.Cancel();
soakCtsList.Clear();
soakTasks.Clear();
foreach (Camera SCL in soakCameraList) // Reset all cameras that were being soaked to default module settings
{
if (!SCL.IsChecked)
continue;
Network.Initialize("developer", SCL.DevPass); // Ensure network is initialized to the right camera, cannot be done in soak test finally becuase of this.
await CameraModules.FactoryResetModules(SCL.IP);
}
}
}
private void CkBxTickAll_CheckedChanged(object sender, EventArgs e)
{
List<CheckBox> checkBoxes = TabSoak.Controls.OfType<CheckBox>().Except([CkBxTickAll]).ToList();
bool ToTick = CkBxTickAll.Text == "Tick all"; // True if we are ticking all checkboxes, false if we are unticking them
CkBxTickAll.Text = ToTick ? "Unick all" : "Tick all"; // Toggle the text of the checkbox
foreach (CheckBox? cb in checkBoxes)
cb.Checked = ToTick;
}
private async void timerTypeIP_Tick(object sender, EventArgs e)
{
timerTypeIP.Enabled = false;
if (!RegexCache.RegexIPPattern().IsMatch(CbBxFoundCams.Text) && !await Network.PingIP(CbBxFoundCams.Text)) // Check IP address is valid
return;
if (TabImagesandSettings.SelectedIndex == 3)
{
soakCameraList.Clear();
// Deletes all the checkboxes in TabSoak
List<CheckBox> checkBoxes = TabSoak.Controls.OfType<CheckBox>().Except([CkBxTickAll]).ToList();
foreach (CheckBox? cb in checkBoxes)
{
TabSoak.Controls.Remove(cb);
cb.Dispose();
}
Camera NewCam = await Helper.NewCamera(CbBxFoundCams.Text);
if (NewCam != null)
{
soakCameraList.Add(NewCam);
TabSoak.Controls.Add(SoakTest.MakeNewCheckbox(NewCam, 74));
}
}
CbBxFoundCams_SelectedIndexChanged(sender, e);
}
private async void BtnUploadBlob_Click(object sender, EventArgs e)
{
2025-09-16 10:42:51 +01:00
BtnUploadBlob.BackColor = BtnColour;
2025-09-02 15:32:24 +01:00
const string networkFolderPath = @"G:\Shared drives\MAV Production\MAV_146_AiQ_Mk2\Flexi";
string fileToUpload = null;
if (await DisplayQuestion("Do you want the latest Flexi version from the MAV Production folder?"))
{
fileToUpload = Directory.GetFiles(networkFolderPath, "*.blob").OrderByDescending(File.GetLastWriteTime).FirstOrDefault();
if (fileToUpload == null)
{
AddToActionsList("No .blob file found in the directory.", false);
return;
}
}
else
{
using OpenFileDialog openFileDialog1 = new()
{
InitialDirectory = networkFolderPath,
Filter = "Blob files (*.blob)|*.blob",
FilterIndex = 0
};
if (openFileDialog1.ShowDialog() == DialogResult.OK)
fileToUpload = openFileDialog1.FileName;
else
{
AddToActionsList("File selection cancelled.", false);
return;
}
}
string fileName = Path.GetFileName(fileToUpload);
AddToActionsList($"Selected file to upload: {fileToUpload}", false);
foreach (Camera? cam in soakCameraList.Where(c => c.IsChecked))
{
string apiUrl = $"http://{cam.IP}/upload/software-update/2";
Network.Initialize("developer", cam.DevPass);
AddToActionsList($"Uploading to {cam.IP}...", false);
string result = await FlexiAPI.SendBlobFileUpload(apiUrl, fileToUpload, fileName);
// Retry once on transient errors
if (result.Contains("Error while copying content to a stream") || result.Contains("Timeout"))
{
AddToActionsList($"Retrying upload to {cam.IP}...", false);
await Task.Delay(1000);
result = await FlexiAPI.SendBlobFileUpload(apiUrl, fileToUpload, fileName);
}
AddToActionsList($"Upload result for {cam.IP}: {result}", false);
await Task.Delay(500);
}
2025-09-16 10:42:51 +01:00
BtnUploadBlob.BackColor = Color.Green;
2025-09-02 15:32:24 +01:00
}
private async void BtnFactoryDefault_Click(object sender, EventArgs e)
{
2025-09-16 10:42:51 +01:00
BtnFactoryDefault.BackColor = BtnColour;
2025-09-02 15:32:24 +01:00
foreach (Camera SCL in soakCameraList) // Reset all cameras that were being soaked to default module settings
{
if (!SCL.IsChecked)
continue;
Network.Initialize("developer", SCL.DevPass); // Ensure network is initialized to the right camera, cannot be done in soak test finally becuase of this.
await CameraModules.FactoryResetModules(SCL.IP);
}
2025-09-16 10:42:51 +01:00
BtnFactoryDefault.BackColor = Color.Green;
2025-09-02 15:32:24 +01:00
}
2025-09-16 10:42:51 +01:00
// Constants
const double RealPlateWidthMeters = 0.52; // UK standard plate width
const double FocalLengthPixels = (50 * 1280) / 14.111224; // focal mm * pixel width / sensor width for IQ
// const double FocalLengthPixels = (50 * 1920) / 6.95; // focal mm * pixel width / sensor width for AiQ
const double FrameRate = 25.0; // Frames per second
2025-09-02 15:32:24 +01:00
2025-09-16 10:42:51 +01:00
public class FrameData
{
public long FrameID;
public int PlatePosX;
public int PlatePosY;
public int PlateWidthPixels;
}
2025-09-02 15:32:24 +01:00
2025-09-16 10:42:51 +01:00
public double EstimateSpeed(List<FrameData> frames)
{
double TimeElapsed = 0;
int frameCount = frames.Count;
2025-09-02 15:32:24 +01:00
2025-09-16 10:42:51 +01:00
for (int i = 1; i < frameCount; i++)
2025-09-02 15:32:24 +01:00
{
2025-09-16 10:42:51 +01:00
double time = (frames[i].FrameID - frames[i - 1].FrameID) / FrameRate;
TimeElapsed += time;
}
2025-09-02 15:32:24 +01:00
2025-09-16 10:42:51 +01:00
double FarDist = (FocalLengthPixels * RealPlateWidthMeters) / frames[0].PlateWidthPixels;
double CloseDist = (FocalLengthPixels * RealPlateWidthMeters) / frames[frameCount - 1].PlateWidthPixels;
2025-09-02 15:32:24 +01:00
2025-09-16 10:42:51 +01:00
double speedMph = (Math.Abs(FarDist - CloseDist) / TimeElapsed) * 2.237;
return speedMph;
2025-09-02 15:32:24 +01:00
}
2025-09-16 10:42:51 +01:00
2025-09-02 15:32:24 +01:00
// ***** Test & Debug *****
private void BtnTest_Click(object sender, EventArgs e)
{
Stopwatch stopWatchTest = Stopwatch.StartNew();
2025-09-16 10:42:51 +01:00
List<FrameData> frames = new List<FrameData>
{
new FrameData { FrameID = 60192555, PlatePosX = 1172, PlatePosY = 393, PlateWidthPixels = 108 },
new FrameData { FrameID = 60192556, PlatePosX = 1103, PlatePosY = 361, PlateWidthPixels = 105 },
new FrameData { FrameID = 60192558, PlatePosX = 983, PlatePosY = 331, PlateWidthPixels = 99 },
new FrameData { FrameID = 60192559, PlatePosX = 930, PlatePosY = 301, PlateWidthPixels = 95 },
new FrameData { FrameID = 60192560, PlatePosX = 880, PlatePosY = 304, PlateWidthPixels = 93 },
new FrameData { FrameID = 60192561, PlatePosX = 834, PlatePosY = 278, PlateWidthPixels = 89 },
new FrameData { FrameID = 60192562, PlatePosX = 792, PlatePosY = 229, PlateWidthPixels = 87 },
new FrameData { FrameID = 60192563, PlatePosX = 752, PlatePosY = 208, PlateWidthPixels = 85 },
new FrameData { FrameID = 60192565, PlatePosX = 680, PlatePosY = 187, PlateWidthPixels = 81 },
new FrameData { FrameID = 60192566, PlatePosX = 648, PlatePosY = 167, PlateWidthPixels = 78 },
new FrameData { FrameID = 60192567, PlatePosX = 617, PlatePosY = 149, PlateWidthPixels = 76 },
new FrameData { FrameID = 60192568, PlatePosX = 588, PlatePosY = 132, PlateWidthPixels = 75 },
new FrameData { FrameID = 60192569, PlatePosX = 561, PlatePosY = 100, PlateWidthPixels = 70 },
new FrameData { FrameID = 60192570, PlatePosX = 535, PlatePosY = 85, PlateWidthPixels = 72 },
new FrameData { FrameID = 60192572, PlatePosX = 488, PlatePosY = 70, PlateWidthPixels = 69 },
new FrameData { FrameID = 60192573, PlatePosX = 466, PlatePosY = 55, PlateWidthPixels = 67 }
};
2025-09-02 15:32:24 +01:00
2025-09-16 10:42:51 +01:00
double Spd = EstimateSpeed(frames);
AddToActionsList("Estimated Speed: " + Spd.ToString("F2") + " MPH");
2025-09-02 15:32:24 +01:00
stopWatchTest.Stop();
AddToActionsList("RunTime " + stopWatchTest.Elapsed.ToString(@"hh\:mm\:ss\.ff"));
}
}
}