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 { private BindingList allCameras = 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) { cameraName = name; cameraIP = ip; cameraLocation = location; cameraSerial = serial; } public string cameraName { get; set; } public string cameraIP { get; set; } public string cameraLocation { get; set; } public string cameraSerial { 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(); bool gotProperIP = false; foreach (NetworkInterface networkInterface in NetworkInterface.GetAllNetworkInterfaces()) { IPInterfaceProperties interfaceProperties = networkInterface.GetIPProperties(); foreach (UnicastIPAddressInformation interfaceUnicastAddr in interfaceProperties.UnicastAddresses) { if (interfaceUnicastAddr.Address.ToString().StartsWith("10.")) { if (interfaceUnicastAddr.IPv4Mask.ToString() == "255.0.0.0") { gotProperIP = true; } } } } if (!gotProperIP) { DialogResult openNetworkSettings = 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 (openNetworkSettings == DialogResult.Yes) { ProcessStartInfo startInfo = new ProcessStartInfo("NCPA.cpl"); startInfo.UseShellExecute = true; Process.Start(startInfo); } } lbMain.DataSource = allCameras; lbMain.DisplayMember = "cameraName"; lbMain.ValueMember = "cameraIP"; // Initialize UDP clients InitializeUdpClients(); // Discover cameras DiscoverCameras(); } 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 AddCamera(Camera newCamera) { if (allCameras.Count == 0) { allCameras.Add(newCamera); } else { Camera searchCamera = allCameras.FirstOrDefault(cam => cam.cameraSerial == newCamera.cameraSerial); if (searchCamera != null) { int i = allCameras.IndexOf(searchCamera); allCameras[i] = newCamera; } else { allCameras.Add(newCamera); } } } private void lbMain_MouseDoubleClick(object sender, MouseEventArgs e) { if (lbMain.SelectedItem != null) { toolStripStatusLabel.Text = "Loading camera..."; Camera camera = allCameras.FirstOrDefault(cam => cam.cameraIP.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? camerasInFile = JsonSerializer.Deserialize>(json); if (camerasInFile is List) { foreach (Camera camera in camerasInFile) { AddCamera(camera); } } } } private void saveToolStripMenuItem_Click(object sender, EventArgs e) { string json = JsonSerializer.Serialize>(allCameras, 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) { ManualIPForm manualForm = new ManualIPForm(); if (manualForm.ShowDialog(this) == DialogResult.OK) { string ipAddress = manualForm.ipAddress; Camera cameraToAdd = new Camera("Camera " + ipAddress, ipAddress, "", ipAddress.Replace(".", "")); AddCamera(cameraToAdd); } } // 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(() => { AddCamera(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 DiscoverCameras() { 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 {allCameras.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); } } }