Skip to content

Commit ce36254

Browse files
authored
Add ONVIF PTZ sample (#108)
## Purpose Add the ONVIF PTZ demo app ## Does this introduce a breaking change? <!-- Mark one with an "x". --> ``` [ ] Yes [x] No ``` ## Pull Request Type What kind of change does this Pull Request introduce? <!-- Please check the one that applies to this PR using "x". --> ``` [ ] Bugfix [ ] Feature [ ] Code style update (formatting, local variables) [ ] Refactoring (no functional changes, no api changes) [x] Documentation content changes [ ] Other... Please describe: ``` ## Other Information This sample is referenced from Microsoft Learn docs
1 parent 9ba903d commit ce36254

File tree

137 files changed

+5667
-0
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

137 files changed

+5667
-0
lines changed

samples/aio-onvif-connector-ptz-demo/.gitignore

+484
Large diffs are not rendered by default.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
2+
Microsoft Visual Studio Solution File, Format Version 12.00
3+
# Visual Studio Version 17
4+
VisualStudioVersion = 17.5.002.0
5+
MinimumVisualStudioVersion = 10.0.40219.1
6+
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}"
7+
EndProject
8+
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PTZ", "PTZ\PTZ.csproj", "{2EC99FE6-8C42-4AAE-ABCD-DE4291ED2655}"
9+
EndProject
10+
Global
11+
GlobalSection(SolutionConfigurationPlatforms) = preSolution
12+
Debug|Any CPU = Debug|Any CPU
13+
Release|Any CPU = Release|Any CPU
14+
EndGlobalSection
15+
GlobalSection(ProjectConfigurationPlatforms) = postSolution
16+
{BE93AC76-C663-47D1-9C3A-E18E32E18A3F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
17+
{BE93AC76-C663-47D1-9C3A-E18E32E18A3F}.Debug|Any CPU.Build.0 = Debug|Any CPU
18+
{BE93AC76-C663-47D1-9C3A-E18E32E18A3F}.Release|Any CPU.ActiveCfg = Release|Any CPU
19+
{BE93AC76-C663-47D1-9C3A-E18E32E18A3F}.Release|Any CPU.Build.0 = Release|Any CPU
20+
{2EC99FE6-8C42-4AAE-ABCD-DE4291ED2655}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
21+
{2EC99FE6-8C42-4AAE-ABCD-DE4291ED2655}.Debug|Any CPU.Build.0 = Debug|Any CPU
22+
{2EC99FE6-8C42-4AAE-ABCD-DE4291ED2655}.Release|Any CPU.ActiveCfg = Release|Any CPU
23+
{2EC99FE6-8C42-4AAE-ABCD-DE4291ED2655}.Release|Any CPU.Build.0 = Release|Any CPU
24+
EndGlobalSection
25+
GlobalSection(SolutionProperties) = preSolution
26+
HideSolutionNode = FALSE
27+
EndGlobalSection
28+
GlobalSection(ExtensibilityGlobals) = postSolution
29+
SolutionGuid = {8AC4980E-CA54-4BB8-B99D-BC2A701A68CF}
30+
EndGlobalSection
31+
EndGlobal
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
<Project Sdk="Microsoft.NET.Sdk">
2+
3+
<ItemGroup>
4+
<ProjectReference Include="..\PTZ\PTZ.csproj" />
5+
</ItemGroup>
6+
7+
<ItemGroup>
8+
<PackageReference Include="Azure.Iot.Operations.Mqtt" Version="0.4.225" />
9+
</ItemGroup>
10+
11+
<PropertyGroup>
12+
<OutputType>Exe</OutputType>
13+
<TargetFramework>net8.0</TargetFramework>
14+
<RootNamespace>Aio.Onvif.Connector.Ptz.Demo</RootNamespace>
15+
<ImplicitUsings>enable</ImplicitUsings>
16+
<Nullable>enable</Nullable>
17+
</PropertyGroup>
18+
19+
</Project>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
using Aio.Onvif.Connector.Ptz.Demo;
2+
using Azure.Iot.Operations.Mqtt.Session;
3+
using Azure.Iot.Operations.Protocol.Models;
4+
using PTZ.dtmi_onvif_ptz__1;
5+
6+
Console.Write("Mqtt Broker Host: ");
7+
var host = Console.ReadLine();
8+
if (string.IsNullOrWhiteSpace(host))
9+
{
10+
Console.Error.WriteLine("Invalid host");
11+
Environment.Exit(1);
12+
}
13+
14+
Console.Write("Mqtt Broker Port: ");
15+
if (!int.TryParse(Console.ReadLine(), out var port))
16+
{
17+
Console.Error.WriteLine("Invalid port number");
18+
Environment.Exit(1);
19+
}
20+
21+
Console.Write("AIO Namespace: ");
22+
var aioNamespace = Console.ReadLine();
23+
if (string.IsNullOrWhiteSpace(aioNamespace))
24+
{
25+
Console.Error.WriteLine("Invalid AIO namespace");
26+
Environment.Exit(1);
27+
}
28+
29+
Console.Write("Asset Name: ");
30+
var assetName = Console.ReadLine();
31+
if (string.IsNullOrWhiteSpace(assetName))
32+
{
33+
Console.Error.WriteLine("Invalid asset name");
34+
Environment.Exit(1);
35+
}
36+
37+
Console.Write("Profile Token: ");
38+
var profileToken = Console.ReadLine();
39+
if (string.IsNullOrWhiteSpace(profileToken))
40+
{
41+
Console.Error.WriteLine("Invalid profile token");
42+
Environment.Exit(1);
43+
}
44+
45+
Console.Clear();
46+
47+
var mqttClientTcpOptions = new MqttClientTcpOptions(host, port);
48+
49+
var mqttClientOptions = new MqttClientOptions(mqttClientTcpOptions) { SessionExpiryInterval = 60 };
50+
51+
var mqttSessionClient = new MqttSessionClient(new MqttSessionClientOptions());
52+
53+
await mqttSessionClient.ConnectAsync(mqttClientOptions).ConfigureAwait(true);
54+
var client = new PtzClient(mqttSessionClient);
55+
client.CustomTopicTokenMap.Add("asset", assetName);
56+
client.CustomTopicTokenMap.Add("namespace", aioNamespace);
57+
58+
Console.WriteLine("Use arrow keys or WASD to move camera, Q to quit");
59+
60+
while (true)
61+
{
62+
var key = Console.ReadKey(true).Key;
63+
if (key == ConsoleKey.Q)
64+
{
65+
break;
66+
}
67+
68+
(float x, float y)? delta = key switch
69+
{
70+
ConsoleKey.UpArrow => (0, 0.2f),
71+
ConsoleKey.DownArrow => (0, -0.2f),
72+
ConsoleKey.LeftArrow => (0.2f, 0),
73+
ConsoleKey.RightArrow => (-0.2f, 0),
74+
ConsoleKey.W => (0, 0.2f),
75+
ConsoleKey.S => (0, -0.2f),
76+
ConsoleKey.A => (0.2f, 0),
77+
ConsoleKey.D => (-0.2f, 0),
78+
_ => null
79+
};
80+
81+
if (delta == null)
82+
{
83+
continue;
84+
}
85+
86+
try
87+
{
88+
var request = new RelativeMoveRequestPayload
89+
{
90+
RelativeMove = new Object_Onvif_Ptz_RelativeMove__1
91+
{
92+
ProfileToken = profileToken,
93+
Translation = new Object_Onvif_Ptz_PTZVector__1
94+
{
95+
PanTilt = new Object_Onvif_Ptz_Vector2D__1
96+
{
97+
X = delta.Value.x,
98+
Y = delta.Value.y,
99+
}
100+
}
101+
}
102+
};
103+
104+
await client.RelativeMoveAsync(request);
105+
}
106+
catch (System.Exception)
107+
{
108+
// Bad request is expected if the camera reaches the limit
109+
}
110+
111+
await Task.Delay(200).ConfigureAwait(true);
112+
}
113+
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
using System;
2+
using Azure.Iot.Operations.Protocol;
3+
using static PTZ.dtmi_onvif_ptz__1.Ptz;
4+
5+
namespace Aio.Onvif.Connector.Ptz.Demo;
6+
7+
public class PtzClient : Client
8+
{
9+
public PtzClient(IMqttPubSubClient mqttClient) : base(mqttClient)
10+
{
11+
}
12+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
/* Code generated by Azure.Iot.Operations.ProtocolCompiler; DO NOT EDIT. */
2+
3+
namespace PTZ
4+
{
5+
using System;
6+
using System.Text.Json;
7+
using System.Text.Json.Serialization;
8+
9+
/// <summary>
10+
/// Class for customized JSON conversion of <c>byte[]</c> values to/from Base64 string representations per RFC 4648.
11+
/// </summary>
12+
internal sealed class BytesJsonConverter : JsonConverter<byte[]>
13+
{
14+
/// <inheritdoc/>
15+
public override byte[] Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
16+
{
17+
return Convert.FromBase64String(reader.GetString()!);
18+
}
19+
20+
/// <inheritdoc/>
21+
public override void Write(Utf8JsonWriter writer, byte[] value, JsonSerializerOptions options)
22+
{
23+
writer.WriteStringValue(Convert.ToBase64String(value));
24+
}
25+
}
26+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
/* Code generated by Azure.Iot.Operations.ProtocolCompiler; DO NOT EDIT. */
2+
3+
namespace PTZ
4+
{
5+
using System;
6+
using System.Collections;
7+
using System.Collections.Generic;
8+
using System.Linq;
9+
10+
/// <summary>
11+
/// An implementation of <c>IReadOnlyDictionary</c> that combines two <c>IReadOnlyDictionary</c> objects by prefixng their string keys.
12+
/// </summary>
13+
/// <typeparam name="TKey">The type of keys in the combined dictionary.</typeparam>
14+
/// <typeparam name="TValue">The type of values in the combined dictionary.</typeparam>
15+
public class CombinedPrefixedReadOnlyDictionary<TValue> : IReadOnlyDictionary<string, TValue>
16+
{
17+
private string prefix1;
18+
private IReadOnlyDictionary<string, TValue> dict1;
19+
private string prefix2;
20+
private IReadOnlyDictionary<string, TValue> dict2;
21+
22+
/// <summary>
23+
/// Initializes a new instance of the <see cref="CombinedPrefixedReadOnlyDictionary{TValue}"/> class.
24+
/// </summary>
25+
/// <param name="prefix1">The prefix for keys in <paramref name="dict1"/>.</param>
26+
/// <param name="dict1">One of the <c>IReadOnlyDictionary</c> objects to combine.</param>
27+
/// <param name="prefix2">The prefix for keys in <paramref name="dict2"/>.</param>
28+
/// <param name="dict2">The other <c>IReadOnlyDictionary</c> object to combine.</param>
29+
public CombinedPrefixedReadOnlyDictionary(
30+
string prefix1,
31+
IReadOnlyDictionary<string, TValue> dict1,
32+
string prefix2,
33+
IReadOnlyDictionary<string, TValue> dict2)
34+
{
35+
ArgumentNullException.ThrowIfNull(prefix1, nameof(prefix1));
36+
ArgumentNullException.ThrowIfNull(dict1, nameof(dict1));
37+
ArgumentNullException.ThrowIfNull(prefix2, nameof(prefix2));
38+
ArgumentNullException.ThrowIfNull(dict2, nameof(dict2));
39+
40+
this.prefix1 = prefix1;
41+
this.dict1 = dict1;
42+
this.prefix2 = prefix2;
43+
this.dict2 = dict2;
44+
}
45+
46+
/// <inheritdoc/>
47+
IEnumerable<string> IReadOnlyDictionary<string, TValue>.Keys => this.dict1.Keys.Select(k => $"{this.prefix1}{k}").Concat(this.dict2.Keys.Select(k => $"{this.prefix2}{k}"));
48+
49+
/// <inheritdoc/>
50+
IEnumerable<TValue> IReadOnlyDictionary<string, TValue>.Values => this.dict1.Values.Concat(this.dict2.Values);
51+
52+
/// <inheritdoc/>
53+
int IReadOnlyCollection<KeyValuePair<string, TValue>>.Count => this.dict1.Count + this.dict2.Count;
54+
55+
/// <inheritdoc/>
56+
TValue IReadOnlyDictionary<string, TValue>.this[string key] =>
57+
key.StartsWith(this.prefix1, StringComparison.InvariantCulture) && this.dict1.TryGetValue(key.Substring(this.prefix1.Length), out TValue? value1) ? value1 :
58+
key.StartsWith(this.prefix2, StringComparison.InvariantCulture) && this.dict2.TryGetValue(key.Substring(this.prefix2.Length), out TValue? value2) ? value2 :
59+
default(TValue)!;
60+
61+
/// <inheritdoc/>
62+
bool IReadOnlyDictionary<string, TValue>.ContainsKey(string key)
63+
{
64+
return
65+
key.StartsWith(this.prefix1, StringComparison.InvariantCulture) && this.dict1.ContainsKey(key.Substring(this.prefix1.Length)) ||
66+
key.StartsWith(this.prefix2, StringComparison.InvariantCulture) && this.dict2.ContainsKey(key.Substring(this.prefix2.Length));
67+
}
68+
69+
/// <inheritdoc/>
70+
IEnumerator<KeyValuePair<string, TValue>> IEnumerable<KeyValuePair<string, TValue>>.GetEnumerator()
71+
{
72+
foreach (var item in this.dict1)
73+
{
74+
yield return new KeyValuePair<string, TValue>($"{this.prefix1}{item.Key}", item.Value);
75+
}
76+
77+
foreach (var item in this.dict2)
78+
{
79+
yield return new KeyValuePair<string, TValue>($"{this.prefix2}{item.Key}", item.Value);
80+
}
81+
}
82+
83+
/// <inheritdoc/>
84+
bool IReadOnlyDictionary<string, TValue>.TryGetValue(string key, out TValue value)
85+
{
86+
if (key.StartsWith(this.prefix1, StringComparison.InvariantCulture) && this.dict1.TryGetValue(key.Substring(this.prefix1.Length), out TValue? value1))
87+
{
88+
value = value1;
89+
return true;
90+
}
91+
92+
if (key.StartsWith(this.prefix2, StringComparison.InvariantCulture) && this.dict2.TryGetValue(key.Substring(this.prefix2.Length), out TValue? value2))
93+
{
94+
value = value2;
95+
return true;
96+
}
97+
98+
value = default(TValue)!;
99+
return false;
100+
}
101+
102+
/// <inheritdoc/>
103+
IEnumerator IEnumerable.GetEnumerator()
104+
{
105+
return ((IEnumerable<KeyValuePair<string, TValue>>)this).GetEnumerator();
106+
}
107+
}
108+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
/* Code generated by Azure.Iot.Operations.ProtocolCompiler; DO NOT EDIT. */
2+
3+
namespace PTZ
4+
{
5+
using System;
6+
using System.Globalization;
7+
using System.Text.Json;
8+
using System.Text.Json.Serialization;
9+
10+
/// <summary>
11+
/// Class for customized JSON conversion of <c>DateOnly</c> values to/from string representations in ISO 8601 Date format.
12+
/// </summary>
13+
internal sealed class DateJsonConverter : JsonConverter<DateOnly>
14+
{
15+
/// <inheritdoc/>
16+
public override DateOnly Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
17+
{
18+
return DateOnly.Parse(reader.GetString()!, CultureInfo.InvariantCulture);
19+
}
20+
21+
/// <inheritdoc/>
22+
public override void Write(Utf8JsonWriter writer, DateOnly value, JsonSerializerOptions options)
23+
{
24+
writer.WriteStringValue(value.ToString("o", CultureInfo.InvariantCulture));
25+
}
26+
}
27+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
/* Code generated by Azure.Iot.Operations.ProtocolCompiler; DO NOT EDIT. */
2+
3+
namespace PTZ
4+
{
5+
using System;
6+
using System.Text.Json;
7+
using System.Text.Json.Serialization;
8+
using PTZ;
9+
10+
/// <summary>
11+
/// Class for customized JSON conversion of <c>DecimalString</c> values to/from strings.
12+
/// </summary>
13+
internal sealed class DecimalJsonConverter : JsonConverter<DecimalString>
14+
{
15+
/// <inheritdoc/>
16+
public override DecimalString Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
17+
{
18+
return new DecimalString(reader.GetString()!);
19+
}
20+
21+
/// <inheritdoc/>
22+
public override void Write(Utf8JsonWriter writer, DecimalString value, JsonSerializerOptions options)
23+
{
24+
writer.WriteStringValue(value.ToString());
25+
}
26+
}
27+
}

0 commit comments

Comments
 (0)