Skip to content

Commit

Permalink
Add ONVIF PTZ sample (#108)
Browse files Browse the repository at this point in the history
## 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
  • Loading branch information
dominicbetts authored Nov 19, 2024
1 parent 9ba903d commit ce36254
Show file tree
Hide file tree
Showing 137 changed files with 5,667 additions and 0 deletions.
484 changes: 484 additions & 0 deletions samples/aio-onvif-connector-ptz-demo/.gitignore

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 17
VisualStudioVersion = 17.5.002.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}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
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}.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
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {8AC4980E-CA54-4BB8-B99D-BC2A701A68CF}
EndGlobalSection
EndGlobal
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
<Project Sdk="Microsoft.NET.Sdk">

<ItemGroup>
<ProjectReference Include="..\PTZ\PTZ.csproj" />
</ItemGroup>

<ItemGroup>
<PackageReference Include="Azure.Iot.Operations.Mqtt" Version="0.4.225" />
</ItemGroup>

<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net8.0</TargetFramework>
<RootNamespace>Aio.Onvif.Connector.Ptz.Demo</RootNamespace>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>

</Project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
using Aio.Onvif.Connector.Ptz.Demo;
using Azure.Iot.Operations.Mqtt.Session;
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))
{
Console.Error.WriteLine("Invalid host");
Environment.Exit(1);
}

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

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

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 mqttClientTcpOptions = new MqttClientTcpOptions(host, port);

var mqttClientOptions = new MqttClientOptions(mqttClientTcpOptions) { SessionExpiryInterval = 60 };

var mqttSessionClient = new MqttSessionClient(new MqttSessionClientOptions());

await mqttSessionClient.ConnectAsync(mqttClientOptions).ConfigureAwait(true);
var client = new PtzClient(mqttSessionClient);
client.CustomTopicTokenMap.Add("asset", assetName);
client.CustomTopicTokenMap.Add("namespace", aioNamespace);

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

while (true)
{
var key = Console.ReadKey(true).Key;
if (key == ConsoleKey.Q)
{
break;
}

(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;
}

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

await client.RelativeMoveAsync(request);
}
catch (System.Exception)
{
// Bad request is expected if the camera reaches the limit
}

await Task.Delay(200).ConfigureAwait(true);
}

Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
using System;
using Azure.Iot.Operations.Protocol;
using static PTZ.dtmi_onvif_ptz__1.Ptz;

namespace Aio.Onvif.Connector.Ptz.Demo;

public class PtzClient : Client
{
public PtzClient(IMqttPubSubClient mqttClient) : base(mqttClient)
{
}
}
26 changes: 26 additions & 0 deletions samples/aio-onvif-connector-ptz-demo/PTZ/BytesJsonConverter.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
/* Code generated by Azure.Iot.Operations.ProtocolCompiler; DO NOT EDIT. */

