Skip to content

Commit

Permalink
Upgraded HomeAutio.Mqtt.Core and added broker TLS support, as well as…
Browse files Browse the repository at this point in the history
… separate refresh interval for detecting motion
  • Loading branch information
i8beef committed Jul 29, 2019
1 parent 34cb096 commit 6f1e854
Show file tree
Hide file tree
Showing 4 changed files with 173 additions and 19 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,8 @@
<PackageReference Include="I8Beef.CodeAnalysis.RuleSet" Version="1.0.15">
<PrivateAssets>all</PrivateAssets>
</PackageReference>
<PackageReference Include="HomeAutio.Mqtt.Core" Version="3.0.0.45" />
<PackageReference Include="I8Beef.UniFi.Video" Version="1.1.0.5" />
<PackageReference Include="HomeAutio.Mqtt.Core" Version="3.0.0.51" />
<PackageReference Include="I8Beef.UniFi.Video" Version="1.1.0.7" />
<PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="2.1.1" />
<PackageReference Include="Microsoft.Extensions.Hosting" Version="2.1.1" />
<PackageReference Include="Serilog.Extensions.Logging" Version="2.0.2" />
Expand Down
55 changes: 51 additions & 4 deletions src/HomeAutio.Mqtt.UnifiVideo/Program.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
using System;
using System.IO;
using System.Linq;
using System.Security.Cryptography.X509Certificates;
using System.Threading.Tasks;
using I8Beef.UniFi.Video;
using Microsoft.Extensions.Configuration;
Expand Down Expand Up @@ -90,16 +93,60 @@ private static IHostBuilder CreateHostBuilder(IConfiguration config)
var brokerSettings = new Core.BrokerSettings
{
BrokerIp = config.GetValue<string>("mqtt:brokerIp"),
BrokerPort = config.GetValue<int>("mqtt:brokerPort"),
BrokerPort = config.GetValue<int>("mqtt:brokerPort", 1883),
BrokerUsername = config.GetValue<string>("mqtt:brokerUsername"),
BrokerPassword = config.GetValue<string>("mqtt:brokerPassword")
BrokerPassword = config.GetValue<string>("mqtt:brokerPassword"),
BrokerUseTls = config.GetValue<bool>("mqtt:brokerUseTls", false)
};

// TLS settings
if (brokerSettings.BrokerUseTls)
{
var brokerTlsSettings = new Core.BrokerTlsSettings
{
AllowUntrustedCertificates = config.GetValue<bool>("mqtt:brokerTlsSettings:allowUntrustedCertificates"),
IgnoreCertificateChainErrors = config.GetValue<bool>("mqtt:brokerTlsSettings:ignoreCertificateChainErrors"),
IgnoreCertificateRevocationErrors = config.GetValue<bool>("mqtt:brokerTlsSettings:ignoreCertificateRevocationErrors")
};

switch (config.GetValue<string>("mqtt:brokerTlsSettings:protocol", "1.2"))
{
case "1.0":
brokerTlsSettings.SslProtocol = System.Security.Authentication.SslProtocols.Tls;
break;
case "1.1":
brokerTlsSettings.SslProtocol = System.Security.Authentication.SslProtocols.Tls11;
break;
case "1.2":
default:
brokerTlsSettings.SslProtocol = System.Security.Authentication.SslProtocols.Tls12;
break;
}

var brokerTlsCertificatesSection = config.GetSection("mqtt:brokerTlsSettings:certificates");
brokerTlsSettings.Certificates = brokerTlsCertificatesSection.GetChildren()
.Select(x =>
{
var file = x.GetValue<string>("file");
var passPhrase = x.GetValue<string>("passPhrase");

if (!File.Exists(file))
throw new FileNotFoundException($"Broker Certificate '{file}' is missing!");

return !string.IsNullOrEmpty(passPhrase) ?
new X509Certificate2(file, passPhrase) :
new X509Certificate2(file);
}).ToList();

brokerSettings.BrokerTlsSettings = brokerTlsSettings;
}

