Files
robospot-camera-finder/MainForm.cs
2025-05-30 21:35:09 +02:00

512 lines
17 KiB
C#

using System.ComponentModel;
using System.Diagnostics;
using System.Net;
using System.Net.Sockets;
using System.Net.NetworkInformation;
using System.Text.Json;
using System.Text.Json.Serialization;
using System.Windows.Forms;
using static robospot_camera_finder.MainForm;
using System.Runtime.InteropServices;
using System.Text;
namespace robospot_camera_finder
{
public partial class MainForm : Form
{
public string VERSION = "1.0.0";
private BindingList<Camera> all_cameras = new();
public static string cam_username = "admin";
public static string cam_password = "RoboSpot10";
// UDP discovery constants
private const int SEND_PORT = 7701;
private const int RECEIVE_PORT = 7711;
private const string BROADCAST_ADDRESS = "10.255.255.255";
private const byte DEF_REQ_SCAN = 1;
private const byte RES_REQ_SCAN = 11;
private UdpClient sendClient;
private UdpClient receiveClient;
private bool isDiscovering = false;
public class Camera
{
public Camera(string name, string ip, string location, string serial)
{
camera_name = name;
camera_ip = ip;
camera_location = location;
camera_serial = serial;
}
public string camera_name { get; set; }
public string camera_ip { get; set; }
public string camera_location { get; set; }
public string camera_serial { get; set; }
}
[StructLayout(LayoutKind.Sequential, Pack = 1)]
public struct DataPacketIPv4
{
public byte mode;
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 18)]
public byte[] packet_id;
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 18)]
public byte[] mac_addr;
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 16)]
public byte[] ip_addr;
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 16)]
public byte[] subnetmask;
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 16)]
public byte[] gateway;
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 20)]
public byte[] password;
public byte reserved1;
public ushort port;
public byte status;
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 10)]
public byte[] device_name;
public byte reserved2;
public ushort http_port;
public ushort device_port;
public ushort tcp_port;
public ushort udp_port;
public ushort upload_port;
public ushort multicast_port;
public byte network_mode;
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 128)]
public byte[] ddns_url;
public byte reserved3;
}
public MainForm()
{
InitializeComponent();
this.Text += " - " + VERSION;
bool got_proper_ip = false;
foreach (var net_interface in NetworkInterface.GetAllNetworkInterfaces())
{
IPInterfaceProperties prop = net_interface.GetIPProperties();
foreach (var ip in prop.UnicastAddresses)
{
if (ip.Address.ToString().StartsWith("10."))
{
if (ip.IPv4Mask.ToString() == "255.0.0.0")
{
got_proper_ip = true;
}
}
}
}
if (!got_proper_ip)
{
DialogResult open_netsettings = MessageBox.Show("No Ethernet interface on the 10.0.0.0/8 subnet found. Cameras might not be detected. Do you want to open network settings ?", "Wrong IP configuration", MessageBoxButtons.YesNo, MessageBoxIcon.Warning, MessageBoxDefaultButton.Button1);
if (open_netsettings == DialogResult.Yes)
{
ProcessStartInfo startInfo = new ProcessStartInfo("NCPA.cpl");
startInfo.UseShellExecute = true;
Process.Start(startInfo);
}
}
lbMain.DataSource = all_cameras;
lbMain.DisplayMember = "camera_name";
lbMain.ValueMember = "camera_ip";
// Initialize UDP clients
InitializeUdpClients();
// Discover cameras
discover_cameras();
}
private void InitializeUdpClients()
{
try
{
sendClient = new UdpClient();
sendClient.EnableBroadcast = true;
receiveClient = new UdpClient(RECEIVE_PORT);
}
catch (Exception ex)
{
MessageBox.Show($"Failed to initialize UDP clients: {ex.Message}", "Network Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
}
}
private void add_camera(Camera new_camera)
{
if (all_cameras.Count == 0)
{
all_cameras.Add(new_camera);
}
else
{
Camera find_cam = all_cameras.FirstOrDefault(cam => cam.camera_serial == new_camera.camera_serial);
if (find_cam != null)
{
int i = all_cameras.IndexOf(find_cam);
all_cameras[i] = new_camera;
}
else
{
all_cameras.Add(new_camera);
}
}
}
private void lbMain_MouseDoubleClick(object sender, MouseEventArgs e)
{
if (lbMain.SelectedItem != null)
{
toolStripStatusLabel.Text = "Loading camera...";
Camera camera = all_cameras.FirstOrDefault<Camera>(cam => cam.camera_ip.ToString() == lbMain.SelectedValue.ToString());
Form viewer = new StreamViewer(lbMain.GetItemText(lbMain.SelectedItem), lbMain.SelectedValue.ToString(), camera, this);
viewer.Show();
toolStripStatusLabel.Text = "";
}
}
private void loadToolStripMenuItem_Click(object sender, EventArgs e)
{
string json = "";
OpenFileDialog openFile = new OpenFileDialog();
openFile.Filter = "json file (*.json)|*.json";
openFile.RestoreDirectory = true;
if (openFile.ShowDialog() == DialogResult.OK)
{
json = File.ReadAllText(openFile.FileName);
List<Camera> cameras_in_file = JsonSerializer.Deserialize<List<Camera>>(json);
foreach (Camera new_camera in cameras_in_file)
{
add_camera(new_camera);
}
}
}
private void saveToolStripMenuItem_Click(object sender, EventArgs e)
{
string json = JsonSerializer.Serialize<BindingList<Camera>>(all_cameras, new JsonSerializerOptions
{
WriteIndented = true
});
SaveFileDialog saveFile = new SaveFileDialog();
saveFile.Filter = "json file (*.json)|*.json";
saveFile.RestoreDirectory = true;
if (saveFile.ShowDialog() == DialogResult.OK)
{
File.WriteAllText(saveFile.FileName, json);
}
}
private void manualAddToolStripMenuItem_Click(object sender, EventArgs e)
{
Manual_IP_Form manualForm = new Manual_IP_Form();
if (manualForm.ShowDialog(this) == DialogResult.OK)
{
string ipAddress = manualForm.ipAddress;
Camera camera_to_add = new Camera("Camera " + ipAddress, ipAddress, "", "");
add_camera(camera_to_add);
}
}
// Helper method to convert structure to byte array
private byte[] StructureToByteArray(DataPacketIPv4 packet)
{
int size = Marshal.SizeOf(packet);
byte[] array = new byte[size];
IntPtr ptr = Marshal.AllocHGlobal(size);
try
{
Marshal.StructureToPtr(packet, ptr, true);
Marshal.Copy(ptr, array, 0, size);
}
finally
{
Marshal.FreeHGlobal(ptr);
}
return array;
}
// Helper method to convert byte array to structure
private DataPacketIPv4 ByteArrayToStructure(byte[] data)
{
DataPacketIPv4 packet = new DataPacketIPv4();
int size = Marshal.SizeOf(packet);
IntPtr ptr = Marshal.AllocHGlobal(size);
try
{
Marshal.Copy(data, 0, ptr, size);
packet = (DataPacketIPv4)Marshal.PtrToStructure(ptr, typeof(DataPacketIPv4));
}
finally
{
Marshal.FreeHGlobal(ptr);
}
return packet;
}
// Helper method to create a string array field with null termination
private byte[] CreateStringField(string value, int fieldSize)
{
byte[] field = new byte[fieldSize];
if (!string.IsNullOrEmpty(value))
{
byte[] valueBytes = Encoding.ASCII.GetBytes(value);
int copyLength = Math.Min(valueBytes.Length, fieldSize - 1); // Leave space for null terminator
Array.Copy(valueBytes, field, copyLength);
}
return field;
}
// Helper method to extract string from byte array
private string ExtractString(byte[] data)
{
if (data == null) return "";
// Find the null terminator
int nullIndex = Array.IndexOf(data, (byte)0);
if (nullIndex >= 0)
{
return Encoding.ASCII.GetString(data, 0, nullIndex);
}
else
{
return Encoding.ASCII.GetString(data).TrimEnd('\0');
}
}
// Create discovery packet
private DataPacketIPv4 CreateDiscoveryPacket()
{
DataPacketIPv4 packet = new DataPacketIPv4
{
mode = DEF_REQ_SCAN,
packet_id = CreateStringField("CAM_FINDER_" + DateTime.Now.Ticks.ToString(), 18),
mac_addr = new byte[18],
ip_addr = new byte[16],
subnetmask = new byte[16],
gateway = new byte[16],
password = new byte[20],
reserved1 = 0,
port = 0,
status = 0,
device_name = new byte[10],
reserved2 = 0,
http_port = 0,
device_port = 0,
tcp_port = 0,
udp_port = 0,
upload_port = 0,
multicast_port = 0,
network_mode = 1,
ddns_url = new byte[128],
reserved3 = 0
};
return packet;
}
// Send discovery packet
private async Task<bool> SendDiscoveryPacketAsync(DataPacketIPv4 packet)
{
try
{
byte[] data = StructureToByteArray(packet);
IPEndPoint broadcastEndPoint = new IPEndPoint(IPAddress.Parse(BROADCAST_ADDRESS), SEND_PORT);
int bytesSent = await sendClient.SendAsync(data, data.Length, broadcastEndPoint);
return bytesSent > 0;
}
catch (Exception ex)
{
MessageBox.Show($"Error sending discovery packet: {ex.Message}", "Network Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
return false;
}
}
// Listen for camera responses
private async Task ListenForResponsesAsync(int timeoutMs = 5000)
{
var startTime = DateTime.Now;
var endTime = startTime.AddMilliseconds(timeoutMs);
try
{
while (DateTime.Now < endTime && isDiscovering)
{
var receiveTask = receiveClient.ReceiveAsync();
var timeoutTask = Task.Delay(1000); // Check every second
var completedTask = await Task.WhenAny(receiveTask, timeoutTask);
if (completedTask == receiveTask)
{
var result = await receiveTask;
ProcessCameraResponse(result.Buffer, result.RemoteEndPoint);
}
}
}
catch (Exception ex)
{
if (isDiscovering) // Only show error if we're still supposed to be discovering
{
this.Invoke(new Action(() =>
{
MessageBox.Show($"Error listening for responses: {ex.Message}", "Network Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
}));
}
}
}
// Process received camera response
private void ProcessCameraResponse(byte[] data, IPEndPoint remoteEndPoint)
{
try
{
if (data.Length == Marshal.SizeOf<DataPacketIPv4>())
{
DataPacketIPv4 response = ByteArrayToStructure(data);
// Check if this is a valid camera response
if (response.mode == RES_REQ_SCAN)
{
string cameraIP = ExtractString(response.ip_addr);
string deviceName = ExtractString(response.device_name);
string macAddress = ExtractString(response.mac_addr);
string packetId = ExtractString(response.packet_id);
// Use the IP from the response or fall back to the sender's IP
if (string.IsNullOrEmpty(cameraIP))
{
cameraIP = remoteEndPoint.Address.ToString();
}
// Create camera name
string cameraName = !string.IsNullOrEmpty(deviceName) ? deviceName : $"Camera {cameraIP}";
// Create camera object
Camera discoveredCamera = new Camera(
$"{deviceName} - ({cameraIP})",
cameraIP,
$"MAC: {macAddress}",
packetId // Using packet_id as serial for uniqueness
);
// Add camera to list (must be done on UI thread)
this.Invoke(new Action(() =>
{
add_camera(discoveredCamera);
toolStripStatusLabel.Text = $"Found camera: {cameraName} ({cameraIP})";
}));
}
}
}
catch (Exception ex)
{
this.Invoke(new Action(() =>
{
MessageBox.Show($"Error processing camera response: {ex.Message}", "Processing Error", MessageBoxButtons.OK, MessageBoxIcon.Warning);
}));
}
}
// Main discovery method
private async void discover_cameras()
{
if (isDiscovering)
{
MessageBox.Show("Discovery is already in progress.", "Discovery", MessageBoxButtons.OK, MessageBoxIcon.Information);
return;
}
if (sendClient == null || receiveClient == null)
{
InitializeUdpClients();
if (sendClient == null || receiveClient == null)
{
MessageBox.Show("Failed to initialize network clients.", "Network Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
return;
}
}
isDiscovering = true;
toolStripStatusLabel.Text = "Discovering cameras...";
try
{
// Create and send discovery packet
DataPacketIPv4 discoveryPacket = CreateDiscoveryPacket();
bool sent = await SendDiscoveryPacketAsync(discoveryPacket);
if (sent)
{
// Listen for responses for 10 seconds
await ListenForResponsesAsync(10000);
}
else
{
MessageBox.Show("Failed to send discovery packet.", "Network Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
}
}
catch (Exception ex)
{
MessageBox.Show($"Discovery error: {ex.Message}", "Discovery Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
}
finally
{
isDiscovering = false;
toolStripStatusLabel.Text = $"Discovery complete. Found {all_cameras.Count} cameras.";
}
}
// Clean up resources when form is closing
protected override void OnFormClosing(FormClosingEventArgs e)
{
isDiscovering = false;
try
{
sendClient?.Close();
receiveClient?.Close();
}
catch { }
base.OnFormClosing(e);
}
}
}