508 lines
17 KiB
C#
508 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
|
|
{
|
|
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();
|
|
|
|
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);
|
|
}
|
|
}
|
|
} |