return new UniFiVideoMqttService(
serviceProvider.GetRequiredService<ILogger<UniFiVideoMqttService>>(),
serviceProvider.GetRequiredService<IClient>(),
config.GetValue<string>("unifi:unifiName"),
config.GetValue<int>("unifi:refreshInterval"),
config.GetValue<string>("unifi:unifiName", "default"),
config.GetValue<int>("unifi:refreshInterval", 60),
config.GetValue<int>("unifi:detectMotionRefreshInterval", 60),
brokerSettings);
});
});
Expand Down
127 changes: 116 additions & 11 deletions src/HomeAutio.Mqtt.UnifiVideo/UniFiVideoMqttService.cs
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Timers;
using HomeAutio.Mqtt.Core;
using HomeAutio.Mqtt.Core.Utilities;
using I8Beef.UniFi.Video;
using I8Beef.UniFi.Video.Protocol.Camera;
using I8Beef.UniFi.Video.Protocol.Common;
using I8Beef.UniFi.Video.Protocol.Recording;
using Microsoft.Extensions.Logging;
using MQTTnet;
Expand All @@ -29,7 +29,9 @@ public class UniFiVideoMqttService : ServiceBase

private bool _disposed = false;
private System.Timers.Timer _refresh;
private System.Timers.Timer _detectMotionRefresh;
private int _refreshInterval;
private int _detectMotionRefreshInterval;

/// <summary>
/// Initializes a new instance of the <see cref="UniFiVideoMqttService"/> class.
Expand All @@ -38,17 +40,20 @@ public class UniFiVideoMqttService : ServiceBase
/// <param name="nvrClient">The UniFi Video client.</param>
/// <param name="nvrName">The target NVR name.</param>
/// <param name="refreshInterval">The refresh interval.</param>
/// <param name="detectMotionRefreshInterval">The detect motion refresh interval.</param>
/// <param name="brokerSettings">MQTT broker settings.</param>
public UniFiVideoMqttService(
ILogger<UniFiVideoMqttService> logger,
IClient nvrClient,
string nvrName,
int refreshInterval,
int detectMotionRefreshInterval,
BrokerSettings brokerSettings)
: base(logger, brokerSettings, "unifi/video/" + nvrName)
{
_log = logger;
_refreshInterval = refreshInterval * 1000;
_detectMotionRefreshInterval = detectMotionRefreshInterval * 1000;
SubscribedTopics.Add(TopicRoot + "/camera/+/+/set");

_client = nvrClient;
Expand All @@ -73,6 +78,17 @@ await GetInitialStatusAsync(cancellationToken)
_refresh.Elapsed += RefreshAsync;
_refresh.Interval = _refreshInterval;
_refresh.Start();

// Enable refresh
if (_detectMotionRefresh != null)
{
_detectMotionRefresh.Dispose();
}

_detectMotionRefresh = new System.Timers.Timer();
_detectMotionRefresh.Elapsed += DetectMotionAsync;
_detectMotionRefresh.Interval = _detectMotionRefreshInterval;
_detectMotionRefresh.Start();
}

/// <inheritdoc />
Expand All @@ -90,23 +106,106 @@ await GetInitialStatusAsync(cancellationToken)
/// </summary>
/// <param name="sender">Event sender.</param>
/// <param name="e">Event args.</param>
protected override void Mqtt_MqttMsgPublishReceived(object sender, MqttApplicationMessageReceivedEventArgs e)
protected override async void Mqtt_MqttMsgPublishReceived(object sender, MqttApplicationMessageReceivedEventArgs e)
{
var message = Encoding.UTF8.GetString(e.ApplicationMessage.Payload);
var message = e.ApplicationMessage.ConvertPayloadToString();
_log.LogInformation("MQTT message received for topic " + e.ApplicationMessage.Topic + ": " + message);

// Parse topic out
var topicWithoutRoot = e.ApplicationMessage.Topic.Substring(TopicRoot.Length + 8);
var cameraName = topicWithoutRoot.Substring(0, topicWithoutRoot.IndexOf('/'));
if (_cameraInfo.Values.Any(x => x.Name.Sluggify() == cameraName))
{
var cameraId = _cameraInfo.FirstOrDefault(x => x.Value.Name.Sluggify() == cameraName).Key;
var cameraTopic = topicWithoutRoot.Substring(topicWithoutRoot.IndexOf('/') + 1);

switch (cameraTopic)
{
case "recordMode/set":
await HandleRecordModeCommandAsync(cameraId, message)
.ConfigureAwait(false);
break;
}
}
}

/// <summary>
/// Handles a record mode command.
/// </summary>
/// <param name="cameraId">Camera id.</param>
/// <param name="value">Record mode value.</param>
/// <returns>An awaitable <see cref="Task"/>.</returns>
private async Task HandleRecordModeCommandAsync(string cameraId, string value)
{
var recordMode = RecordingMode.None;
switch (value)
{
case "always":
recordMode = RecordingMode.FullTime;
break;
case "motion":
recordMode = RecordingMode.Motion;
break;
case "none":
default:
recordMode = RecordingMode.None;
break;
}

await _client.SetRecordModeAsync(cameraId, recordMode)
.ConfigureAwait(false);
}

