Add project files.

This commit is contained in:
2025-09-02 15:32:24 +01:00
parent 6a633eed7a
commit 50bb9c9781
53 changed files with 9925 additions and 0 deletions

109
HWAccessories/Ez.cs Normal file
View File

@@ -0,0 +1,109 @@
using System.Text;
namespace AiQ_GUI
{
internal class Ez
{
public static string PoEPowerIP = "";
// Controls the EzOutlet state (ON/OFF)
public async static Task<bool> EzOutletControl(string state)
{
try
{
string currentState = await GetEzOutletState();
if (string.IsNullOrEmpty(currentState)) return false;
// Check if a state change is required
if ((currentState.Contains("1,") && state == "OFF") || (currentState.Contains("0,") && state == "ON"))
{
// Uses XOR so if only one is true. As if both are true something is wrong as it can't have both versions of code and if none are true then it failed
bool invertResult = await InvertEzOutletState();
bool ezOutlet2Result = EzOutlet2Control(ToTitleCase(state)).Result;
return invertResult ^ ezOutlet2Result;
}
return true; // State already matches
}
catch (Exception ex)
{
MainForm.Instance.AddToActionsList($"Error: {ex.Message}");
return false;
}
}
// Controls EzOutlet2 state by sending an HTTP request
public async static Task<bool> EzOutlet2Control(string state)
{
try
{
string upperState = state.ToUpper();
string URL = $"http://{PoEPowerIP}:100/overview?onoff={upperState}";
// Send ON/OFF command twice to handle login prompt, admin/admin creds true
await SendHttpRequest(URL, true);
// TODO Is twice needed??
await SendHttpRequest(URL, true);
await Task.Delay(1000); // Check flags every 1000ms
// Verify the state change
string response = await SendHttpRequest(URL.Substring(0, URL.IndexOf('?')), false); // Cut down to only get up to overview
return response.Contains($"Status: {state} Mode: Manual Control");
}
catch
{
return false;
}
}
// Inverts the current EzOutlet state
public async static Task<bool> InvertEzOutletState()
{
try
{
string response = await SendHttpRequest($"http://{PoEPowerIP}:100/invert.cgi", false);
return response.Contains(','); // Completed message includes comma
}
catch
{
return false;
}
}
// Helper to get the current EzOutlet state
private async static Task<string> GetEzOutletState()
{
return await SendHttpRequest($"http://{PoEPowerIP}:100/socket.cgi", false);
}
private static async Task<string> SendHttpRequest(string url, bool useCreds)
{
try
{
HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Get, url);
if (useCreds)
{
byte[] byteArray = Encoding.ASCII.GetBytes("admin:admin");
request.Headers.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Basic", Convert.ToBase64String(byteArray));
}
using HttpResponseMessage response = await Network.Client.SendAsync(request);
response.EnsureSuccessStatusCode();
return await response.Content.ReadAsStringAsync();
}
catch (Exception ex)
{
MainForm.Instance.AddToActionsList($"Error: {ex.Message}");
return $"Error: {ex.Message}";
}
}
// Converts a string to TitleCase (first letter uppercase, rest lowercase)
private static string ToTitleCase(string input)
{
return string.IsNullOrEmpty(input) || input.Length == 1 ? input : char.ToUpper(input[0]) + input.Substring(1).ToLower();
}
}
}

111
HWAccessories/PSU.cs Normal file
View File

