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" ) ) ;
}
}
}