#endregion

#region UniFi Video implementation

/// <summary>
/// Heartbeat ping. Failure will result in the heartbeat being stopped, which will
/// make any future calls throw an exception as the heartbeat indicator will be disabled.
/// Refresh camera state.
/// </summary>
/// <param name="sender">Event sender.</param>
/// <param name="e">Event args.</param>
private async void RefreshAsync(object sender, ElapsedEventArgs e)
{
// Get camera states
var cameraInfo = (await _client.CameraAsync().ConfigureAwait(false)).ToDictionary(k => k.Id, v => v);

// Publish and changes in camera state
foreach (var cameraId in cameraInfo.Keys)
{
var cameraName = cameraInfo[cameraId].Name.Sluggify();

// Record mode
if (!_cameraInfo.ContainsKey(cameraId) ||
_cameraInfo[cameraId].RecordingSettings.FullTimeRecordEnabled != cameraInfo[cameraId].RecordingSettings.FullTimeRecordEnabled ||
_cameraInfo[cameraId].RecordingSettings.MotionRecordEnabled != cameraInfo[cameraId].RecordingSettings.MotionRecordEnabled)
{
var recordMode = "none";
if (cameraInfo[cameraId].RecordingSettings.FullTimeRecordEnabled)
recordMode = "always";

if (cameraInfo[cameraId].RecordingSettings.MotionRecordEnabled)
recordMode = "motion";

await MqttClient.PublishAsync(new MqttApplicationMessageBuilder()
.WithTopic($"{TopicRoot}/camera/{cameraName}/recordMode")
.WithPayload(recordMode)
.WithAtLeastOnceQoS()
.WithRetainFlag()
.Build())
.ConfigureAwait(false);
}

_cameraInfo[cameraId] = cameraInfo[cameraId];
}
}

/// <summary>
/// Refresh camera state.
/// </summary>
/// <param name="sender">Event sender.</param>
/// <param name="e">Event args.</param>
private async void DetectMotionAsync(object sender, ElapsedEventArgs e)
{
var now = e.SignalTime;

Expand All @@ -121,22 +220,22 @@ private async void RefreshAsync(object sender, ElapsedEventArgs e)
// Publish and changes in camera state
foreach (var cameraId in _cameraInfo.Keys)
{
var currentState = "close";
// Motion state
var currentMotionState = "close";
if (inProgressRecordings.Any(x => x.Cameras[0] == cameraId))
currentState = "open";
currentMotionState = "open";

// If this is a new state, publish
if (!_currentMotionStates.ContainsKey(cameraId) || _currentMotionStates[cameraId] != currentState)
if (!_currentMotionStates.ContainsKey(cameraId) || _currentMotionStates[cameraId] != currentMotionState)
{
await MqttClient.PublishAsync(new MqttApplicationMessageBuilder()
.WithTopic($"{TopicRoot}/camera/{_cameraInfo[cameraId].Name.Sluggify()}/motion")
.WithPayload(currentState)
.WithPayload(currentMotionState)
.WithAtLeastOnceQoS()
.WithRetainFlag()
.Build())
.ConfigureAwait(false);

_currentMotionStates[cameraId] = currentState;
_currentMotionStates[cameraId] = currentMotionState;
}
}
}
Expand Down Expand Up @@ -166,6 +265,12 @@ protected override void Dispose(bool disposing)

if (disposing)
{
if (_detectMotionRefresh != null)
{
_detectMotionRefresh.Stop();
_detectMotionRefresh.Dispose();
}

if (_refresh != null)
{
_refresh.Stop();
Expand Down
6 changes: 4 additions & 2 deletions src/HomeAutio.Mqtt.UnifiVideo/appsettings.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,15 @@
"unifiUsername": "blank",
"unifiPassword": "blank",
"unifiDisableSslCheck": "true",
"refreshInterval": 1
"refreshInterval": 1,
"detectMotionRefreshInterval": 1
},
"mqtt": {
"brokerIp": "localhost",
"brokerPort": 1883,
"brokerUsername": null,
"brokerPassword": null
"brokerPassword": null,
"brokerUseTls": false
},
"Serilog": {
"Enrich": [ "FromLogContext" ],
Expand Down

0 comments on commit 6f1e854

Please sign in to comment.