408 lines
16 KiB
C#
408 lines
16 KiB
C#
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)
|
|
{
|
|
return $"Error during GET request: {ex.Message}";
|
|
}
|
|
}
|
|
|
|
// 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);
|
|
string url = $"http://{IPAddress}/api/update-config";
|
|
return await Network.SendHttpRequest(url, HttpMethod.Post, 2, JSONdata);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
return $"Error in HTTP_Update: {ex.Message}";
|
|
}
|
|
}
|
|
|
|
// For 'fetch-config' getting info from AiQ
|
|
public static async Task<string> HTTP_Fetch(string ID, string IPAddress, int? Timeout = 2)
|
|
{
|
|
try
|
|
{
|
|
string url = $"http://{IPAddress}/api/fetch-config?id={ID}";
|
|
return await Network.SendHttpRequest(url, HttpMethod.Get, Timeout);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
return $"Error in HTTP_Fetch: {ex.Message}";
|
|
}
|
|
}
|
|
|
|
// 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)
|
|
{
|
|
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}";
|
|
return await APIHTTPRequest(suffix, IPAddress);
|
|
}
|
|
|
|
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");
|
|
|
|
MultipartFormDataContent content = new() { { streamContent, "upload", fileName } };
|
|
|
|
using HttpResponseMessage response = await Network.Client.PostAsync(url, content);
|
|
string responseBody = await response.Content.ReadAsStringAsync();
|
|
|
|
if (!response.IsSuccessStatusCode)
|
|
return $"Server returned {(int)response.StatusCode}: {response.ReasonPhrase}. Details: {responseBody}";
|
|
|
|
return responseBody;
|
|
}
|
|
catch (TaskCanceledException)
|
|
{
|
|
return $"Timeout uploading to {url}.";
|
|
}
|
|
catch (HttpRequestException ex)
|
|
{
|
|
return $"HTTP error uploading to {url}: {ex.Message}";
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
return $"Unexpected error uploading to {url}: {ex.Message} {(ex.InnerException?.Message ?? "")}";
|
|
}
|
|
}
|
|
|
|
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"))
|
|
{
|
|
MainForm.Instance.AddToActionsList("Error talking to Flexi, are you sure this is an AiQ?" + Environment.NewLine + JSON);
|
|
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
|
|
}
|
|
}
|
|
|
|
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.")
|
|
&& !await MainForm.Instance.DisplayQuestion("Could not set zoomlock on" + Environment.NewLine + "Set Zoomlock to on then click YES. Click NO to restart."))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
public static async Task<bool> ZoomModules(string VISCAInput, string IPAddress)
|
|
{
|
|
// Populate the VISCA command with the four zoom characters
|
|
string VISCA = $"810104470{VISCAInput[0]}0{VISCAInput[1]}0{VISCAInput[2]}0{VISCAInput[3]}FF";
|
|
|
|
Task<string> TS1 = APIHTTPVISCA(IPAddress, VISCA, true);
|
|
Task<string> TS2 = APIHTTPVISCA(IPAddress, VISCA, false);
|
|
await Task.WhenAll(TS1, TS2);
|
|
|
|
const string ExpReply = "9041FF9051FF";
|
|
if (TS1.Result == ExpReply && TS1.Result == ExpReply)
|
|
return true;
|
|
|
|
return false;
|
|
}
|
|
|
|
public static async Task SetTrim(string IPAddress, string LblTxt, int RetryCount = 0) // Sets trim by getting plate postion as metric
|
|
{
|
|
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
|
|
{
|
|
MainForm.Instance.AddToActionsList("Error reading trim JSON - " + trimData);
|
|
return;
|
|
}
|
|
|
|
// Check no value is -1 (no plate found) or if the positions are identical (one plate found). If it is then try again 3 times
|
|
if (new[] { trim.infraredX, trim.infraredY, trim.colourX, trim.colourY }.Any(value => value == -1)
|
|
|| (trim.infraredX == trim.colourX && trim.infraredY == trim.colourY))
|
|
{
|
|
if (RetryCount >= 3)
|
|
{
|
|
await MainForm.Instance.DisplayOK("Please align trim in webpage then click OK."); // Awaited till OK has been clicked
|
|
return;
|
|
}
|
|
|
|
await Task.Delay(5000); // Give 5 second delay for it to see a plate
|
|
await SetTrim(IPAddress, LblTxt, RetryCount++);
|
|
}
|
|
|
|
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
|
|
{
|
|
await MainForm.Instance.DisplayOK("Please centralise plate in view THEN press OK"); // Awaited till OK has been clicked
|
|
|
|
if (RetryCount >= 3)
|
|
{
|
|
await MainForm.Instance.DisplayOK("Please align trim in webpage then click OK."); // Awaited till OK has been clicked
|
|
return;
|
|
}
|
|
await Task.Delay(5000); // Give 5 second delay for it to see a plate
|
|
await SetTrim(IPAddress, LblTxt, RetryCount++);
|
|
}
|
|
}
|
|
|
|
// 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\"}},"))
|
|
MainForm.Instance.AddToActionsList("Could not set camera trim");
|
|
}
|
|
|
|
// 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)
|
|
{
|
|
MainForm.Instance.AddToActionsList($"Error in getting network config from camera: {ex.Message}");
|
|
return null; // Return empty string if there is an error
|
|
}
|
|
}
|
|
|
|
// Knowing the format this builds the message to send to AiQ
|
|
public static string BuildJsonUpdate(string[,] jsonData, string id)
|
|
{
|
|
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
|
|
public async static Task ChangeNetworkToDHCP(string IPAddress)
|
|
{
|
|
string[,] TEST_JSON = { { "propDHCP", "true" } }; // Update GLOBAL--NetworkConfig with fixed IP and turn off DHCP
|
|
await HTTP_Update("GLOBAL--NetworkConfig", IPAddress, TEST_JSON);
|
|
// TODO - Check if this worked, if not return false
|
|
}
|
|
}
|
|
|
|
//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 appname { get; set; } = string.Empty;
|
|
public string MAC { get; set; } = string.Empty;
|
|
public int timeStamp { get; set; }
|
|
public string UUID { get; set; } = string.Empty;
|
|
public string proquint { get; set; } = string.Empty;
|
|
|
|
[JsonProperty("Serial No.")]
|
|
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;
|
|
}
|
|
}
|