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 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 = "255.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(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 cameras_in_file = JsonSerializer.Deserialize>(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>(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, "", "CAM_FINDER_" + DateTime.Now.Ticks.ToString()); 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 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 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); } } }