Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
Original file line number Diff line number Diff line change
@@ -1,26 +1,60 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 17
VisualStudioVersion = 17.5.002.0
VisualStudioVersion = 17.5.2.0
MinimumVisualStudioVersion = 10.0.40219.1
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Aio.Onvif.Connector.Ptz.Demo", "Aio.Onvif.Connector.Ptz.Demo\Aio.Onvif.Connector.Ptz.Demo.csproj", "{BE93AC76-C663-47D1-9C3A-E18E32E18A3F}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PTZ", "PTZ\PTZ.csproj", "{2EC99FE6-8C42-4AAE-ABCD-DE4291ED2655}"
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MediaClient", "MediaClient\MediaClient.csproj", "{5FC4EEFA-CD63-44CB-8426-6F54DA096328}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PtzClient", "PtzClient\PtzClient.csproj", "{A3A934C1-6A52-4FCB-96C1-ACE0F4C50911}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Debug|x64 = Debug|x64
Debug|x86 = Debug|x86
Release|Any CPU = Release|Any CPU
Release|x64 = Release|x64
Release|x86 = Release|x86
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{BE93AC76-C663-47D1-9C3A-E18E32E18A3F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{BE93AC76-C663-47D1-9C3A-E18E32E18A3F}.Debug|Any CPU.Build.0 = Debug|Any CPU
{BE93AC76-C663-47D1-9C3A-E18E32E18A3F}.Debug|x64.ActiveCfg = Debug|Any CPU
{BE93AC76-C663-47D1-9C3A-E18E32E18A3F}.Debug|x64.Build.0 = Debug|Any CPU
{BE93AC76-C663-47D1-9C3A-E18E32E18A3F}.Debug|x86.ActiveCfg = Debug|Any CPU
{BE93AC76-C663-47D1-9C3A-E18E32E18A3F}.Debug|x86.Build.0 = Debug|Any CPU
{BE93AC76-C663-47D1-9C3A-E18E32E18A3F}.Release|Any CPU.ActiveCfg = Release|Any CPU
{BE93AC76-C663-47D1-9C3A-E18E32E18A3F}.Release|Any CPU.Build.0 = Release|Any CPU
{2EC99FE6-8C42-4AAE-ABCD-DE4291ED2655}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{2EC99FE6-8C42-4AAE-ABCD-DE4291ED2655}.Debug|Any CPU.Build.0 = Debug|Any CPU
{2EC99FE6-8C42-4AAE-ABCD-DE4291ED2655}.Release|Any CPU.ActiveCfg = Release|Any CPU
{2EC99FE6-8C42-4AAE-ABCD-DE4291ED2655}.Release|Any CPU.Build.0 = Release|Any CPU
{BE93AC76-C663-47D1-9C3A-E18E32E18A3F}.Release|x64.ActiveCfg = Release|Any CPU
{BE93AC76-C663-47D1-9C3A-E18E32E18A3F}.Release|x64.Build.0 = Release|Any CPU
{BE93AC76-C663-47D1-9C3A-E18E32E18A3F}.Release|x86.ActiveCfg = Release|Any CPU
{BE93AC76-C663-47D1-9C3A-E18E32E18A3F}.Release|x86.Build.0 = Release|Any CPU
{5FC4EEFA-CD63-44CB-8426-6F54DA096328}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{5FC4EEFA-CD63-44CB-8426-6F54DA096328}.Debug|Any CPU.Build.0 = Debug|Any CPU
{5FC4EEFA-CD63-44CB-8426-6F54DA096328}.Debug|x64.ActiveCfg = Debug|Any CPU
{5FC4EEFA-CD63-44CB-8426-6F54DA096328}.Debug|x64.Build.0 = Debug|Any CPU
{5FC4EEFA-CD63-44CB-8426-6F54DA096328}.Debug|x86.ActiveCfg = Debug|Any CPU
{5FC4EEFA-CD63-44CB-8426-6F54DA096328}.Debug|x86.Build.0 = Debug|Any CPU
{5FC4EEFA-CD63-44CB-8426-6F54DA096328}.Release|Any CPU.ActiveCfg = Release|Any CPU
{5FC4EEFA-CD63-44CB-8426-6F54DA096328}.Release|Any CPU.Build.0 = Release|Any CPU
{5FC4EEFA-CD63-44CB-8426-6F54DA096328}.Release|x64.ActiveCfg = Release|Any CPU
{5FC4EEFA-CD63-44CB-8426-6F54DA096328}.Release|x64.Build.0 = Release|Any CPU
{5FC4EEFA-CD63-44CB-8426-6F54DA096328}.Release|x86.ActiveCfg = Release|Any CPU
{5FC4EEFA-CD63-44CB-8426-6F54DA096328}.Release|x86.Build.0 = Release|Any CPU
{A3A934C1-6A52-4FCB-96C1-ACE0F4C50911}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{A3A934C1-6A52-4FCB-96C1-ACE0F4C50911}.Debug|Any CPU.Build.0 = Debug|Any CPU
{A3A934C1-6A52-4FCB-96C1-ACE0F4C50911}.Debug|x64.ActiveCfg = Debug|Any CPU
{A3A934C1-6A52-4FCB-96C1-ACE0F4C50911}.Debug|x64.Build.0 = Debug|Any CPU
{A3A934C1-6A52-4FCB-96C1-ACE0F4C50911}.Debug|x86.ActiveCfg = Debug|Any CPU
{A3A934C1-6A52-4FCB-96C1-ACE0F4C50911}.Debug|x86.Build.0 = Debug|Any CPU
{A3A934C1-6A52-4FCB-96C1-ACE0F4C50911}.Release|Any CPU.ActiveCfg = Release|Any CPU
{A3A934C1-6A52-4FCB-96C1-ACE0F4C50911}.Release|Any CPU.Build.0 = Release|Any CPU
{A3A934C1-6A52-4FCB-96C1-ACE0F4C50911}.Release|x64.ActiveCfg = Release|Any CPU
{A3A934C1-6A52-4FCB-96C1-ACE0F4C50911}.Release|x64.Build.0 = Release|Any CPU
{A3A934C1-6A52-4FCB-96C1-ACE0F4C50911}.Release|x86.ActiveCfg = Release|Any CPU
{A3A934C1-6A52-4FCB-96C1-ACE0F4C50911}.Release|x86.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
<Project Sdk="Microsoft.NET.Sdk">

<ItemGroup>
<ProjectReference Include="..\PTZ\PTZ.csproj" />
<ItemGroup>
<PackageReference Include="Azure.Iot.Operations.Mqtt" Version="0.9.*-*" />
<PackageReference Include="System.CommandLine" Version="2.0.0-beta4.22272.1" />
</ItemGroup>

<ItemGroup>
<PackageReference Include="Azure.Iot.Operations.Mqtt" Version="0.4.225" />
</ItemGroup>
<ProjectReference Include="..\MediaClient\MediaClient.csproj" />
<ProjectReference Include="..\PtzClient\PtzClient.csproj" />
</ItemGroup>

<PropertyGroup>
<OutputType>Exe</OutputType>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
using System;
using Azure.Iot.Operations.Protocol;
using static MediaClient.Media.Media;

namespace Aio.Onvif.Connector.Ptz.Demo;

public class OnvifMediaClient : Client
{
public OnvifMediaClient(ApplicationContext applicationContext, IMqttPubSubClient mqttClient) : base(applicationContext, mqttClient)
{
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
using System;
using Azure.Iot.Operations.Protocol;
using static PtzClient.Ptz.Ptz;


namespace Aio.Onvif.Connector.Ptz.Demo;

public class OnvifPtzClient : Client
{
public OnvifPtzClient(ApplicationContext applicationContext, IMqttPubSubClient mqttClient) : base(applicationContext, mqttClient)
{
}
}
Original file line number Diff line number Diff line change
@@ -1,113 +1,193 @@
using Aio.Onvif.Connector.Ptz.Demo;
using System.CommandLine;
using Aio.Onvif.Connector.Ptz.Demo;
using Azure.Iot.Operations.Mqtt.Session;
using Azure.Iot.Operations.Protocol;
using Azure.Iot.Operations.Protocol.Models;
using PTZ.dtmi_onvif_ptz__1;

Console.Write("Mqtt Broker Host: ");
var host = Console.ReadLine();
if (string.IsNullOrWhiteSpace(host))
using MediaClient.Media;
using PtzClient.Ptz;

var hostOption = new Option<string>(["--mqtt-host", "-h"], description: "The Hostname or IP of the MQTT Listener the demo connects to", getDefaultValue: () => "localhost");
var portOption = new Option<int>(["--mqtt-port", "-p"], description: "The port of the MQTT Listener the demo connects to", getDefaultValue: () => 1883);
var namespaceOption = new Option<string>(["--namespace", "-n"], description: "The Kubernetes namespace AIO is deployed to", getDefaultValue: () => "azure-iot-operations");
var ptzAssetOption = new Option<string>(["--ptz-asset", "-pa"], description: "The name of the PTZ asset") { IsRequired = true };
var mediaAssetOption = new Option<string>(["--media-asset", "-ma"], description: "The name of the media asset") { IsRequired = true };
var modeOption = new Option<string>(["--mode", "-m"], description: "The method that should be used to move the camera", getDefaultValue: () => "relative").FromAmong("relative", "continuous");

var rootCommand = new RootCommand("AIO ONVIF Connector Demo");
rootCommand.AddOption(hostOption);
rootCommand.AddOption(portOption);
rootCommand.AddOption(namespaceOption);
rootCommand.AddOption(ptzAssetOption);
rootCommand.AddOption(mediaAssetOption);
rootCommand.AddOption(modeOption);

rootCommand.SetHandler(async (host, port, @namespace, ptzAsset, mediaAsset, mode) =>
{
Console.Error.WriteLine("Invalid host");
Environment.Exit(1);
}
Console.WriteLine($"MQTT Host: {host}");
Console.WriteLine($"MQTT Port: {port}");
Console.WriteLine($"Namespace: {@namespace}");
Console.WriteLine($"PTZ Asset: {ptzAsset}");
Console.WriteLine($"Media Asset: {mediaAsset}");
Console.WriteLine($"Mode: {mode}");

Console.Write("Mqtt Broker Port: ");
if (!int.TryParse(Console.ReadLine(), out var port))
{
Console.Error.WriteLine("Invalid port number");
Environment.Exit(1);
}
var mqttClientTcpOptions = new MqttClientTcpOptions(host, port);

Console.Write("AIO Namespace: ");
var aioNamespace = Console.ReadLine();
if (string.IsNullOrWhiteSpace(aioNamespace))
{
Console.Error.WriteLine("Invalid AIO namespace");
Environment.Exit(1);
}
var mqttClientOptions = new MqttClientOptions(mqttClientTcpOptions) { SessionExpiryInterval = 60 };

Console.Write("Asset Name: ");
var assetName = Console.ReadLine();
if (string.IsNullOrWhiteSpace(assetName))
{
Console.Error.WriteLine("Invalid asset name");
Environment.Exit(1);
}

Console.Write("Profile Token: ");
var profileToken = Console.ReadLine();
if (string.IsNullOrWhiteSpace(profileToken))
{
Console.Error.WriteLine("Invalid profile token");
Environment.Exit(1);
}

Console.Clear();
var mqttSessionClient = new MqttSessionClient(new MqttSessionClientOptions());
await mqttSessionClient.ConnectAsync(mqttClientOptions);
var applicationContext = new ApplicationContext();
var ptzClient = new OnvifPtzClient(applicationContext, mqttSessionClient);
var mediaClient = new OnvifMediaClient(applicationContext, mqttSessionClient);

var mqttClientTcpOptions = new MqttClientTcpOptions(host, port);
var profiles = await mediaClient.GetProfilesAsync(new Dictionary<string, string>
{
{ "ex:namespace", @namespace },
{ "ex:asset", mediaAsset }
});
var profile = profiles.GetProfilesCommandResponse.Profiles.First();

var mqttClientOptions = new MqttClientOptions(mqttClientTcpOptions) { SessionExpiryInterval = 60 };
Console.WriteLine("Use arrow keys or WASD to move camera, Q to quit");

var mqttSessionClient = new MqttSessionClient(new MqttSessionClientOptions());
if (mode == "relative")
{
await StartRelativeMoveAsync(ptzClient, profile, @namespace, ptzAsset);
}
else if (mode == "continuous")
{
await StartContinuousMoveAsync(ptzClient, profile, @namespace, ptzAsset);
}

await mqttSessionClient.ConnectAsync(mqttClientOptions).ConfigureAwait(true);
var client = new PtzClient(mqttSessionClient);
client.CustomTopicTokenMap.Add("asset", assetName);
client.CustomTopicTokenMap.Add("namespace", aioNamespace);
await StartRelativeMoveAsync(ptzClient, profile, @namespace, ptzAsset);
}, hostOption, portOption, namespaceOption, ptzAssetOption, mediaAssetOption, modeOption);

Console.WriteLine("Use arrow keys or WASD to move camera, Q to quit");
await rootCommand.InvokeAsync(args);

while (true)
async Task StartRelativeMoveAsync(OnvifPtzClient ptzClient, Profile profile, string @namespace, string ptzAssetName)
{
var key = Console.ReadKey(true).Key;
if (key == ConsoleKey.Q)
while (true)
{
break;
}
var key = Console.ReadKey(true).Key;
if (key == ConsoleKey.Q)
{
return;
}

(float x, float y)? delta = key switch
{
ConsoleKey.UpArrow => (0, 0.2f),
ConsoleKey.DownArrow => (0, -0.2f),
ConsoleKey.LeftArrow => (0.2f, 0),
ConsoleKey.RightArrow => (-0.2f, 0),
ConsoleKey.W => (0, 0.2f),
ConsoleKey.S => (0, -0.2f),
ConsoleKey.A => (0.2f, 0),
ConsoleKey.D => (-0.2f, 0),
_ => null
};

if (delta == null)
{
continue;
}
(float x, float y)? delta = key switch
{
ConsoleKey.UpArrow => (0, 0.2f),
ConsoleKey.DownArrow => (0, -0.2f),
ConsoleKey.LeftArrow => (0.2f, 0),
ConsoleKey.RightArrow => (-0.2f, 0),
ConsoleKey.W => (0, 0.2f),
ConsoleKey.S => (0, -0.2f),
ConsoleKey.A => (0.2f, 0),
ConsoleKey.D => (-0.2f, 0),
_ => null
};

try
{
var request = new RelativeMoveRequestPayload
if (delta == null)
{
continue;
}

try
{
RelativeMove = new Object_Onvif_Ptz_RelativeMove__1
var request = new RelativeMoveRequestPayload
{
ProfileToken = profileToken,
Translation = new Object_Onvif_Ptz_PTZVector__1
RelativeMove = new RelativeMove
{
PanTilt = new Object_Onvif_Ptz_Vector2D__1
ProfileToken = profile.Token,
Translation = new Ptzvector
{
X = delta.Value.x,
Y = delta.Value.y,
PanTilt = new PtzClient.Ptz.Vector2d
{
X = delta.Value.x,
Y = delta.Value.y,
}
}
}
}
};
};

await client.RelativeMoveAsync(request);
await ptzClient.RelativeMoveAsync(request, new Dictionary<string, string>
{
{ "ex:namespace", @namespace },
{ "ex:asset", ptzAssetName }
});
}
catch (Exception e)
{
Console.WriteLine($"Error: {e.Message}");
// Bad request is expected if the camera reaches the limit
}

await Task.Delay(1000).ConfigureAwait(true);
}
catch (System.Exception)
}

