2025-09-02 15:32:24 +01:00
|
|
|
|
using System.Net;
|
|
|
|
|
using System.Net.NetworkInformation;
|
|
|
|
|
using System.Net.Sockets;
|
|
|
|
|
using System.Text;
|
|
|
|
|
|
|
|
|
|
namespace AiQ_GUI
|
|
|
|
|
{
|
|
|
|
|
public class Network
|
|
|
|
|
{
|
|
|
|
|
public static HttpClient? SingleHTTPClient;
|
|
|
|
|
public static HttpClient Client => SingleHTTPClient ?? throw new InvalidOperationException("Client not initialized.");
|
|
|
|
|
|
|
|
|
|
public static void Initialize(string username, string password)
|
|
|
|
|
{
|
2025-09-16 10:42:51 +01:00
|
|
|
|
HttpClientHandler handler = new()
|
2025-09-02 15:32:24 +01:00
|
|
|
|
{
|
|
|
|
|
MaxConnectionsPerServer = 25,
|
|
|
|
|
Credentials = new NetworkCredential(username, password)
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
SingleHTTPClient?.Dispose(); // Dispose old client if needed
|
|
|
|
|
SingleHTTPClient = new HttpClient(handler)
|
|
|
|
|
{
|
|
|
|
|
Timeout = TimeSpan.FromSeconds(20)
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Handles get and post to the camera API
|
|
|
|
|
public static async Task<string> SendHttpRequest(string url, HttpMethod method, int? Timeout, string? jsonData = null, string[]? Headers = null)
|
|
|
|
|
{
|
|
|
|
|
try
|
|
|
|
|
{
|
2025-09-16 10:42:51 +01:00
|
|
|
|
HttpRequestMessage request = new(method, url);
|
2025-09-02 15:32:24 +01:00
|
|
|
|
|
|
|
|
|
if (jsonData != null) // Fills in the body of the request if jsonData is provided
|
|
|
|
|
request.Content = new StringContent(jsonData, Encoding.UTF8, "application/json");
|
|
|
|
|
|
|
|
|
|
if (Headers != null) // Fills in the headers of the request if Headers are provided
|
|
|
|
|
{
|
|
|
|
|
foreach (string Header in Headers)
|
|
|
|
|
{
|
|
|
|
|
string[] parts = Header.Split(':');
|
|
|
|
|
|
|
|
|
|
if (parts.Length == 2)
|
|
|
|
|
request.Headers.Add(parts[0].Trim(), parts[1].Trim());
|
|
|
|
|
else
|
|
|
|
|
throw new ArgumentException($"Invalid header format: {Header}");
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-09-16 10:42:51 +01:00
|
|
|
|
int timeoutMs = (Timeout ?? 10) * 1000; // Convert from seconds to ms, default to 10s if null
|
|
|
|
|
using CancellationTokenSource cts = new(timeoutMs);
|
2025-09-02 15:32:24 +01:00
|
|
|
|
|
|
|
|
|
using HttpResponseMessage response = await Client.SendAsync(request, HttpCompletionOption.ResponseHeadersRead, cts.Token);
|
|
|
|
|
response.EnsureSuccessStatusCode();
|
|
|
|
|
|
|
|
|
|
return await response.Content.ReadAsStringAsync(cts.Token); // Use token here too if you're chaining timeouts
|
|
|
|
|
}
|
|
|
|
|
catch (TaskCanceledException ex)
|
|
|
|
|
{
|
|
|
|
|
return $"HTTP error calling {url}: {ex.Message}";
|
|
|
|
|
}
|
|
|
|
|
catch (HttpRequestException ex)
|
|
|
|
|
{
|
|
|
|
|
return $"HTTP error calling {url}: {ex.Message}";
|
|
|
|
|
}
|
|
|
|
|
catch (Exception ex)
|
|
|
|
|
{
|
|
|
|
|
return $"Unexpected error calling {url}: {ex.Message}";
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public async static Task<IList<string>> SearchForCams()
|
|
|
|
|
{
|
|
|
|
|
const int sendPort = 6666;
|
|
|
|
|
const int receivePort = 6667;
|
|
|
|
|
IList<string> FoundCams = [];
|
|
|
|
|
|
|
|
|
|
byte[] discoveryPacket = [0x50, 0x4f, 0x4c, 0x4c, 0xaf, 0xb0, 0xb3, 0xb3, 0xb6, 0x01, 0xa8, 0xc0, 0x0b, 0x1a, 0x00, 0x00];
|
|
|
|
|
|
|
|
|
|
async Task SendAndListen(IPAddress localIp)
|
|
|
|
|
{
|
|
|
|
|
using (UdpClient sender = new(new IPEndPoint(localIp, sendPort)))
|
|
|
|
|
{
|
|
|
|
|
sender.EnableBroadcast = true;
|
|
|
|
|
sender.Connect(new IPEndPoint(IPAddress.Broadcast, sendPort));
|
|
|
|
|
sender.Send(discoveryPacket, discoveryPacket.Length);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
using UdpClient receiver = new(receivePort); // Listen for replies on fixed port
|
|
|
|
|
receiver.Client.ReceiveTimeout = 750;
|
|
|
|
|
|
|
|
|
|
DateTime timeout = DateTime.Now.AddMilliseconds(750);
|
|
|
|
|
try
|
|
|
|
|
{
|
|
|
|
|
while (DateTime.Now < timeout)
|
|
|
|
|
{
|
|
|
|
|
if (receiver.Available > 0)
|
|
|
|
|
{
|
|
|
|
|
UdpReceiveResult result = await receiver.ReceiveAsync();
|
|
|
|
|
byte[] recvBuffer = result.Buffer;
|
|
|
|
|
|
|
|
|
|
if (recvBuffer.Length >= 52) // Safety check
|
|
|
|
|
{
|
|
|
|
|
byte[] ipBytes = recvBuffer.Skip(recvBuffer.Length - 52).Take(4).Reverse().ToArray();
|
|
|
|
|
string ipToAdd = string.Join(".", ipBytes);
|
|
|
|
|
|
|
|
|
|
if (!FoundCams.Contains(ipToAdd))
|
|
|
|
|
FoundCams.Add(ipToAdd);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
await Task.Delay(50); // brief wait to allow data in
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
catch (SocketException ex) when (ex.SocketErrorCode == SocketError.TimedOut)
|
|
|
|
|
{
|
|
|
|
|
// No data received in time — normal case
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Get first IPv4 interface (non-loopback)
|
|
|
|
|
foreach (IPAddress ip in Dns.GetHostEntry(Dns.GetHostName()).AddressList)
|
|
|
|
|
{
|
|
|
|
|
try
|
|
|
|
|
{
|
|
|
|
|
await SendAndListen(ip);
|
|
|
|
|
}
|
|
|
|
|
catch { }
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return FoundCams;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Ping to make sure devices are connected to the network, be aware it isn't consistant across subnets.
|
|
|
|
|
public async static Task<bool> PingIP(string ipAddress)
|
|
|
|
|
{
|
|
|
|
|
if (RegexCache.RegexIPPattern().IsMatch(ipAddress))
|
|
|
|
|
{
|
|
|
|
|
try
|
|
|
|
|
{
|
|
|
|
|
Ping myPing = new();
|
|
|
|
|
PingReply reply = await myPing.SendPingAsync(ipAddress, 1000); // Timeout is 1s
|
|
|
|
|
return reply.Status == IPStatus.Success;
|
|
|
|
|
}
|
|
|
|
|
catch (PingException ex) { MainForm.Instance.AddToActionsList($"PingException: Unable to ping {ipAddress}. Reason: {ex.Message}"); }
|
|
|
|
|
catch (SocketException ex) { MainForm.Instance.AddToActionsList($"SocketException: Network error while pinging {ipAddress}. Reason: {ex.Message}"); }
|
|
|
|
|
catch (Exception ex) { MainForm.Instance.AddToActionsList($"Unexpected error while pinging {ipAddress}: {ex.Message}"); }
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Start a thread that pings each IP in the hardware accessories menu
|
|
|
|
|
public static async Task PingAndUpdateUI(string IP, Label label, string ItemName, ToolTip TT, Button[]? buttonsToEnable = null)
|
|
|
|
|
{
|
|
|
|
|
bool isAvailable = await PingIP(IP);
|
|
|
|
|
|
|
|
|
|
label.Invoke(() =>
|
|
|
|
|
{
|
|
|
|
|
if (isAvailable)
|
|
|
|
|
{
|
|
|
|
|
label.Text = "✔";
|
|
|
|
|
label.ForeColor = Color.ForestGreen;
|
|
|
|
|
TT.SetToolTip(label, ItemName + " Available");
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
label.Text = "❌";
|
|
|
|
|
label.ForeColor = Color.Red;
|
|
|
|
|
TT.SetToolTip(label, "No ping from " + ItemName);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (buttonsToEnable != null)
|
|
|
|
|
{
|
|
|
|
|
foreach (Button button in buttonsToEnable)
|
|
|
|
|
button.Enabled = isAvailable;
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|