@@ -0,0 +1,111 @@
using System.Diagnostics;
using System.Net.Sockets;
using System.Text;
namespace AiQ_GUI
{
public class PSU
{
public static string PSUIP = "";
public static string SendDataPsu(string dataTx, string IPAddress)
{
dataTx += "\n"; // Ensure command ends with newline
Socket psuSocket = new(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
if (!psuSocket.Connected)
{
try
{
IAsyncResult result = psuSocket.BeginConnect(IPAddress, 9221, null, null);
bool success = result.AsyncWaitHandle.WaitOne(1000, true);
if (!psuSocket.Connected)
{
psuSocket.Close();
return "Error: Failed to connect to PSU";
}
}
catch
{
psuSocket.Close();
return "Error: Caught failure to connect to PSU";
}
}
try
{
psuSocket.Send(Encoding.ASCII.GetBytes(dataTx));
}
catch
{
return "Error: Failed to send bytes to PSU";
}
if (dataTx.Contains('?'))
{
byte[] data = new byte[1024];
psuSocket.ReceiveTimeout = 500;
try
{
int receivedDataLength = psuSocket.Receive(data);
psuSocket.Close();
return Encoding.ASCII.GetString(data, 0, receivedDataLength);
}
catch { }
}
else
{
psuSocket.Close();
return "CmdDone";
}
psuSocket.Close();
return "Error: Failed to Send message to PSU.";
}
public static void PSU_OFF(string IPAddress)
{
SendDataPsu("OP1 0", IPAddress);
if (!SendDataPsu("OP1?", IPAddress).Contains("Error"))
{
MainForm.Instance.btnPsuOff.BackColor = System.Drawing.Color.Green;
MainForm.Instance.btnPsuOn.BackColor = MainForm.BtnColour;
}
else
MainForm.Instance.AddToActionsList("Cannot turn PSU off");
}
public static void PSU_ON(string IPAddress)
{
SendDataPsu("OP1 1", IPAddress);
if (!SendDataPsu("OP1?", IPAddress).Contains("Error"))
{
MainForm.Instance.btnPsuOn.BackColor = Color.Green;
MainForm.Instance.btnPsuOff.BackColor = MainForm.BtnColour;
}
else
MainForm.Instance.AddToActionsList("Cannot turn PSU off");
}
public static void DisplayState(string IPAddress)
{
string DataFromPSU = SendDataPsu("OP1?", IPAddress);
Debug.WriteLine(DataFromPSU);
if (DataFromPSU.Contains('1'))
{
MainForm.Instance.btnPsuOff.BackColor = MainForm.BtnColour;
MainForm.Instance.btnPsuOn.BackColor = Color.Green;
}
else if (DataFromPSU.Contains('0'))
{
MainForm.Instance.btnPsuOff.BackColor = Color.Green;
MainForm.Instance.btnPsuOn.BackColor = MainForm.BtnColour;
}
}
}
}

77
HWAccessories/Printer.cs Normal file
View File

@@ -0,0 +1,77 @@
using System.Net.Sockets;
namespace AiQ_GUI
{
internal class Printer
{
public static string ZebraIP = "";
// Sends a .prn string to the Zebra printer
public static bool PrintToZebra(string stringToPrint)
{
try
{
using TcpClient client = new();
client.Connect(ZebraIP, 9100); // Open connection
client.SendTimeout = 5000; // 5 second timeout
using StreamWriter writer = new(client.GetStream());
writer.Write(stringToPrint); // Write ZPL string to connection
writer.Flush(); // Flush ensures data is sent
return true;
}
catch
{
return false;
}
}
public static void PrintSerialLbl(string Model, string Serial, string Processor)
{
// Model and serial label with new MAV logo and QR code
// Has to be Zebra font in the Zebra designer to be a variable in the .prn file
// Replace model, serial and description in label before printing
string AiQLbl = "^XA~TA000~JSN^LT0^MNW^MTT^PON^PMN^LH0,0^JMA^PR2,2~SD15^JUS^LRN^CI0^XZ\n" +
"^XA\n" +
"^MMT\n" +
"^PW384\n" +
"^LL0096\n" +
"^LS0\n" +
"^FO256,0^GFA,01536,01536,00016,:Z64:\n" +
"eJzN072K20AQB/ARW6gRp1aCYL2CzDU2BJtUeQ2BC7cBF+eAyCpsiBuB2gSO82skTZAwRM1hv4LMQVRGxzYSLJ6sOa2+sFPfdD+W0ez+tQvw+ougoDe09B3MLhsMb+9XaIF32U74AYPZnSVqzx/X0iu79ceBabyY+P/xMsnwfuCwsRO+T2J8aOaBobtkRjv76xvFcnz/e9U5X8+yvIJWrBPJ0O9K+rxpVB02p4UW2ap/YJLz/CBSfiK1075hKuy9A+SX2dg6e6RsC+vLCMiDXvfr8RK8OT/GnvKfnmGe3bzRopN7xSPyFUBnP4zaM7I52xr4m1HPjw06R1hlt7Uz41PPVeG53jSwd8FlTwxLEGSlykv61LVr2NK7xgnPE6YXI5XP0EA3MU0Ky7xsmW8l7pAja1yeFtK7dl36qXFs5nJ+YN2+7JdkfUMVYLKVH1HzKjmtYzJ567tOwhEbr/v2S9dsDcizPUzDkQvKx66JJvSC6Azr/IY+H1iLdPaZZ623OtM6JlFreb/XyMC0H73Llu9jLCqmxVfshNSl+RMWteX7dGnKB5brmbL1LFKmHa6ajrcaHjv9PYNBtU3KvydBa5ZydZ9e/Jf/VEbhxIBRpf7nwK+p/gEmr4ir:A94D\n" +
"^FO0,0^GFA,01280,01280,00020,:Z64:\n" +
"eJztz7ENAyEMQFGjKygZgVFuNBiNUxbhdAsQpQgFwrFDlARwkzr3JTePAhvg/8LkMhisKoGOjTRS4BAv9PIyw+Zp9vI2y7bRXAvYMFiuvR3NVt9sDTaaI8OS3ce8DWyKzMG3JbLVS1Yns0GwrUxm9t54P/59NH0T7J4mW1Cy2BnfpgZLTwuTgWh+NicZnJ392gMKlLLa:A7F4\n" +
$"^FT159,84^A0N,25,24^FH\\^FD{Serial}^FS\n" +
"^FT26,56^A0N,14,14^FH\\^FDModel:^FS\n" +
$"^FT159,32^A0N,25,28^FH\\^FDAiQ {Processor}^FS\n" +
"^FT159,56^A0N,14,14^FH\\^FDSerial Number:^FS\n" +
$"^FT26,84^A0N,25,24^FH\\^FD{Model}^FS\n" +
"^PQ1,0,1,Y^XZ";
if (!PrintToZebra(AiQLbl))
MainForm.Instance.AddToActionsList("Error printing AiQ label");
}
public static void PrintGBLbl()
{
// New Label with UKCA logo
const string MadeInGB = "^XA~TA000~JSN^LT0^MNW^MTT^PON^PMN^LH0,0^JMA^PR2,2~SD15^JUS^LRN^CI0^XZ\n" +
"^XA\n" +
"^MMT\n" +
"^PW384\n" +
"^LL0096\n" +
"^LS0\n" +
"^FO192,0^GFA,01152,01152,00012,:Z64:\n" +
"eJzt0DEKgDAMBdCUDo49Qm9ij2aP1qN4BEeHYvziL0TURRcHA4VXaEryRf76agWciVZcaQ8nuoMHOsBKxypO2ZqK+Jke0NK+XNAy0hUuOx3exkzjbZSzPdw3j0cnzY999efdDHZOO/9hL7tvyuIrHSfEyGbk5vQiT5uzzX+bwDX/9a5WFTRBPA==:FF83\n" +
"^FO320,0^GFA,00768,00768,00008,:Z64:\n" +
"eJxjYBjOgAWIHYCYA4gNGOwYBEBs9v8HFEBycnY2IDkmOwabBiDNaP//L1jTBAYxMB3AIAOmDaC0AIMNmJawLwDTMvUTwLQQswCYlmRSANMcLVA6A0obQOkKVHlOqHouRgjNw/AAIs+QALEHSuswfADTLow/QBRjC2MNiGZi//8L7Ec2xo8MUD82gJ3JANaoAHY65UD+////D0ighxYAAPB/L1Q=:B1A7\n" +
"^FO0,0^GFA,02688,02688,00028,:Z64:\n" +
"eJzt071OwzAQB/BEHjL6ETzwAowMSH4Y3gMXdejII/RRcJWBsY9QVx0YsdTFEsbH3fmjSaGMDKhXZ/oldc73T9dd61r/sLTXcACnu8ENXsBCAHRGLhRbyLaQTqKFbDZbUmxWsaVsLhtINvwpemjJ5osNv9jI5o1TEW1kC8X2bAGvKLweQ4cWi3m2z2zqwJaqGTLAKwkvs0GxqIuZRzJIZGsyBUmRGXCPRvhhahJAsr27NLFdsRWZRtPNdkBGLZGprYt6WffbsvUAezL5vMdDqe/5ynYL4HlGEm2s/Y1sdwbCw8nMODVs8IbtAHhA5Tw3bPeKZnayPAfLphVAtjd6SShzN2wrujfbcKxzt5ptSXuwObQwsz1t4y5kEOB40Qx8XDLMJ1wyzCfNPOJd1FASoUs115hPMrC0qtmWTzZeaENEcy2fbJ5WNd/yObc0Md8skEmy0PJJtvRaBN3h2ZHlXDPPDNBSNTUz9YIG1WTZr9oitcxD7W9g02zrmXEPZIpsN7PQzKaS66k5Mhx6Krmu9hTprMnApZLrn208N8X/id/AueHnK32zzfm7DGQ9m/1m1HsfRUg116fe+cz6wKZnZvERNBH6WO1a1/qD+gLCtYx7:603A\n" +
"^FO256,0^GFA,01152,01152,00012,:Z64:\n" +
"eJzt0DEKwzAMBVAJDxlzBB/F18pQcI7mo/gIGlVwqzpBrkSbDoGO+V4egXy+DXDlv8EKtMCyO1QkRjZPZJ6LOa7mDOZmfrBZyLk4rz++1+9/kZDuzcxPtQhx0suIVI7FPGtRlsJBi6LAuOPpRKFpDM1CcYzrG5Lw21mNu5tuBrj1M9zUoU/kkxbt9N46D83O21tpD3hn5+R85SMvvQiYIA==:00E3\n" +
"^PQ1,0,1,Y^XZ";
if (!PrintToZebra(MadeInGB))
MainForm.Instance.AddToActionsList("Error printing GB label");
}
}
}

88
HWAccessories/TestTube.cs Normal file
View File

@@ -0,0 +1,88 @@
using Newtonsoft.Json;
namespace AiQ_GUI
{
internal class TestTube
{
public static string TTPiPicoIP = "";
// Gets the API JSON from the Test Tube Pi Pico
public static async Task<TestTubeAPI> GetAPI()
{
string url = $"http://{TTPiPicoIP}/api";
try
{
using CancellationTokenSource cts = new CancellationTokenSource(2000);
string JSON = await Network.Client.GetStringAsync(url, cts.Token);
TestTubeAPI? result = JsonConvert.DeserializeObject<TestTubeAPI>(JSON);
if (result == null)
{
MainForm.Instance.AddToActionsList("JSON deserialization from test tube returned null");
return new TestTubeAPI();
}
return result;
}
catch (TaskCanceledException)
{
MainForm.Instance.AddToActionsList($"Timeout calling {url} after 2s.");
return new TestTubeAPI();
}
catch (Exception ex)
{
MainForm.Instance.AddToActionsList($"Error in GetAPI: {ex.Message}");
return new TestTubeAPI();
}
}
// Sets LED's to medium or safe depending on whether the switch is pressed down or not
public static async Task<bool> CheckInTestTube(string CamIP)
{
if (await InTestTube()) // Switch pressed down
{
string LEDreply = await FlexiAPI.APIHTTPLED(CamIP, LEDPOWER.MID); // Set LED's to medium (0x30)
if (!LEDreply.Contains("Power levels set successfully"))
MainForm.Instance.AddToActionsList($"LED level could not be set: {LEDreply}");
return true;
}
else
{
string LEDreply = await FlexiAPI.APIHTTPLED(CamIP, LEDPOWER.SAFE); // Set LED's to safe (0x0E)
if (!LEDreply.Contains("Power levels set successfully"))
MainForm.Instance.AddToActionsList($"LED level could not be set: {LEDreply}");
await MainForm.Instance.DisplayOK("Please put camera in test tube then click OK"); // Awaited till OK has been clicked
return await InTestTube(); // Check again after user says they have put it in the test tube
}
}
// Checks whether the TestTube has the switch presses down or not
public async static Task<bool> InTestTube()
{
TestTubeAPI TTAPI = await GetAPI();
try
{
if (TTAPI.Switch[0] == true) // Switch not pressed down
return true;
}
catch (Exception ex)
{
MainForm.Instance.AddToActionsList($"Error checking Test Tube switch state: {ex.Message}");
}
return false;
}
}
public class TestTubeAPI
{
public List<bool> Switch { get; set; } = [];
}
}