async Task StartContinuousMoveAsync(OnvifPtzClient ptzClient, Profile profile, string @namespace, string ptzAssetName)
{
while (true)
{
// Bad request is expected if the camera reaches the limit
}
var key = Console.ReadKey(true).Key;
if (key == ConsoleKey.Q)
{
return;
}

(float x, float y)? delta = key switch
{
ConsoleKey.UpArrow => (0, 0.2f),
ConsoleKey.DownArrow => (0, -0.2f),
ConsoleKey.LeftArrow => (-0.2f, 0),
ConsoleKey.RightArrow => (0.2f, 0),
ConsoleKey.W => (0, 0.2f),
ConsoleKey.S => (0, -0.2f),
ConsoleKey.A => (-0.2f, 0),
ConsoleKey.D => (0.2f, 0),
_ => null
};

await Task.Delay(200).ConfigureAwait(true);
if (delta == null)
{
continue;
}

try
{
var request = new ContinuousMoveRequestPayload
{
ContinuousMove = new ContinuousMove
{
ProfileToken = profile.Token,
Velocity = new PtzClient.Ptz.Ptzspeed
{
PanTilt = new PtzClient.Ptz.Vector2d
{
X = delta.Value.x,
Y = delta.Value.y,
}
}
}
};

await ptzClient.ContinuousMoveAsync(request, new Dictionary<string, string>
{
{ "ex:namespace", @namespace },
{ "ex:asset", ptzAssetName }
});
await Task.Delay(1000).ConfigureAwait(true);
await ptzClient.StopAsync(new StopRequestPayload { Stop = new Stop { ProfileToken = profile.Token, PanTilt = true } }, new Dictionary<string, string>
{
{ "ex:namespace", @namespace },
{ "ex:asset", ptzAssetName }
});
}
catch (Exception e)
{
Console.WriteLine($"Error: {e.Message}");
// Bad request is expected if the camera reaches the limit
}
}
}

Loading