This commit is contained in:
Vincent BOUQUET
2024-02-17 21:51:46 +01:00
committed by Vincent BOUQUET
commit d49235969d
17 changed files with 2233 additions and 0 deletions

63
.gitattributes vendored Normal file
View File

@@ -0,0 +1,63 @@
###############################################################################
# Set default behavior to automatically normalize line endings.
###############################################################################
* text=auto
###############################################################################
# Set default behavior for command prompt diff.
#
# This is need for earlier builds of msysgit that does not have it on by
# default for csharp files.
# Note: This is only used by command line
###############################################################################
#*.cs diff=csharp
###############################################################################
# Set the merge driver for project and solution files
#
# Merging from the command prompt will add diff markers to the files if there
# are conflicts (Merging from VS is not affected by the settings below, in VS
# the diff markers are never inserted). Diff markers may cause the following
# file extensions to fail to load in VS. An alternative would be to treat
# these files as binary and thus will always conflict and require user
# intervention with every merge. To do so, just uncomment the entries below
###############################################################################
#*.sln merge=binary
#*.csproj merge=binary
#*.vbproj merge=binary
#*.vcxproj merge=binary
#*.vcproj merge=binary
#*.dbproj merge=binary
#*.fsproj merge=binary
#*.lsproj merge=binary
#*.wixproj merge=binary
#*.modelproj merge=binary
#*.sqlproj merge=binary
#*.wwaproj merge=binary
###############################################################################
# behavior for image files
#
# image files are treated as binary by default.
###############################################################################
#*.jpg binary
#*.png binary
#*.gif binary
###############################################################################
# diff behavior for common document formats
#
# Convert binary document formats to text before diffing them. This feature
# is only available from the command line. Turn it on by uncommenting the
# entries below.
###############################################################################
#*.doc diff=astextplain
#*.DOC diff=astextplain
#*.docx diff=astextplain
#*.DOCX diff=astextplain
#*.dot diff=astextplain
#*.DOT diff=astextplain
#*.pdf diff=astextplain
#*.PDF diff=astextplain
#*.rtf diff=astextplain
#*.RTF diff=astextplain

366
.gitignore vendored Normal file
View File

