2025-09-02 15:32:24 +01:00
using Newtonsoft.Json ;
using System.Net.Http.Headers ;
namespace AiQ_GUI
{
public enum LEDPOWER
{
LOW ,
MID ,
HIGH ,
SAFE ,
OFF
}
public class FlexiAPI
{
// GET API from camera
public static async Task < string > APIHTTPRequest ( string EndPoint , string IPAddress , int? Timeout = 2 )
{
try
{
string URL = $"http://{IPAddress}{EndPoint}" ;
return await Network . SendHttpRequest ( URL , HttpMethod . Get , Timeout ) ;
}
catch ( Exception ex )
{
2025-12-02 12:59:40 +00:00
return $"Error during GET request: {ex.Message}{Level.ERROR}" ;
2025-09-02 15:32:24 +01:00
}
}
// For 'update-config' sending a change to the AiQ
// TODO - Not many of the references check the output is positive. Need them all to check.
public static async Task < string > HTTP_Update ( string ID , string IPAddress , string [ , ] jsonArrayData )
{
try
{
string JSONdata = BuildJsonUpdate ( jsonArrayData , ID ) ;
2025-10-21 14:01:27 +01:00
JSONdata = JSONdata . Replace ( "\"14\"" , "14" ) . Replace ( "\"30\"" , "30" ) ; // Fixes & encoding issue
2025-09-02 15:32:24 +01:00
string url = $"http://{IPAddress}/api/update-config" ;
return await Network . SendHttpRequest ( url , HttpMethod . Post , 2 , JSONdata ) ;
2025-11-04 12:56:16 +00:00
2025-09-02 15:32:24 +01:00
}
catch ( Exception ex )
{
2025-12-02 12:59:40 +00:00
return $"Error in HTTP_Update: {ex.Message}{Level.ERROR}" ;
2025-09-02 15:32:24 +01:00
}
}
// For 'fetch-config' getting info from AiQ
public static async Task < string > HTTP_Fetch ( string ID , string IPAddress , int? Timeout = 2 )
{
try
{
2025-09-16 10:42:51 +01:00
string url = $"http://{IPAddress}/api/fetch-config?id={ID}" ;
return await Network . SendHttpRequest ( url , HttpMethod . Get , Timeout ) ;
2025-09-02 15:32:24 +01:00
}
catch ( Exception ex )
{
2025-12-02 12:59:40 +00:00
return $"Error in HTTP_Fetch: {ex.Message}{Level.ERROR}" ;
2025-09-02 15:32:24 +01:00
}
}
// For sending VISCA directly to the camera module
// Be aware this does bypass Flexi's watchdog so settings like zoom, focus, SIG wont keep forever
public static async Task < string > APIHTTPVISCA ( string IPAddress , string VISCA , bool IR )
{
2025-10-17 12:06:36 +01:00
// http://localhost:8080/Infrared-camera-control?commandHex=8101044700000000FF
2025-09-02 15:32:24 +01:00
string suffix = $"-camera-control?commandHex={VISCA}" ;
if ( IR ) // Add on which camera to control
suffix = "/Infrared" + suffix ;
else
suffix = "/Colour" + suffix ;
return await APIHTTPRequest ( suffix , IPAddress ) ;
}
// Sets the LED level into the camera
public static async Task < string > APIHTTPLED ( string IPAddress , LEDPOWER LEVEL )
{
// Always Infrared as LED's are controlled from infrared page
// Level can be word eg. SAFE or hex eg. 0x0E
string suffix = $"/Infrared/led-controls?power={LEVEL}" ;
2025-10-29 16:17:59 +00:00
return await APIHTTPRequest ( suffix , IPAddress , 5 ) ;
2025-09-02 15:32:24 +01:00
}
public static async Task < string > SendBlobFileUpload ( string url , string filePath , string fileName )
{
try
{
Network . Client . DefaultRequestHeaders . ExpectContinue = false ;
byte [ ] fileBytes = await File . ReadAllBytesAsync ( filePath ) . ConfigureAwait ( false ) ;
MemoryStream ms = new ( fileBytes ) ;
StreamContent streamContent = new ( ms ) ;
streamContent . Headers . ContentType = new MediaTypeHeaderValue ( "application/octet-stream" ) ;
2025-09-16 10:42:51 +01:00
MultipartFormDataContent content = new ( ) { { streamContent , "upload" , fileName } } ;
2025-09-02 15:32:24 +01:00
using HttpResponseMessage response = await Network . Client . PostAsync ( url , content ) ;
string responseBody = await response . Content . ReadAsStringAsync ( ) ;
if ( ! response . IsSuccessStatusCode )
2025-12-02 12:59:40 +00:00
return $"Server returned {(int)response.StatusCode}: {response.ReasonPhrase}. Details: {responseBody}{Level.ERROR}" ;
2025-09-02 15:32:24 +01:00
return responseBody ;
}
catch ( TaskCanceledException )
{
2025-12-02 12:59:40 +00:00
return $"Timeout uploading to {url}.{Level.ERROR}" ;
2025-09-02 15:32:24 +01:00
}
catch ( HttpRequestException ex )
{
2025-12-02 12:59:40 +00:00
return $"HTTP error uploading to {url}: {ex.Message}{Level.ERROR}" ;
2025-09-02 15:32:24 +01:00
}
catch ( Exception ex )
{
2025-12-02 12:59:40 +00:00
return $"Unexpected error uploading to {url}: {ex.Message} {(ex.InnerException?.Message ?? string.Empty)} {Level.ERROR}" ;
2025-09-02 15:32:24 +01:00
}
}
public static async Task < Versions > GetVersions ( string IPAddress )
{
string JSON = await APIHTTPRequest ( "/api/versions" , IPAddress ) ; // Version API request
if ( JSON = = null | | JSON . Contains ( "Error" ) | | JSON . Contains ( "Timeout" ) )
return null ;
Logging . LogMessage ( "Received versions JSON: " + JSON ) ;
try
{
return JsonConvert . DeserializeObject < Versions > ( JSON ) ;
}
catch
{
MainForm . Instance . AddToActionsList ( "Cannot deserialise Versions JSON" + Environment . NewLine + JSON ) ;
return null ; // If it fails to parse the JSON
}
}
public static async Task < Diags > GetDiagnostics ( string IPAddress )
{
string JSON = await APIHTTPRequest ( "/api/diagnostics" , IPAddress , 20 ) ; // Version API request
if ( JSON = = null | | JSON . Contains ( "Error" ) | | JSON . Contains ( "Timeout" ) )
{
2025-12-02 12:59:40 +00:00
MainForm . Instance . AddToActionsList ( $"Error talking to Flexi, are you sure this is an AiQ?{Level.WARNING}" + Environment . NewLine + JSON ) ;
2025-09-02 15:32:24 +01:00
return null ;
}
Logging . LogMessage ( "Received diagnostics JSON: " + JSON ) ;
try
{
return JsonConvert . DeserializeObject < Diags > ( JSON ) ;
}
catch
{
MainForm . Instance . AddToActionsList ( "Cannot deserialise Diagnostics JSON" + Environment . NewLine + JSON ) ;
return null ; // If it fails to parse the JSON
}
}
2025-10-29 16:17:59 +00:00
2025-10-21 14:04:59 +01:00
public static async Task < bool > SetVaxtorMinMaxPlate ( string IP )
{
try
{
// Build JSON array for Vaxtor min/max plate configuration
2025-10-29 16:17:59 +00:00
string [ , ] Vaxtor_JSON = { { "propMinCharHeight" , "14" } , { "propMaxCharHeight" , "40" } , { "propMinGlobalConfidence" , "30" } } ;
2025-10-21 14:04:59 +01:00
string response = await HTTP_Update ( "RaptorOCR" . Trim ( ) , IP , Vaxtor_JSON ) ;
// Treat "operation was canceled" as a successful apply
if ( response . Contains ( "The operation was canceled" , StringComparison . OrdinalIgnoreCase ) )
{
2025-12-02 12:59:40 +00:00
Logging . LogMessage ( $"SetVaxtorMinMaxPlate: Camera applied config but closed connection early (safe to ignore).{Level.WARNING}" ) ;
2025-10-21 14:04:59 +01:00
return true ;
}
if ( response . Contains ( "error" , StringComparison . OrdinalIgnoreCase ) )
{
2025-12-02 12:59:40 +00:00
MainForm . Instance . DisplayQuestion ( $"SetVaxtorMinMaxPlate: failed - Please set manually{Level.WARNING}" ) ;
2025-10-21 14:04:59 +01:00
return false ;
}
return true ;
}
catch ( Exception ex )
{
2025-12-02 12:59:40 +00:00
MainForm . Instance . AddToActionsList ( $"Could not set Vaxtor Plate height and min confidence: {ex.Message}{Level.ERROR}" ) ;
2025-10-21 14:04:59 +01:00
return false ;
}
}
2025-09-02 15:32:24 +01:00
public static async Task < bool > SetZoomLockOn ( string IP )
{
// Set Zoomlock on and if it fails ask user to set it manually
if ( ! ( await APIHTTPRequest ( "/api/zoomLock?enable=true" , IP ) ) . Contains ( "Zoom lock enabled." )
2025-12-02 12:59:40 +00:00
& & ! await MainForm . Instance . DisplayQuestion ( $"Could not set zoomlock on{Level.WARNING}" + Environment . NewLine + $"Set Zoomlock to on then click YES. Click NO to restart. {Level.WARNING}" ) )
2025-09-02 15:32:24 +01:00
{
return false ;
}
return true ;
}
public static async Task < bool > ZoomModules ( string VISCAInput , string IPAddress )
{
// Populate the VISCA command with the four zoom characters
string VISCA = $"810104470{VISCAInput[0]}0{VISCAInput[1]}0{VISCAInput[2]}0{VISCAInput[3]}FF" ;
Task < string > TS1 = APIHTTPVISCA ( IPAddress , VISCA , true ) ;
Task < string > TS2 = APIHTTPVISCA ( IPAddress , VISCA , false ) ;
await Task . WhenAll ( TS1 , TS2 ) ;
const string ExpReply = "9041FF9051FF" ;
if ( TS1 . Result = = ExpReply & & TS1 . Result = = ExpReply )
return true ;
return false ;
}
2025-09-16 10:42:51 +01:00
public static async Task SetTrim ( string IPAddress , string LblTxt , int RetryCount = 0 ) // Sets trim by getting plate postion as metric
{
Trim trim ;
string trimData = await APIHTTPRequest ( "/SightingCreator-plate-positions" , IPAddress , 5 ) ; // Get plate positions
try // Deserialise the JSON
{
Logging . LogMessage ( "Trim Data: " + trimData ) ;
trim = JsonConvert . DeserializeObject < Trim > ( trimData ) ;
}
catch
{
2025-12-02 12:59:40 +00:00
MainForm . Instance . AddToActionsList ( $"Error reading trim JSON - {Level.ERROR}" + trimData ) ;
2025-09-16 10:42:51 +01:00
return ;
}
// Check no value is -1 (no plate found) or if the positions are identical (one plate found). If it is then try again 3 times
2025-10-29 16:17:59 +00:00
if ( new [ ] { trim . infraredX , trim . infraredY , trim . colourX , trim . colourY } . Any ( value = > value = = - 1 ) | | ( trim . infraredX = = trim . colourX & & trim . infraredY = = trim . colourY ) )
2025-09-16 10:42:51 +01:00
{
if ( RetryCount > = 3 )
{
2025-12-02 12:59:40 +00:00
await MainForm . Instance . DisplayOK ( $"Please align trim in webpage then click OK.{Level.WARNING}" ) ; // Awaited till OK has been clicked
2025-09-16 10:42:51 +01:00
return ;
}
await Task . Delay ( 5000 ) ; // Give 5 second delay for it to see a plate
2025-10-29 16:17:59 +00:00
await SetTrim ( IPAddress , LblTxt , RetryCount + 1 ) ;
2025-09-16 10:42:51 +01:00
}
int offset = 105 ;
if ( LblTxt = = "❌" ) // Test tube not connected so do the 2.7m check.
offset = 98 ;
// Horizontal distance offset for 2.7m compared to 30m is 98 pixels. This was empirically found from testing in the car park
// Colour camera is to the right of the infrared so it gets the offset.
// Using similar triangles going from 2.7m -> 0.65m which is the length of the test tube (Also exactly 1/4 length).
// 98 * (29.35/27.3) = 105.35 pixels
int OverviewX = trim . colourX + offset ;
if ( OverviewX > 1920 ) // If adding on the offset has pushed it out of limits then remove 0.1
{
if ( OverviewX < 2120 & & trim . infraredX > 400 ) // Within enough of a limit to automatically do it
{
OverviewX - = 200 ;
trim . infraredX - = 200 ;
}
else // Ask user to centre the plate in the field of view
{
2025-12-02 12:59:40 +00:00
await MainForm . Instance . DisplayOK ( $"Please centralise plate in view THEN press OK {Level.WARNING}" ) ; // Awaited till OK has been clicked
2025-09-16 10:42:51 +01:00
if ( RetryCount > = 3 )
{
2025-12-02 12:59:40 +00:00
await MainForm . Instance . DisplayOK ( $"Please align trim in webpage then click OK. {Level.WARNING}" ) ; // Awaited till OK has been clicked
2025-09-16 10:42:51 +01:00
return ;
}
await Task . Delay ( 5000 ) ; // Give 5 second delay for it to see a plate
2025-10-29 16:17:59 +00:00
await SetTrim ( IPAddress , LblTxt , RetryCount + 1 ) ;
2025-09-16 10:42:51 +01:00
}
}
// Compensated trim values, therefore should be close to 0,0 with limits of ±5% of 1920 and 1080 respectivly being ±96 and ±54
int TrimX = trim . infraredX - OverviewX ;
int TrimY = trim . infraredY - trim . colourY ;
// Update trim values
string [ , ] Trim_JSON = { { "propInterCameraOffsetX" , Convert . ToString ( TrimX ) } , { "propInterCameraOffsetY" , Convert . ToString ( TrimY ) } } ;
string TrimResp = await HTTP_Update ( "SightingCreator" , IPAddress , Trim_JSON ) ;
if ( ! TrimResp . Contains ( $"\" propInterCameraOffsetX \ ": {{\"value\": \"{Convert.ToString(TrimX)}\", \"datatype\": \"int\"}}, \"propInterCameraOffsetY\": {{\"value\": \"{Convert.ToString(TrimY)}\", \"datatype\": \"int\"}}," ) )
2025-12-02 12:59:40 +00:00
MainForm . Instance . AddToActionsList ( $"Could not set camera trim{Level.ERROR}" ) ;
2025-09-16 10:42:51 +01:00
}
2025-09-02 15:32:24 +01:00
// Processes the network config from the camera and returns a string indicating the status
public static async Task < string > ProcessNetworkConfig ( string IPAddress )
{
try
{
string JSON = await HTTP_Fetch ( "GLOBAL--NetworkConfig" , IPAddress , 10 ) ;
NetworkConfig NC = JsonConvert . DeserializeObject < NetworkConfig > ( JSON ) ;
if ( Convert . ToBoolean ( NC . propDHCP . Value ) = = false )
return "Set to DHCP" ;
else
return "Set to 211" ;
}
catch ( Exception ex )
{
2025-12-02 12:59:40 +00:00
MainForm . Instance . AddToActionsList ( $"Error in getting network config from camera: {ex.Message}{Level.ERROR}" ) ;
2025-09-02 15:32:24 +01:00
return null ; // Return empty string if there is an error
}
}
// Knowing the format this builds the message to send to AiQ
2025-09-16 10:42:51 +01:00
public static string BuildJsonUpdate ( string [ , ] jsonData , string id )
2025-09-02 15:32:24 +01:00
{
if ( jsonData = = null | | jsonData . GetLength ( 1 ) ! = 2 )
throw new ArgumentException ( "Input data must be a non-null 2D array with two columns." ) ;
int rows = jsonData . GetLength ( 0 ) ;
List < object > fields = [ ] ;
for ( int i = 0 ; i < rows ; i + + ) // Puts each part of the array into correct format
{
fields . Add ( new
{
property = jsonData [ i , 0 ] ,
value = jsonData [ i , 1 ]
} ) ;
}
var updateObject = new // Correct naming and format so it is JSON ready
{
id ,
fields
} ;
return JsonConvert . SerializeObject ( updateObject , Formatting . None ) ; // Uses no white space
}
// Change network settings to 192.168.1.211 and wait 5 seconds to see if it takes effect
public static async Task < bool > ChangeNetwork211 ( string IPAddress )
{
// Update GLOBAL--NetworkConfig with fixed IP and turn off DHCP
string [ , ] TEST_JSON = { { "propDHCP" , "false" } , { "propHost" , "192.168.1.211" } , { "propNetmask" , "255.255.255.0" } , { "propGateway" , "192.168.1.1" } } ;
await HTTP_Update ( "GLOBAL--NetworkConfig" , IPAddress , TEST_JSON ) ;
await Task . Delay ( 5000 ) ; // Wait for 5 seconds to allow the camera to restart
IList < string > FoundCams = await Network . SearchForCams ( ) ; // Have to check via broadcast becuase Ping sometimes fails across subnets
return FoundCams . Contains ( "192.168.1.211" ) ;
}
// Change network settings to DHCP and restart camera for it to take effect
2025-11-04 12:56:16 +00:00
public async static Task < bool > ChangeNetworkToDHCP ( string IPAddress )
2025-09-02 15:32:24 +01:00
{
2025-11-04 12:56:16 +00:00
string [ , ] TEST_JSON = { { "propDHCP" , "true" } } ;
2025-11-04 14:29:38 +00:00
await HTTP_Update ( "GLOBAL--NetworkConfig" , IPAddress , TEST_JSON ) ; // Don't care about response because it will fail as it has changed IP.
2025-11-04 12:56:16 +00:00
await Task . Delay ( 5000 ) ; // Wait for 5 seconds to allow the camera to restart
2025-11-04 14:29:38 +00:00
IList < string > FoundCams = await Network . SearchForCams ( ) ;
2025-11-04 12:56:16 +00:00
if ( FoundCams . Contains ( "192.168.1.211" ) )
{
2025-12-02 12:59:40 +00:00
MainForm . Instance . AddToActionsList ( $"Could not set camera to DHCP please check camera.{Level.ERROR}" ) ;
2025-11-04 12:56:16 +00:00
return false ;
}
2025-12-02 12:59:40 +00:00
MainForm . Instance . AddToActionsList ( $"Camera successfully set to DHCP.{Level.Success}" ) ;
2025-11-04 12:56:16 +00:00
return true ;
2025-09-02 15:32:24 +01:00
}
2025-11-04 12:56:16 +00:00
2025-11-04 14:29:38 +00:00
public static async Task UploadWonwooSet ( string ipAddress , bool isIR )
{
string fileToUpload = null ;
using OpenFileDialog openFileDialog1 = new ( )
{
2025-11-26 14:39:07 +00:00
InitialDirectory = GoogleAPI . GoogleDrivePath ,
2025-11-04 14:29:38 +00:00
Filter = "CSV files (*.csv)|*.csv" ,
FilterIndex = 0
} ;
if ( openFileDialog1 . ShowDialog ( ) = = DialogResult . OK )
fileToUpload = openFileDialog1 . FileName ;
else
{
2025-11-26 14:39:07 +00:00
MainForm . Instance . AddToActionsList ( "File selection cancelled." , Level . WARNING ) ;
2025-11-04 14:29:38 +00:00
return ;
}
//Filename validation
string filename = Path . GetFileName ( fileToUpload ) . ToUpper ( ) ;
2025-11-11 13:49:42 +00:00
if ( ( isIR & & ! filename . Contains ( "IR" ) ) | | ( ! isIR & & ! filename . Contains ( "OV" ) ) )
2025-11-04 14:29:38 +00:00
{
2025-11-26 14:39:07 +00:00
MainForm . Instance . AddToActionsList ( $"Incorrect file selected. Expected {(isIR ? " IR " : " OV ")} file" , Level . WARNING ) ;
2025-11-04 14:29:38 +00:00
return ;
}
string [ ] lines = File . ReadAllLines ( fileToUpload ) ;
for ( int i = 1 ; i < lines . Length ; i + + )
{
string [ ] parts = lines [ i ] . Split ( ',' ) . Select ( p = > p . Trim ( ) ) . ToArray ( ) ;
if ( parts . Length < 3 )
{
2025-11-26 14:39:07 +00:00
MainForm . Instance . AddToActionsList ( $"Invalid row format at line {i + 1}" , Level . WARNING ) ;
2025-11-04 14:29:38 +00:00
continue ;
}
string name = parts [ 0 ] ;
string command = parts [ 1 ] ;
string expectedResponse = parts [ 2 ] ;
// VISCA format check
if ( ! RegexCache . VISCAAPIRegex ( ) . IsMatch ( command ) )
{
2025-11-26 14:39:07 +00:00
MainForm . Instance . AddToActionsList ( $"{name}: Invalid VISCA command ({command})" , Level . WARNING ) ;
2025-11-04 14:29:38 +00:00
continue ; // do not send it if bad
}
string result = await APIHTTPVISCA ( ipAddress , command , isIR ) ;
if ( result . Contains ( expectedResponse ) )
2025-12-02 12:59:40 +00:00
MainForm . Instance . AddToActionsList ( $"{name}: Success ({(isIR ? " IR " : " Colour ")})" , Level . Success ) ;
2025-11-04 14:29:38 +00:00
else
2025-11-26 14:39:07 +00:00
MainForm . Instance . AddToActionsList ( $"{name}: Unexpected response ({result})" , Level . ERROR ) ;
2025-11-04 14:29:38 +00:00
await Task . Delay ( 150 ) ;
}
2025-12-02 12:59:40 +00:00
MainForm . Instance . AddToActionsList ( $"Upload complete ({(isIR ? " IR " : " Colour ")})." , Level . Success ) ;
2025-11-26 14:39:07 +00:00
}
public static async void UploadBlob ( List < Camera > soakCameraList )
{
const string networkFolderPath = @"G:\Shared drives\MAV Production\MAV_146_AiQ_Mk2\Flexi" ;
string fileToUpload = null ;
if ( await MainForm . Instance . DisplayQuestion ( "Do you want the latest Flexi version from the MAV Production folder?" ) )
{
fileToUpload = Directory . GetFiles ( networkFolderPath , "*.blob" ) . OrderByDescending ( File . GetLastWriteTime ) . FirstOrDefault ( ) ;
if ( fileToUpload = = null )
{
2025-12-02 12:59:40 +00:00
MainForm . Instance . AddToActionsList ( "No .blob file found in the directory." , Level . WARNING ) ;
2025-11-26 14:39:07 +00:00
return ;
}
}
else
{
using OpenFileDialog openFileDialog1 = new ( )
{
InitialDirectory = networkFolderPath ,
Filter = "Blob files (*.blob)|*.blob" ,
FilterIndex = 0
} ;
if ( openFileDialog1 . ShowDialog ( ) = = DialogResult . OK )
fileToUpload = openFileDialog1 . FileName ;
else
{
MainForm . Instance . AddToActionsList ( "File selection cancelled." , Level . WARNING ) ;
return ;
}
}
string fileName = Path . GetFileName ( fileToUpload ) ;
MainForm . Instance . AddToActionsList ( $"Selected file to upload: {fileToUpload}" , Level . LOG ) ;
foreach ( Camera ? cam in soakCameraList . Where ( c = > c . IsChecked ) )
{
string apiUrl = $"http://{cam.IP}/upload/software-update/2" ;
Network . Initialize ( "developer" , cam . DevPass ) ;
await Task . Delay ( 1000 ) ; // Gives extra time to allow for Network to initialize
MainForm . Instance . AddToActionsList ( $"Uploading to {cam.IP}..." , Level . LOG ) ;
string result = await SendBlobFileUpload ( apiUrl , fileToUpload , fileName ) ;
// Retry once on transient errors
if ( result . Contains ( "Error while copying content to a stream" ) | | result . Contains ( "Timeout" ) )
{
MainForm . Instance . AddToActionsList ( $"Retrying upload to {cam.IP}..." , Level . WARNING ) ;
await Task . Delay ( 1000 ) ;
result = await SendBlobFileUpload ( apiUrl , fileToUpload , fileName ) ;
}
2025-12-02 12:59:40 +00:00
MainForm . Instance . AddToActionsList ( $"Upload result for {cam.IP}: {result}" ) ;
2025-11-26 14:39:07 +00:00
await Task . Delay ( 500 ) ;
}
2025-11-04 14:29:38 +00:00
}
2025-09-02 15:32:24 +01:00
}
//Items recieved in Versions API
public class Versions
{
public string version { get ; set ; } = string . Empty ;
public string revision { get ; set ; } = string . Empty ;
public string buildtime { get ; set ; } = string . Empty ;
public string MAC { get ; set ; } = string . Empty ;
public int timeStamp { get ; set ; }
public string proquint { get ; set ; } = string . Empty ;
[JsonProperty("Serial No.")]
public string Serial { get ; set ; } = string . Empty ;
[JsonProperty("Model No.")]
public string Model { get ; set ; } = string . Empty ;
}
// Items returned in the diagnostics api
public class Diags
{
[JsonProperty("version")]
public string FlexiVersion { get ; set ; } = string . Empty ;
[JsonProperty("revision")]
public string FlexiRevision { get ; set ; } = string . Empty ;
public string serialNumber { get ; set ; } = string . Empty ;
public string modelNumber { get ; set ; } = string . Empty ;
public string MAC { get ; set ; } = string . Empty ;
public long timeStamp { get ; set ; }
public Licenses licenses { get ; set ; } = new Licenses ( ) ;
[JsonProperty("internalTemperature")]
public double IntTemperature { get ; set ; }
[JsonProperty("cpuUsage")]
public double CPUusage { get ; set ; }
public List < int > trim { get ; set ; } = [ ] ;
public bool zoomLock { get ; set ; }
public Module IRmodule { get ; set ; } = new Module ( ) ;
public Module OVmodule { get ; set ; } = new Module ( ) ;
[JsonProperty("ledChannelVoltages")]
public List < double > LedVoltage { get ; set ; } = [ ] ;
[JsonProperty("ledChannelCurrents")]
public List < double > LedCurrent { get ; set ; } = [ ] ;
}
public class Module
{
public int zoom { get ; set ; }
public int expMode { get ; set ; }
public string firmwareVer { get ; set ; } = string . Empty ;
}
public class Trim
{
public int infraredX { get ; set ; }
public int infraredY { get ; set ; }
public int colourX { get ; set ; }
public int colourY { get ; set ; }
}
public class NetworkConfig
{
public Property propDHCP { get ; set ; } = new Property ( ) ;
}
public class Property
{
public string Value { get ; set ; } = string . Empty ;
}
}