- CbBxCamType now spans the full width of CbBxCameraModel - BtnTest_Click moved to the bottom of AiQ_GUI.cs for easier testing/navigation - Unused using directives removed across files - Login credentials factored out and reused in MobileAPI.cs - VLC.cs Capture function reviewed and superseded by TakeSnapshot - InsertCamTab implementation updated to avoid use of `var` - Misleading '// Non-ONVIF cameras' comment corrected - Added Level.Debug logging, accessible only to developers, for detailed diagnostics
228 lines
8.0 KiB
C#
228 lines
8.0 KiB
C#
using OnvifDiscovery;
|
|
using OnvifDiscovery.Models;
|
|
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 async static void Initialize(string username, string password)
|
|
{
|
|
HttpClientHandler handler = new()
|
|
{
|
|
MaxConnectionsPerServer = 25,
|
|
Credentials = new NetworkCredential(username, password)
|
|
};
|
|
|
|
SingleHTTPClient?.Dispose(); // Dispose old client if needed
|
|
SingleHTTPClient = new HttpClient(handler)
|
|
{
|
|
Timeout = TimeSpan.FromSeconds(20)
|
|
};
|
|
|
|
bool UP = false;
|
|
int i = 0;
|
|
|
|
while (!UP)
|
|
{
|
|
UP = await PingIP("10.10.10.137");
|
|
await Task.Delay(500);
|
|
if (i >= 5)
|
|
return; // Try 5 times max
|
|
|
|
i++;
|
|
}
|
|
}
|
|
|
|
// 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
|
|
{
|
|
HttpRequestMessage request = new(method, url);
|
|
|
|
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}");
|
|
}
|
|
}
|
|
|
|
int timeoutMs = (Timeout ?? 10) * 1000; // Convert from seconds to ms, default to 10s if null
|
|
using CancellationTokenSource cts = new(timeoutMs);
|
|
|
|
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;
|
|
const int discoveryTimeoutMs = 1000;
|
|
|
|
List<string> FoundCams = [];
|
|
HashSet<string> discoveredIPs = new(StringComparer.OrdinalIgnoreCase);
|
|
|
|
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);
|
|
receiver.Client.ReceiveTimeout = discoveryTimeoutMs;
|
|
|
|
DateTime timeout = DateTime.Now.AddMilliseconds(discoveryTimeoutMs);
|
|
|
|
while (DateTime.Now < timeout)
|
|
{
|
|
if (receiver.Available > 0)
|
|
{
|
|
UdpReceiveResult result = await receiver.ReceiveAsync();
|
|
byte[] recvBuffer = result.Buffer;
|
|
|
|
if (recvBuffer.Length >= 52)
|
|
{
|
|
byte[] ipBytes = recvBuffer
|
|
.Skip(recvBuffer.Length - 52)
|
|
.Take(4)
|
|
.Reverse()
|
|
.ToArray();
|
|
|
|
string ip = string.Join(".", ipBytes);
|
|
|
|
if (discoveredIPs.Add(ip))
|
|
FoundCams.Add(ip);
|
|
}
|
|
}
|
|
|
|
await Task.Delay(50);
|
|
}
|
|
}
|
|
|
|
foreach (IPAddress ip in Dns.GetHostEntry(Dns.GetHostName()).AddressList)
|
|
{
|
|
if (ip.AddressFamily != AddressFamily.InterNetwork)
|
|
continue;
|
|
|
|
try
|
|
{
|
|
await SendAndListen(ip);
|
|
}
|
|
catch { }
|
|
}
|
|
|
|
//ONVIF discovery
|
|
try
|
|
{
|
|
Discovery onvifDiscovery = new();
|
|
|
|
await foreach (DiscoveryDevice device in onvifDiscovery.DiscoverAsync(5))
|
|
{
|
|
string ip = device.Address;
|
|
|
|
// Already found via UDP → skip
|
|
if (!discoveredIPs.Add(ip))
|
|
continue;
|
|
|
|
// ONVIF-only camera
|
|
FoundCams.Add($"{ip} - Onvif");
|
|
}
|
|
}
|
|
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}{Level.WARNING}"); }
|
|
catch (SocketException ex) { MainForm.Instance.AddToActionsList($"SocketException: Network error while pinging {ipAddress}. Reason: {ex.Message}{Level.ERROR}"); }
|
|
catch (Exception ex) { MainForm.Instance.AddToActionsList($"Unexpected error while pinging {ipAddress}: {ex.Message}{Level.ERROR}"); }
|
|
}
|
|
|
|
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;
|
|
}
|
|
});
|
|
}
|
|
}
|
|
}
|