@@ -0,0 +1,366 @@
## Ignore Visual Studio temporary files, build results, and
## files generated by popular Visual Studio add-ons.
##
## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore
# User-specific files
*.rsuser
*.suo
*.user
*.userosscache
*.sln.docstates
# User-specific files (MonoDevelop/Xamarin Studio)
*.userprefs
# Mono auto generated files
mono_crash.*
# Build results
[Dd]ebug/
[Dd]ebugPublic/
[Rr]elease/
[Rr]eleases/
x64/
x86/
[Ww][Ii][Nn]32/
[Aa][Rr][Mm]/
[Aa][Rr][Mm]64/
bld/
[Bb]in/
[Oo]bj/
[Oo]ut/
[Ll]og/
[Ll]ogs/
# Visual Studio 2015/2017 cache/options directory
.vs/
# Uncomment if you have tasks that create the project's static files in wwwroot
#wwwroot/
# Visual Studio 2017 auto generated files
Generated\ Files/
# MSTest test Results
[Tt]est[Rr]esult*/
[Bb]uild[Ll]og.*
# NUnit
*.VisualState.xml
TestResult.xml
nunit-*.xml
# Build Results of an ATL Project
[Dd]ebugPS/
[Rr]eleasePS/
dlldata.c
# Benchmark Results
BenchmarkDotNet.Artifacts/
# .NET Core
project.lock.json
project.fragment.lock.json
artifacts/
# ASP.NET Scaffolding
ScaffoldingReadMe.txt
# StyleCop
StyleCopReport.xml
# Files built by Visual Studio
*_i.c
*_p.c
*_h.h
*.ilk
*.meta
*.obj
*.iobj
*.pch
*.pdb
*.ipdb
*.pgc
*.pgd
*.rsp
*.sbr
*.tlb
*.tli
*.tlh
*.tmp
*.tmp_proj
*_wpftmp.csproj
*.log
*.vspscc
*.vssscc
.builds
*.pidb
*.svclog
*.scc
# Chutzpah Test files
_Chutzpah*
# Visual C++ cache files
ipch/
*.aps
*.ncb
*.opendb
*.opensdf
*.sdf
*.cachefile
*.VC.db
*.VC.VC.opendb
# Visual Studio profiler
*.psess
*.vsp
*.vspx
*.sap
# Visual Studio Trace Files
*.e2e
# TFS 2012 Local Workspace
$tf/
# Guidance Automation Toolkit
*.gpState
# ReSharper is a .NET coding add-in
_ReSharper*/
*.[Rr]e[Ss]harper
*.DotSettings.user
# TeamCity is a build add-in
_TeamCity*
# DotCover is a Code Coverage Tool
*.dotCover
# AxoCover is a Code Coverage Tool
.axoCover/*
!.axoCover/settings.json
# Coverlet is a free, cross platform Code Coverage Tool
coverage*.json
coverage*.xml
coverage*.info
# Visual Studio code coverage results
*.coverage
*.coveragexml
# NCrunch
_NCrunch_*
.*crunch*.local.xml
nCrunchTemp_*
# MightyMoose
*.mm.*
AutoTest.Net/
# Web workbench (sass)
.sass-cache/
# Installshield output folder
[Ee]xpress/
# DocProject is a documentation generator add-in
DocProject/buildhelp/
DocProject/Help/*.HxT
DocProject/Help/*.HxC
DocProject/Help/*.hhc
DocProject/Help/*.hhk
DocProject/Help/*.hhp
DocProject/Help/Html2
DocProject/Help/html
# Click-Once directory
publish/
# Publish Web Output
*.[Pp]ublish.xml
*.azurePubxml
# Note: Comment the next line if you want to checkin your web deploy settings,
# but database connection strings (with potential passwords) will be unencrypted
*.pubxml
*.publishproj
# Microsoft Azure Web App publish settings. Comment the next line if you want to
# checkin your Azure Web App publish settings, but sensitive information contained
# in these scripts will be unencrypted
PublishScripts/
# NuGet Packages
*.nupkg
# NuGet Symbol Packages
*.snupkg
# The packages folder can be ignored because of Package Restore
**/[Pp]ackages/*
# except build/, which is used as an MSBuild target.
!**/[Pp]ackages/build/
# Uncomment if necessary however generally it will be regenerated when needed
#!**/[Pp]ackages/repositories.config
# NuGet v3's project.json files produces more ignorable files
*.nuget.props
*.nuget.targets
# Microsoft Azure Build Output
csx/
*.build.csdef
# Microsoft Azure Emulator
ecf/
rcf/
# Windows Store app package directories and files
AppPackages/
BundleArtifacts/
Package.StoreAssociation.xml
_pkginfo.txt
*.appx
*.appxbundle
*.appxupload
# Visual Studio cache files
# files ending in .cache can be ignored
*.[Cc]ache
# but keep track of directories ending in .cache
!?*.[Cc]ache/
# Others
ClientBin/
~$*
*~
*.dbmdl
*.dbproj.schemaview
*.jfm
*.pfx
*.publishsettings
orleans.codegen.cs
# Including strong name files can present a security risk
# (https://github.com/github/gitignore/pull/2483#issue-259490424)
#*.snk
# Since there are multiple workflows, uncomment next line to ignore bower_components
# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
#bower_components/
# RIA/Silverlight projects
Generated_Code/
# Backup & report files from converting an old project file
# to a newer Visual Studio version. Backup files are not needed,
# because we have git ;-)
_UpgradeReport_Files/
Backup*/
UpgradeLog*.XML
UpgradeLog*.htm
ServiceFabricBackup/
*.rptproj.bak
# SQL Server files
*.mdf
*.ldf
*.ndf
# Business Intelligence projects
*.rdl.data
*.bim.layout
*.bim_*.settings
*.rptproj.rsuser
*- [Bb]ackup.rdl
*- [Bb]ackup ([0-9]).rdl
*- [Bb]ackup ([0-9][0-9]).rdl
# Microsoft Fakes
FakesAssemblies/
# GhostDoc plugin setting file
*.GhostDoc.xml
# Node.js Tools for Visual Studio
.ntvs_analysis.dat
node_modules/
# Visual Studio 6 build log
*.plg
# Visual Studio 6 workspace options file
*.opt
# Visual Studio 6 auto-generated workspace file (contains which files were open etc.)
*.vbw
# Visual Studio LightSwitch build output
**/*.HTMLClient/GeneratedArtifacts
**/*.DesktopClient/GeneratedArtifacts
**/*.DesktopClient/ModelManifest.xml
**/*.Server/GeneratedArtifacts
**/*.Server/ModelManifest.xml
_Pvt_Extensions
# Paket dependency manager
.paket/paket.exe
paket-files/
# FAKE - F# Make
.fake/
# CodeRush personal settings
.cr/personal
# Python Tools for Visual Studio (PTVS)
__pycache__/
*.pyc
# Cake - Uncomment if you are using it
# tools/**
# !tools/packages.config
# Tabs Studio
*.tss
# Telerik's JustMock configuration file
*.jmconfig
# BizTalk build output
*.btp.cs
*.btm.cs
*.odx.cs
*.xsd.cs
# OpenCover UI analysis results
OpenCover/
# Azure Stream Analytics local run output
ASALocalRun/
# MSBuild Binary and Structured Log
*.binlog
# NVidia Nsight GPU debugger configuration file
*.nvuser
# MFractors (Xamarin productivity tool) working folder
.mfractor/
# Local History for Visual Studio
.localhistory/
# BeatPulse healthcheck temp database
healthchecksdb
# Backup folder for Package Reference Convert tool in Visual Studio 2017
MigrationBackup/
# Ionide (cross platform F# VS Code tools) working folder
.ionide/
# Fody - auto-generated XML schema
FodyWeavers.xsd
# Docs
docs

167
MainForm.Designer.cs generated Normal file
View File

@@ -0,0 +1,167 @@
namespace robospot_camera_finder
{
partial class MainForm
{
/// <summary>
/// Required designer variable.
/// </summary>
private System.ComponentModel.IContainer components = null;
/// <summary>
/// Clean up any resources being used.
/// </summary>
/// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
protected override void Dispose(bool disposing)
{
if (disposing && (components != null))
{
components.Dispose();
}
base.Dispose(disposing);
}
#region Windows Form Designer generated code
/// <summary>
/// Required method for Designer support - do not modify
/// the contents of this method with the code editor.
/// </summary>
private void InitializeComponent()
{
System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(MainForm));
lbMain = new ListBox();
statusStripMain = new StatusStrip();
toolStripStatusLabel = new ToolStripStatusLabel();
toolStripStatusDisco = new ToolStripStatusLabel();
menuStrip1 = new MenuStrip();
discoveryToolStripMenuItem = new ToolStripMenuItem();
manualAddToolStripMenuItem = new ToolStripMenuItem();
toolStripSeparator1 = new ToolStripSeparator();
saveToolStripMenuItem = new ToolStripMenuItem();
loadToolStripMenuItem = new ToolStripMenuItem();
toolStripSeparator2 = new ToolStripSeparator();
discoverToolStripMenuItem = new ToolStripMenuItem();
statusStripMain.SuspendLayout();
menuStrip1.SuspendLayout();
SuspendLayout();
//
// lbMain
//
lbMain.Anchor = AnchorStyles.Top | AnchorStyles.Bottom | AnchorStyles.Left | AnchorStyles.Right;
lbMain.FormattingEnabled = true;
lbMain.ItemHeight = 15;
lbMain.Location = new Point(12, 23);
lbMain.Name = "lbMain";
lbMain.Size = new Size(776, 394);
lbMain.TabIndex = 0;
lbMain.MouseDoubleClick += lbMain_MouseDoubleClick;
//
// statusStripMain
//
statusStripMain.Items.AddRange(new ToolStripItem[] { toolStripStatusLabel, toolStripStatusDisco });
statusStripMain.Location = new Point(0, 428);
statusStripMain.Name = "statusStripMain";
statusStripMain.Size = new Size(800, 22);
statusStripMain.TabIndex = 1;
statusStripMain.Text = "statusStrip1";
//
// toolStripStatusLabel
//
toolStripStatusLabel.Name = "toolStripStatusLabel";
toolStripStatusLabel.Size = new Size(0, 17);
//
// toolStripStatusDisco
//
toolStripStatusDisco.Name = "toolStripStatusDisco";
toolStripStatusDisco.Size = new Size(111, 17);
toolStripStatusDisco.Text = "";
//
// menuStrip1
//
menuStrip1.Items.AddRange(new ToolStripItem[] { discoveryToolStripMenuItem });
menuStrip1.Location = new Point(0, 0);
menuStrip1.Name = "menuStrip1";
menuStrip1.Size = new Size(800, 24);
menuStrip1.TabIndex = 2;
menuStrip1.Text = "menuStrip1";
//
// discoveryToolStripMenuItem
//
discoveryToolStripMenuItem.DropDownItems.AddRange(new ToolStripItem[] { discoverToolStripMenuItem, toolStripSeparator2, manualAddToolStripMenuItem, toolStripSeparator1, saveToolStripMenuItem, loadToolStripMenuItem });
discoveryToolStripMenuItem.Name = "discoveryToolStripMenuItem";
discoveryToolStripMenuItem.Size = new Size(70, 20);
discoveryToolStripMenuItem.Text = "Discovery";
//
// manualAddToolStripMenuItem
//
manualAddToolStripMenuItem.Name = "manualAddToolStripMenuItem";
manualAddToolStripMenuItem.Size = new Size(180, 22);
manualAddToolStripMenuItem.Text = "Add manually";
manualAddToolStripMenuItem.Click += manualAddToolStripMenuItem_Click;
//
// toolStripSeparator1
//
toolStripSeparator1.Name = "toolStripSeparator1";
toolStripSeparator1.Size = new Size(177, 6);
//
// saveToolStripMenuItem
//
saveToolStripMenuItem.Name = "saveToolStripMenuItem";
saveToolStripMenuItem.Size = new Size(180, 22);
saveToolStripMenuItem.Text = "Save";
saveToolStripMenuItem.Click += saveToolStripMenuItem_Click;
//
// loadToolStripMenuItem
//
loadToolStripMenuItem.Name = "loadToolStripMenuItem";
loadToolStripMenuItem.Size = new Size(180, 22);
loadToolStripMenuItem.Text = "Load";
loadToolStripMenuItem.Click += loadToolStripMenuItem_Click;
//
// toolStripSeparator2
//
toolStripSeparator2.Name = "toolStripSeparator2";
toolStripSeparator2.Size = new Size(177, 6);
//
// discoverToolStripMenuItem
//
discoverToolStripMenuItem.Name = "discoverToolStripMenuItem";
discoverToolStripMenuItem.Size = new Size(180, 22);
discoverToolStripMenuItem.Text = "Discover cameras";
//
// MainForm
//
AutoScaleDimensions = new SizeF(7F, 15F);
AutoScaleMode = AutoScaleMode.Font;
ClientSize = new Size(800, 450);
Controls.Add(statusStripMain);
Controls.Add(menuStrip1);
Controls.Add(lbMain);
Icon = (Icon)resources.GetObject("$this.Icon");
MainMenuStrip = menuStrip1;
Name = "MainForm";
Text = "RoboSpot MotionCamera finder";
statusStripMain.ResumeLayout(false);
statusStripMain.PerformLayout();
menuStrip1.ResumeLayout(false);
menuStrip1.PerformLayout();
ResumeLayout(false);
PerformLayout();
}
#endregion
private ListBox lbMain;
private StatusStrip statusStripMain;
private ToolStripStatusLabel toolStripStatusLabel;
private MenuStrip menuStrip1;
private ToolStripMenuItem discoveryToolStripMenuItem;
private ToolStripMenuItem saveToolStripMenuItem;
private ToolStripMenuItem loadToolStripMenuItem;
private ToolStripSeparator toolStripSeparator1;
private ToolStripStatusLabel toolStripStatusDisco;
private ToolStripMenuItem manualAddToolStripMenuItem;
private ToolStripMenuItem discoverToolStripMenuItem;
private ToolStripSeparator toolStripSeparator2;
}
}

512
MainForm.cs Normal file
View File

@@ -0,0 +1,512 @@
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);
}
}
}

151
MainForm.resx Normal file
View File

@@ -0,0 +1,151 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
Version 2.0
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</resheader>
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
<value>[base64 mime encoded serialized .NET Framework object]</value>
</data>
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment>
</data>
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<metadata name="statusStripMain.TrayLocation" type="System.Drawing.Point, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
<value>17, 17</value>
</metadata>
<metadata name="menuStrip1.TrayLocation" type="System.Drawing.Point, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
<value>153, 17</value>
</metadata>
<assembly alias="System.Drawing" name="System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a" />
<data name="$this.Icon" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>
AAABAAEAEBAAAAEAIABoBAAAFgAAACgAAAAQAAAAIAAAAAEAIAAAAAAAQAQAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAkAAP9UAAD/VwAA
/w0AAAAAAAAAgwAAAMgAAADIAAAAyAAAALsAAAAAAAAAAAAAAAAAAADLAAAA0AAAGNQAAN/4AAD//wAA
//8AAP/iAAD/HgAAAIIAAAD/AAAA/wAAAP8AAAD/AAAAFgAAAAAAAAAUAAAA/wAAAP8AAIT/AAD//wAA
//8AAP//AAD//wAA/5cAAABYAAAA/wAAAP8AAAD/AAAA/wAAAEAAAAAAAAAALwAAAP8AAAD/AACi/wAA
//8AAP//AAD//wAA//8AAP+0AAAALwAAAP8AAAD/AAAA/wAAAP8AAABrAAAAAAAAAEsAAAD/AAAA/wAA
Wv8AAP//AAD//wAA//8AAP//AAD/bQAAAAgAAAD9AAAA/wAAAP8AAAD/AAAAlQAAAAAAAABnAAAA/wAA
AP8AAAD/AAB7/wAA9fcAAP/wAAD/iAAAAAIAAAAAAAAA2wAAAP8AAAD/AAAA/wAAAM8AAABQAAAApgAA
AP8AAAD/AAAA/wAAAP8AAABXAAAAAAAAAAAAAAAAAAAAAAAAALEAAAD/AAAA/wAAAP8AAAD/AAAA/wAA
AP8AAAD/AAAA/wAAAP8AAAD/AAAApgAAABsAAAAAAAAAAAAAAAAAAACIAAAA/wAAAP8AAAD/AAAA/wAA
AP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAADsAAAAMwAAAAAAAAAAAAAAXgAAAP8AAAD/AAAA/wAA
AP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAOMAAAALAAAAAAAAADQAAAD/AAAA/wAA
AP8AAAD/AAAAZAAAAAAAAAAAAAAADgAAAMoAAAD/AAAA/wAAAP8AAAD/AAAAawAAAAAAAAAMAAAA/gAA
AP8AAAD/AAAA/wAAAIwAAAAAAAAAAAAAAAYAAACwAAAA/wAAAP8AAAD/AAAA/wAAAKgAAAAAAAAAAAAA
AOEAAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAACfAAAAAAAA
AAAAAAC3AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD9AAAAPgAA
AAAAAAAAAAAAjQAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAADXAAAAUAAA
AAAAAAAAAAAAAAAAABkAAAA4AAAAOAAAADgAAAA4AAAAOAAAADgAAAA4AAAAOAAAADQAAAAZAAAAAAAA
AAAAAAAA//EAAAcAAAACAAAAAgAAAAIAAACCAQAAgAcAAIADAACAAQAAgAAAAIGAAACBwAAAwAAAAMAA
AADAAQAAwAcAAA==
</value>
</data>
</root>

90
Manual_IP_Form.Designer.cs generated Normal file
View File

@@ -0,0 +1,90 @@
namespace robospot_camera_finder
{
partial class Manual_IP_Form
{
/// <summary>
/// Required designer variable.
/// </summary>
private System.ComponentModel.IContainer components = null;
/// <summary>
/// Clean up any resources being used.
/// </summary>
/// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
protected override void Dispose(bool disposing)
{
if (disposing && (components != null))
{
components.Dispose();
}
base.Dispose(disposing);
}
#region Windows Form Designer generated code
/// <summary>
/// Required method for Designer support - do not modify
/// the contents of this method with the code editor.
/// </summary>
private void InitializeComponent()
{
btnValidate = new Button();
txtIPAddress = new TextBox();
lblEnterIP = new Label();
SuspendLayout();
//
// btnValidate
//
btnValidate.Location = new Point(313, 27);
btnValidate.Name = "btnValidate";
btnValidate.Size = new Size(75, 23);
btnValidate.TabIndex = 1;
btnValidate.Text = "OK";
btnValidate.UseVisualStyleBackColor = true;
btnValidate.Click += btnValidate_Click;
//
// txtIPAddress
//
txtIPAddress.Location = new Point(12, 27);
txtIPAddress.MaxLength = 14;
txtIPAddress.Name = "txtIPAddress";
txtIPAddress.PlaceholderText = "10.*.*.*";
txtIPAddress.Size = new Size(295, 23);
txtIPAddress.TabIndex = 0;
//
// lblEnterIP
//
lblEnterIP.AutoSize = true;
lblEnterIP.Location = new Point(12, 9);
lblEnterIP.Name = "lblEnterIP";
lblEnterIP.Size = new Size(299, 15);
lblEnterIP.TabIndex = 2;
lblEnterIP.Text = "Please enter the camera IP address in the 10.*.*.* format";
//
// Manual_IP_Form
//
AutoScaleDimensions = new SizeF(7F, 15F);
AutoScaleMode = AutoScaleMode.Font;
ClientSize = new Size(400, 66);
Controls.Add(lblEnterIP);
Controls.Add(txtIPAddress);
Controls.Add(btnValidate);
FormBorderStyle = FormBorderStyle.FixedSingle;
MaximizeBox = false;
MinimizeBox = false;
Name = "Manual_IP_Form";
ShowIcon = false;
ShowInTaskbar = false;
Text = "Enter IP";
TopMost = true;
ResumeLayout(false);
PerformLayout();
}
#endregion
private Button btnValidate;
private TextBox txtIPAddress;
private Label lblEnterIP;
}
}

30
Manual_IP_Form.cs Normal file
View File

@@ -0,0 +1,30 @@
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
namespace robospot_camera_finder
{
public partial class Manual_IP_Form : Form
{
public string ipAddress { get; set; }
public Manual_IP_Form()
{
InitializeComponent();
}
private void btnValidate_Click(object sender, EventArgs e)
{
this.ipAddress = txtIPAddress.Text;
this.DialogResult = DialogResult.OK;
this.Close();
}
}
}

120
Manual_IP_Form.resx Normal file
View File

@@ -0,0 +1,120 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
Version 2.0
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</resheader>
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
<value>[base64 mime encoded serialized .NET Framework object]</value>
</data>
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment>
</data>
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
</root>

17
Program.cs Normal file
View File

@@ -0,0 +1,17 @@
namespace robospot_camera_finder
{
internal static class Program
{
/// <summary>
/// The main entry point for the application.
/// </summary>
[STAThread]
static void Main()
{
// To customize application configuration such as set high DPI settings or default font,
// see https://aka.ms/applicationconfiguration.
ApplicationConfiguration.Initialize();
Application.Run(new MainForm());
}
}
}

63
Properties/Resources.Designer.cs generated Normal file
View File

@@ -0,0 +1,63 @@
//------------------------------------------------------------------------------
// <auto-generated>
// This code was generated by a tool.
// Runtime Version:4.0.30319.42000
//
// Changes to this file may cause incorrect behavior and will be lost if
// the code is regenerated.
// </auto-generated>
//------------------------------------------------------------------------------
namespace robospot_camera_finder.Properties {
using System;
/// <summary>
/// A strongly-typed resource class, for looking up localized strings, etc.
/// </summary>
// This class was auto-generated by the StronglyTypedResourceBuilder
// class via a tool like ResGen or Visual Studio.
// To add or remove a member, edit your .ResX file then rerun ResGen
// with the /str option, or rebuild your VS project.
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "17.0.0.0")]
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
internal class Resources {
private static global::System.Resources.ResourceManager resourceMan;
private static global::System.Globalization.CultureInfo resourceCulture;
[global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
internal Resources() {
}
/// <summary>
/// Returns the cached ResourceManager instance used by this class.
/// </summary>
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
internal static global::System.Resources.ResourceManager ResourceManager {
get {
if (object.ReferenceEquals(resourceMan, null)) {
global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("robospot_camera_finder.Properties.Resources", typeof(Resources).Assembly);
resourceMan = temp;
}
return resourceMan;
}
}
/// <summary>
/// Overrides the current thread's CurrentUICulture property for all
/// resource lookups using this strongly typed resource class.
/// </summary>
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
internal static global::System.Globalization.CultureInfo Culture {
get {
return resourceCulture;
}
set {
resourceCulture = value;
}
}
}
}

120
Properties/Resources.resx Normal file
View File

@@ -0,0 +1,120 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
Version 2.0
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</resheader>
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
<value>[base64 mime encoded serialized .NET Framework object]</value>
</data>
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment>
</data>
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
</root>

179
StreamViewer.Designer.cs generated Normal file
View File

@@ -0,0 +1,179 @@
using LibVLCSharp.Forms.Shared;
namespace robospot_camera_finder
{
partial class StreamViewer
{
/// <summary>
/// Required designer variable.
/// </summary>
private System.ComponentModel.IContainer components = null;
/// <summary>
/// Clean up any resources being used.
/// </summary>
/// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
protected override void Dispose(bool disposing)
{
if (disposing && (components != null))
{
components.Dispose();
}
base.Dispose(disposing);
}
#region Windows Form Designer generated code
/// <summary>
/// Required method for Designer support - do not modify
/// the contents of this method with the code editor.
/// </summary>
private void InitializeComponent()
{
components = new System.ComponentModel.Container();
System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(StreamViewer));
videoView = new LibVLCSharp.WinForms.VideoView();
tbZoom = new TrackBar();
timerUpdateZoom = new System.Windows.Forms.Timer(components);
btnZoomMin = new Button();
btnZoomMax = new Button();
tbRtspLink = new TextBox();
lblRtspLink = new Label();
cbTestZoom = new CheckBox();
timerZoomTestSeq = new System.Windows.Forms.Timer(components);
((System.ComponentModel.ISupportInitialize)videoView).BeginInit();
((System.ComponentModel.ISupportInitialize)tbZoom).BeginInit();
SuspendLayout();
//
// videoView
//
videoView.Anchor = AnchorStyles.Top | AnchorStyles.Bottom | AnchorStyles.Left | AnchorStyles.Right;
videoView.BackColor = Color.Black;
videoView.Location = new Point(9, 8);
videoView.MediaPlayer = null;
videoView.Name = "videoView";
videoView.Size = new Size(724, 478);
videoView.TabIndex = 0;
videoView.TabStop = false;
videoView.Text = "videoView";
//
// tbZoom
//
tbZoom.Anchor = AnchorStyles.Top | AnchorStyles.Bottom | AnchorStyles.Right;
tbZoom.LargeChange = 20;
tbZoom.Location = new Point(739, 35);
tbZoom.Maximum = 9999;
tbZoom.Name = "tbZoom";
tbZoom.Orientation = Orientation.Vertical;
tbZoom.Size = new Size(45, 426);
tbZoom.SmallChange = 10;
tbZoom.TabIndex = 1;
tbZoom.TickFrequency = 300;
tbZoom.TickStyle = TickStyle.TopLeft;
tbZoom.Value = 10;
tbZoom.KeyUp += tbZoom_KeyUp;
tbZoom.MouseUp += tbZoom_MouseUp;
//
// timerUpdateZoom
//
timerUpdateZoom.Enabled = true;
timerUpdateZoom.Interval = 10000;
timerUpdateZoom.Tick += timerUpdateZoom_Tick;
//
// btnZoomMin
//
btnZoomMin.Anchor = AnchorStyles.Bottom | AnchorStyles.Right;
btnZoomMin.Font = new Font("Segoe UI", 9F, FontStyle.Bold, GraphicsUnit.Point);
btnZoomMin.Location = new Point(739, 467);
btnZoomMin.Name = "btnZoomMin";
btnZoomMin.Size = new Size(42, 23);
btnZoomMin.TabIndex = 2;
btnZoomMin.Text = "1x";
btnZoomMin.UseVisualStyleBackColor = true;
btnZoomMin.Click += btnZoomMin_Click;
//
// btnZoomMax
//
btnZoomMax.Anchor = AnchorStyles.Top | AnchorStyles.Right;
btnZoomMax.Font = new Font("Segoe UI", 9F, FontStyle.Bold, GraphicsUnit.Point);
btnZoomMax.Location = new Point(739, 6);
btnZoomMax.Name = "btnZoomMax";
btnZoomMax.Size = new Size(42, 23);
btnZoomMax.TabIndex = 3;
btnZoomMax.Text = "32x";
btnZoomMax.UseVisualStyleBackColor = true;
btnZoomMax.Click += btnZoomMax_Click;
//
// tbRtspLink
//
tbRtspLink.Anchor = AnchorStyles.Bottom | AnchorStyles.Left;
tbRtspLink.Location = new Point(75, 492);
tbRtspLink.Name = "tbRtspLink";
tbRtspLink.ReadOnly = true;
tbRtspLink.Size = new Size(559, 23);
tbRtspLink.TabIndex = 8;
//
// lblRtspLink
//
lblRtspLink.Anchor = AnchorStyles.Bottom | AnchorStyles.Left;
lblRtspLink.AutoSize = true;
lblRtspLink.Location = new Point(12, 495);
lblRtspLink.Name = "lblRtspLink";
lblRtspLink.Size = new Size(57, 15);
lblRtspLink.TabIndex = 7;
lblRtspLink.Text = "RTSP Link";
//
// cbTestZoom
//
cbTestZoom.Anchor = AnchorStyles.Bottom | AnchorStyles.Right;
cbTestZoom.AutoSize = true;
cbTestZoom.Location = new Point(640, 493);
cbTestZoom.Name = "cbTestZoom";
cbTestZoom.Size = new Size(144, 19);
cbTestZoom.TabIndex = 9;
cbTestZoom.Text = "Automated Zoom Test";
cbTestZoom.UseVisualStyleBackColor = true;
cbTestZoom.CheckedChanged += cbTestZoom_CheckedChanged;
//
// timerZoomTestSeq
//
timerZoomTestSeq.Interval = 6000;
timerZoomTestSeq.Tick += timerZoomTestSeq_Tick;
//
// StreamViewer
//
AutoScaleDimensions = new SizeF(7F, 15F);
AutoScaleMode = AutoScaleMode.Font;
ClientSize = new Size(801, 514);
Controls.Add(cbTestZoom);
Controls.Add(tbRtspLink);
Controls.Add(lblRtspLink);
Controls.Add(videoView);
Controls.Add(btnZoomMax);
Controls.Add(tbZoom);
Controls.Add(btnZoomMin);
Icon = (Icon)resources.GetObject("$this.Icon");
Margin = new Padding(4, 3, 4, 3);
MinimumSize = new Size(817, 546);
Name = "StreamViewer";
Text = "Camera";
FormClosed += StreamViewer_FormClosed;
((System.ComponentModel.ISupportInitialize)videoView).EndInit();
((System.ComponentModel.ISupportInitialize)tbZoom).EndInit();
ResumeLayout(false);
PerformLayout();
}
#endregion
private LibVLCSharp.WinForms.VideoView videoView;
private TrackBar tbZoom;
private System.Windows.Forms.Timer timerUpdateZoom;
private Button btnZoomMin;
private Button btnZoomMax;
private TextBox tbRtspLink;
private Label lblRtspLink;
private CheckBox cbTestZoom;
private System.Windows.Forms.Timer timerZoomTestSeq;
}
}

139
StreamViewer.cs Normal file
View File

@@ -0,0 +1,139 @@
using LibVLCSharp.Shared;
using System.Net;
namespace robospot_camera_finder
{
public partial class StreamViewer : Form
{
public LibVLC _libVLC;
public MediaPlayer _mp;
string cam_ip = "";
int max_zoom_pulse = 9999;
int min_zoom_pulse = 10;
bool zoomTestSeqFlag = false;
public StreamViewer(string name, string ip, MainForm.Camera camera, MainForm mainForm)
{
InitializeComponent();
_libVLC = new LibVLC();
_mp = new MediaPlayer(_libVLC);
cam_ip = ip;
this.Text = name;
string rtsp_link = "rtsp://" + ip + "/profile2/media.smp";
updateZoomSlider();
tbRtspLink.Text = rtsp_link;
videoView.MediaPlayer = _mp;
var media = new Media(_libVLC, new Uri(rtsp_link));
media.AddOption(":network-caching=25");
_mp.Play(media);
media.Dispose();
}
private void updateZoomSlider()
{
HttpWebRequest request = (HttpWebRequest)WebRequest.Create("http://" + cam_ip + "/stw-cgi/ptzcontrol.cgi?msubmenu=query&action=view&Query=Zoom");
request.Credentials = new NetworkCredential(MainForm.cam_username, MainForm.cam_password);
HttpWebResponse response = (HttpWebResponse)request.GetResponse();
string text;
using (var sr = new StreamReader(response.GetResponseStream())) { text = sr.ReadToEnd(); }
string[] resp_lines = text.Split(new[] { Environment.NewLine }, StringSplitOptions.RemoveEmptyEntries);
string zoom_value = resp_lines[1].Substring(resp_lines[1].LastIndexOf('=') + 1);
tbZoom.Value = int.Parse(zoom_value);
}
private void sendZoomValue(string zoom_pulse)
{
// clamp values to allowed range
if (int.Parse(zoom_pulse) < min_zoom_pulse)
{
zoom_pulse = min_zoom_pulse.ToString();
}
if (int.Parse(zoom_pulse) > max_zoom_pulse)
{
zoom_pulse = max_zoom_pulse.ToString();
}
HttpWebRequest request = (HttpWebRequest)WebRequest.Create("http://" + cam_ip + "/stw-cgi/ptzcontrol.cgi?msubmenu=absolute&action=control&ZoomPulse=" + zoom_pulse);
request.Credentials = new NetworkCredential(MainForm.cam_username, MainForm.cam_password);
HttpWebResponse response = (HttpWebResponse)request.GetResponse();
}
private void sendZoomValueManual(string zoom_value)
{
HttpWebRequest request = (HttpWebRequest)WebRequest.Create("http://" + cam_ip + "/stw-cgi/ptzcontrol.cgi?msubmenu=continuous&NormalizedSpeed=True&action=control&Channel=0&Zoom=" + zoom_value);
request.Credentials = new NetworkCredential(MainForm.cam_username, MainForm.cam_password);
HttpWebResponse response = (HttpWebResponse)request.GetResponse();
}
private void StreamViewer_FormClosed(object sender, FormClosedEventArgs e)
{
_mp.Stop();
_mp.Dispose();
_libVLC.Dispose();
}
private void tbZoom_KeyUp(object sender, KeyEventArgs e)
{
sendZoomValue(tbZoom.Value.ToString());
}
private void tbZoom_MouseUp(object sender, MouseEventArgs e)
{
sendZoomValue(tbZoom.Value.ToString());
}
private void timerUpdateZoom_Tick(object sender, EventArgs e)
{
updateZoomSlider();
}
private void btnZoomMax_Click(object sender, EventArgs e)
{
tbZoom.Value = max_zoom_pulse;
sendZoomValue(tbZoom.Value.ToString());
}
private void btnZoomMin_Click(object sender, EventArgs e)
{
tbZoom.Value = min_zoom_pulse;
sendZoomValue(tbZoom.Value.ToString());
}
private void cbTestZoom_CheckedChanged(object sender, EventArgs e)
{
if (cbTestZoom.Checked)
{
timerZoomTestSeq.Start();
} else
{
timerZoomTestSeq.Stop();
}
}
private void timerZoomTestSeq_Tick(object sender, EventArgs e)
{
if (zoomTestSeqFlag)
{
tbZoom.Value = min_zoom_pulse;
sendZoomValue(tbZoom.Value.ToString());
zoomTestSeqFlag = false;
} else
{
tbZoom.Value = max_zoom_pulse;
sendZoomValue(tbZoom.Value.ToString());
zoomTestSeqFlag = true;
}
}
}
}

151
StreamViewer.resx Normal file
View File

@@ -0,0 +1,151 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
Version 2.0
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</resheader>
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
<value>[base64 mime encoded serialized .NET Framework object]</value>
</data>
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment>
</data>
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<metadata name="timerUpdateZoom.TrayLocation" type="System.Drawing.Point, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
<value>17, 17</value>
</metadata>
<metadata name="timerZoomTestSeq.TrayLocation" type="System.Drawing.Point, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
<value>169, 17</value>
</metadata>
<assembly alias="System.Drawing" name="System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a" />
<data name="$this.Icon" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>
AAABAAEAEBAAAAEAIABoBAAAFgAAACgAAAAQAAAAIAAAAAEAIAAAAAAAQAQAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAkAAP9UAAD/VwAA
/w0AAAAAAAAAgwAAAMgAAADIAAAAyAAAALsAAAAAAAAAAAAAAAAAAADLAAAA0AAAGNQAAN/4AAD//wAA
//8AAP/iAAD/HgAAAIIAAAD/AAAA/wAAAP8AAAD/AAAAFgAAAAAAAAAUAAAA/wAAAP8AAIT/AAD//wAA
//8AAP//AAD//wAA/5cAAABYAAAA/wAAAP8AAAD/AAAA/wAAAEAAAAAAAAAALwAAAP8AAAD/AACi/wAA
//8AAP//AAD//wAA//8AAP+0AAAALwAAAP8AAAD/AAAA/wAAAP8AAABrAAAAAAAAAEsAAAD/AAAA/wAA
Wv8AAP//AAD//wAA//8AAP//AAD/bQAAAAgAAAD9AAAA/wAAAP8AAAD/AAAAlQAAAAAAAABnAAAA/wAA
AP8AAAD/AAB7/wAA9fcAAP/wAAD/iAAAAAIAAAAAAAAA2wAAAP8AAAD/AAAA/wAAAM8AAABQAAAApgAA
AP8AAAD/AAAA/wAAAP8AAABXAAAAAAAAAAAAAAAAAAAAAAAAALEAAAD/AAAA/wAAAP8AAAD/AAAA/wAA
AP8AAAD/AAAA/wAAAP8AAAD/AAAApgAAABsAAAAAAAAAAAAAAAAAAACIAAAA/wAAAP8AAAD/AAAA/wAA
AP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAADsAAAAMwAAAAAAAAAAAAAAXgAAAP8AAAD/AAAA/wAA
AP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAOMAAAALAAAAAAAAADQAAAD/AAAA/wAA
AP8AAAD/AAAAZAAAAAAAAAAAAAAADgAAAMoAAAD/AAAA/wAAAP8AAAD/AAAAawAAAAAAAAAMAAAA/gAA
AP8AAAD/AAAA/wAAAIwAAAAAAAAAAAAAAAYAAACwAAAA/wAAAP8AAAD/AAAA/wAAAKgAAAAAAAAAAAAA
AOEAAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAACfAAAAAAAA
AAAAAAC3AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD9AAAAPgAA
AAAAAAAAAAAAjQAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAADXAAAAUAAA
AAAAAAAAAAAAAAAAABkAAAA4AAAAOAAAADgAAAA4AAAAOAAAADgAAAA4AAAAOAAAADQAAAAZAAAAAAAA
AAAAAAAA//EAAAcAAAACAAAAAgAAAAIAAACCAQAAgAcAAIADAACAAQAAgAAAAIGAAACBwAAAwAAAAMAA
AADAAQAAwAcAAA==
</value>
</data>
</root>

BIN
favicon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

View File

@@ -0,0 +1,40 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>WinExe</OutputType>
<TargetFramework>net9.0-windows7.0</TargetFramework>
<RootNamespace>robospot_camera_finder</RootNamespace>
<Nullable>enable</Nullable>
<UseWindowsForms>true</UseWindowsForms>
<ImplicitUsings>enable</ImplicitUsings>
<StartupObject>robospot_camera_finder.Program</StartupObject>
<ApplicationIcon>favicon.ico</ApplicationIcon>
</PropertyGroup>
<ItemGroup>
<Content Include="favicon.ico" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="LibVLCSharp" Version="3.8.2" />
<PackageReference Include="LibVLCSharp.Forms" Version="3.8.2" />
<PackageReference Include="LibVLCSharp.WinForms" Version="3.8.2" />
<PackageReference Include="VideoLAN.LibVLC.Windows" Version="3.0.20" />
</ItemGroup>
<ItemGroup>
<Compile Update="Properties\Resources.Designer.cs">
<DesignTime>True</DesignTime>
<AutoGen>True</AutoGen>
<DependentUpon>Resources.resx</DependentUpon>
</Compile>
</ItemGroup>
<ItemGroup>
<EmbeddedResource Update="Properties\Resources.resx">
<Generator>ResXFileCodeGenerator</Generator>
<LastGenOutput>Resources.Designer.cs</LastGenOutput>
</EmbeddedResource>
</ItemGroup>
</Project>

View File

@@ -0,0 +1,25 @@
Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 17
VisualStudioVersion = 17.5.33516.290
MinimumVisualStudioVersion = 10.0.40219.1
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "robospot-camera-finder", "robospot-camera-finder.csproj", "{D68CD277-4F05-4BEC-879C-E2097CC4BEDC}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{D68CD277-4F05-4BEC-879C-E2097CC4BEDC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{D68CD277-4F05-4BEC-879C-E2097CC4BEDC}.Debug|Any CPU.Build.0 = Debug|Any CPU
{D68CD277-4F05-4BEC-879C-E2097CC4BEDC}.Release|Any CPU.ActiveCfg = Release|Any CPU
{D68CD277-4F05-4BEC-879C-E2097CC4BEDC}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {23A9FE53-49F6-4D0C-951F-CF3CA11F091C}
EndGlobalSection
EndGlobal