namespace PTZ
{
using System;
using System.Text.Json;
using System.Text.Json.Serialization;

/// <summary>
/// Class for customized JSON conversion of <c>byte[]</c> values to/from Base64 string representations per RFC 4648.
/// </summary>
internal sealed class BytesJsonConverter : JsonConverter<byte[]>
{
/// <inheritdoc/>
public override byte[] Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
return Convert.FromBase64String(reader.GetString()!);
}

/// <inheritdoc/>
public override void Write(Utf8JsonWriter writer, byte[] value, JsonSerializerOptions options)
{
writer.WriteStringValue(Convert.ToBase64String(value));
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
/* Code generated by Azure.Iot.Operations.ProtocolCompiler; DO NOT EDIT. */

namespace PTZ
{
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;

/// <summary>
/// An implementation of <c>IReadOnlyDictionary</c> that combines two <c>IReadOnlyDictionary</c> objects by prefixng their string keys.
/// </summary>
/// <typeparam name="TKey">The type of keys in the combined dictionary.</typeparam>
/// <typeparam name="TValue">The type of values in the combined dictionary.</typeparam>
public class CombinedPrefixedReadOnlyDictionary<TValue> : IReadOnlyDictionary<string, TValue>
{
private string prefix1;
private IReadOnlyDictionary<string, TValue> dict1;
private string prefix2;
private IReadOnlyDictionary<string, TValue> dict2;

/// <summary>
/// Initializes a new instance of the <see cref="CombinedPrefixedReadOnlyDictionary{TValue}"/> class.
/// </summary>
/// <param name="prefix1">The prefix for keys in <paramref name="dict1"/>.</param>
/// <param name="dict1">One of the <c>IReadOnlyDictionary</c> objects to combine.</param>
/// <param name="prefix2">The prefix for keys in <paramref name="dict2"/>.</param>
/// <param name="dict2">The other <c>IReadOnlyDictionary</c> object to combine.</param>
public CombinedPrefixedReadOnlyDictionary(
string prefix1,
IReadOnlyDictionary<string, TValue> dict1,
string prefix2,
IReadOnlyDictionary<string, TValue> dict2)
{
ArgumentNullException.ThrowIfNull(prefix1, nameof(prefix1));
ArgumentNullException.ThrowIfNull(dict1, nameof(dict1));
ArgumentNullException.ThrowIfNull(prefix2, nameof(prefix2));
ArgumentNullException.ThrowIfNull(dict2, nameof(dict2));

this.prefix1 = prefix1;
this.dict1 = dict1;
this.prefix2 = prefix2;
this.dict2 = dict2;
}

/// <inheritdoc/>
IEnumerable<string> IReadOnlyDictionary<string, TValue>.Keys => this.dict1.Keys.Select(k => $"{this.prefix1}{k}").Concat(this.dict2.Keys.Select(k => $"{this.prefix2}{k}"));

/// <inheritdoc/>
IEnumerable<TValue> IReadOnlyDictionary<string, TValue>.Values => this.dict1.Values.Concat(this.dict2.Values);

/// <inheritdoc/>
int IReadOnlyCollection<KeyValuePair<string, TValue>>.Count => this.dict1.Count + this.dict2.Count;

/// <inheritdoc/>
TValue IReadOnlyDictionary<string, TValue>.this[string key] =>
key.StartsWith(this.prefix1, StringComparison.InvariantCulture) && this.dict1.TryGetValue(key.Substring(this.prefix1.Length), out TValue? value1) ? value1 :
key.StartsWith(this.prefix2, StringComparison.InvariantCulture) && this.dict2.TryGetValue(key.Substring(this.prefix2.Length), out TValue? value2) ? value2 :
default(TValue)!;

/// <inheritdoc/>
bool IReadOnlyDictionary<string, TValue>.ContainsKey(string key)
{
return
key.StartsWith(this.prefix1, StringComparison.InvariantCulture) && this.dict1.ContainsKey(key.Substring(this.prefix1.Length)) ||
key.StartsWith(this.prefix2, StringComparison.InvariantCulture) && this.dict2.ContainsKey(key.Substring(this.prefix2.Length));
}

/// <inheritdoc/>
IEnumerator<KeyValuePair<string, TValue>> IEnumerable<KeyValuePair<string, TValue>>.GetEnumerator()
{
foreach (var item in this.dict1)
{
yield return new KeyValuePair<string, TValue>($"{this.prefix1}{item.Key}", item.Value);
}

foreach (var item in this.dict2)
{
yield return new KeyValuePair<string, TValue>($"{this.prefix2}{item.Key}", item.Value);
}
}

/// <inheritdoc/>
bool IReadOnlyDictionary<string, TValue>.TryGetValue(string key, out TValue value)
{
if (key.StartsWith(this.prefix1, StringComparison.InvariantCulture) && this.dict1.TryGetValue(key.Substring(this.prefix1.Length), out TValue? value1))
{
value = value1;
return true;
}

if (key.StartsWith(this.prefix2, StringComparison.InvariantCulture) && this.dict2.TryGetValue(key.Substring(this.prefix2.Length), out TValue? value2))
{
value = value2;
return true;
}

value = default(TValue)!;
return false;
}

/// <inheritdoc/>
IEnumerator IEnumerable.GetEnumerator()
{
return ((IEnumerable<KeyValuePair<string, TValue>>)this).GetEnumerator();
}
}
}
27 changes: 27 additions & 0 deletions samples/aio-onvif-connector-ptz-demo/PTZ/DateJsonConverter.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
/* Code generated by Azure.Iot.Operations.ProtocolCompiler; DO NOT EDIT. */

namespace PTZ
{
using System;
using System.Globalization;
using System.Text.Json;
using System.Text.Json.Serialization;

/// <summary>
/// Class for customized JSON conversion of <c>DateOnly</c> values to/from string representations in ISO 8601 Date format.
/// </summary>
internal sealed class DateJsonConverter : JsonConverter<DateOnly>
{
/// <inheritdoc/>
public override DateOnly Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
return DateOnly.Parse(reader.GetString()!, CultureInfo.InvariantCulture);
}

/// <inheritdoc/>
public override void Write(Utf8JsonWriter writer, DateOnly value, JsonSerializerOptions options)
{
writer.WriteStringValue(value.ToString("o", CultureInfo.InvariantCulture));
}
}
}
27 changes: 27 additions & 0 deletions samples/aio-onvif-connector-ptz-demo/PTZ/DecimalJsonConverter.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
/* Code generated by Azure.Iot.Operations.ProtocolCompiler; DO NOT EDIT. */

namespace PTZ
{
using System;
using System.Text.Json;
using System.Text.Json.Serialization;
using PTZ;

/// <summary>
/// Class for customized JSON conversion of <c>DecimalString</c> values to/from strings.
/// </summary>
internal sealed class DecimalJsonConverter : JsonConverter<DecimalString>
{
/// <inheritdoc/>
public override DecimalString Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
return new DecimalString(reader.GetString()!);
}

/// <inheritdoc/>
public override void Write(Utf8JsonWriter writer, DecimalString value, JsonSerializerOptions options)
{
writer.WriteStringValue(value.ToString());
}
}
}
Loading

0 comments on commit ce36254

Please sign in to comment.