From 4ce3b1bf1ec6de873daf226e3de2ee639eefb8e4 Mon Sep 17 00:00:00 2001 From: jimmy Date: Sat, 2 Mar 2024 19:18:11 +0000 Subject: [PATCH 1/5] Bugfix for null meeting state and added framework for MQTT over websockets --- MainWindow.xaml | 1 + MainWindow.xaml.cs | 39 +++++++++++++++++++++++++++++++++++++-- 2 files changed, 38 insertions(+), 2 deletions(-) diff --git a/MainWindow.xaml b/MainWindow.xaml index a83c2cf..9657bd7 100644 --- a/MainWindow.xaml +++ b/MainWindow.xaml @@ -73,6 +73,7 @@ + diff --git a/MainWindow.xaml.cs b/MainWindow.xaml.cs index 7d1e41d..f4b038b 100644 --- a/MainWindow.xaml.cs +++ b/MainWindow.xaml.cs @@ -78,7 +78,7 @@ public static AppSettings Instance [JsonIgnore] public string MqttPassword { get; set; } - + public bool UseWebsockets { get; set; } [JsonIgnore] public string PlainTeamsToken { get; set; } // Properties @@ -834,6 +834,7 @@ private async void MainPage_Loaded(object sender, RoutedEventArgs e) RunMinimisedCheckBox.IsChecked = _settings.RunMinimized; MqttUserNameBox.Text = _settings.MqttUsername; UseTLS.IsChecked = _settings.UseTLS; + Websockets.IsChecked = _settings.UseWebsockets; IgnoreCert.IsChecked = _settings.IgnoreCertificateErrors; MQTTPasswordBox.Password = _settings.MqttPassword; MqttAddress.Text = _settings.MqttAddress; @@ -921,7 +922,24 @@ private async Task PublishConfigurations(MeetingUpdate meetingUpdate, AppSetting Model = "Teams2HA", Manufacturer = "JimmyWhite", }; - + // added to check if meeting update is null + if (meetingUpdate == null) + { + meetingUpdate = new MeetingUpdate + { + MeetingState = new MeetingState + { + IsMuted = false, + IsVideoOn = false, + IsHandRaised = false, + IsInMeeting = false, + IsRecordingOn = false, + IsBackgroundBlurred = false, + IsSharing = false, + HasUnreadMessages = false + } + }; + } string sensorKey = $"{deviceid}_{sensor}"; string sensorName = $"{deviceid}_{sensor}".ToLower().Replace(" ", "_"); string deviceClass = DetermineDeviceClass(sensor); @@ -985,6 +1003,7 @@ private async Task SaveSettingsAsync() settings.UseTLS = UseTLS.IsChecked ?? false; settings.IgnoreCertificateErrors = IgnoreCert.IsChecked ?? false; settings.RunMinimized = RunMinimisedCheckBox.IsChecked ?? false; + settings.UseWebsockets = Websockets.IsChecked ?? false; settings.RunAtWindowsBoot = RunAtWindowsBootCheckBox.IsChecked ?? false; if (string.IsNullOrEmpty(SensorPrefixBox.Text)) { @@ -1180,5 +1199,21 @@ private void ToggleThemeButton_Click(object sender, RoutedEventArgs e) #endregion Private Methods + private void Websockets_Checked(object sender, RoutedEventArgs e) + { + + _settings.UseWebsockets = true; + // Disable the mqtt port box + MqttPort.IsEnabled = false; + + + } + private void Websockets_Unchecked(object sender, RoutedEventArgs e) + { + + _settings.UseWebsockets = false; + MqttPort.IsEnabled = true; + + } } } \ No newline at end of file From d4b047575713ba34e75f12e0dce57caad32733d3 Mon Sep 17 00:00:00 2001 From: jimmy Date: Sat, 2 Mar 2024 19:22:26 +0000 Subject: [PATCH 2/5] code refactoring --- MainWindow.xaml.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/MainWindow.xaml.cs b/MainWindow.xaml.cs index f4b038b..4cf3f3c 100644 --- a/MainWindow.xaml.cs +++ b/MainWindow.xaml.cs @@ -1176,6 +1176,7 @@ private bool CheckIfMqttSettingsChanged(AppSettings newSettings) newSettings.MqttUsername != currentSettings.MqttUsername || newSettings.MqttPassword != currentSettings.MqttPassword || newSettings.UseTLS != currentSettings.UseTLS || + newSettings.UseWebsockets != currentSettings.UseWebsockets || newSettings.IgnoreCertificateErrors != currentSettings.IgnoreCertificateErrors; } private bool CheckIfSensorPrefixChanged(AppSettings newSettings) From 1cedae0607fde8e1fef119b9a1ea2d326f8939ac Mon Sep 17 00:00:00 2001 From: Jimmy White Date: Sun, 3 Mar 2024 13:09:26 +0000 Subject: [PATCH 3/5] Added support for websockets --- API/MqttClientWrapper.cs | 97 ++++++++++++++++++++-------------------- MainWindow.xaml.cs | 26 ++++++----- TEAMS2HA.csproj | 4 +- 3 files changed, 65 insertions(+), 62 deletions(-) diff --git a/API/MqttClientWrapper.cs b/API/MqttClientWrapper.cs index 4968171..d3ba26d 100644 --- a/API/MqttClientWrapper.cs +++ b/API/MqttClientWrapper.cs @@ -34,69 +34,70 @@ public bool IsAttemptingConnection get { return _isAttemptingConnection; } private set { _isAttemptingConnection = value; } } - public MqttClientWrapper(string clientId, string mqttBroker, string mqttPort, string username, string password, bool UseTLS, bool IgnoreCertificateErrors) + [Obsolete] + public MqttClientWrapper(string clientId, string mqttBroker, string mqttPort, string username, string password, bool useTLS, bool ignoreCertificateErrors, bool useWebsockets) { - var factory = new MqttFactory(); - _mqttClient = factory.CreateMqttClient() as MqttClient; + try + { + var factory = new MqttFactory(); + _mqttClient = (MqttClient?)factory.CreateMqttClient(); + + if (!int.TryParse(mqttPort, out int mqttportInt)) + { + mqttportInt = 1883; // Default MQTT port + Log.Warning($"Invalid MQTT port provided, defaulting to {mqttportInt}"); + } - int mqttportInt; - int.TryParse(mqttPort, out mqttportInt); - if (mqttportInt == 0) mqttportInt = 1883; + var mqttClientOptionsBuilder = new MqttClientOptionsBuilder() + .WithClientId(clientId) + .WithCredentials(username, password) + .WithCleanSession(); - var mqttClientOptionsBuilder = new MqttClientOptionsBuilder() - .WithClientId(clientId) - .WithCredentials(username, password) - .WithCleanSession(); + string protocol = useWebsockets ? "ws" : "tcp"; + string connectionType = useTLS ? "with TLS" : "without TLS"; - // If useTls is true or the port is 8883, configure the client to use TLS. - if (UseTLS || mqttportInt == 8883) - { - var untrusted = IgnoreCertificateErrors; - // Configure TLS options - mqttClientOptionsBuilder.WithTcpServer(mqttBroker, mqttportInt) + if (useWebsockets) + { + string websocketUri = useTLS ? $"wss://{mqttBroker}:{mqttportInt}" : $"ws://{mqttBroker}:{mqttportInt}"; + mqttClientOptionsBuilder.WithWebSocketServer(websocketUri); + Log.Information($"Configuring MQTT client for WebSocket {connectionType} connection to {websocketUri}"); + } + else + { + mqttClientOptionsBuilder.WithTcpServer(mqttBroker, mqttportInt); + Log.Information($"Configuring MQTT client for TCP {connectionType} connection to {mqttBroker}:{mqttportInt}"); + } - .WithTls(new MqttClientOptionsBuilderTlsParameters + if (useTLS) + { + mqttClientOptionsBuilder.WithTls(new MqttClientOptionsBuilderTlsParameters { UseTls = true, - AllowUntrustedCertificates = untrusted, - IgnoreCertificateChainErrors = untrusted, - IgnoreCertificateRevocationErrors = untrusted, + AllowUntrustedCertificates = ignoreCertificateErrors, + IgnoreCertificateChainErrors = ignoreCertificateErrors, + IgnoreCertificateRevocationErrors = ignoreCertificateErrors, CertificateValidationHandler = context => { - if(IgnoreCertificateErrors) - { - return true; - } - else - { - return false; - } - + Log.Debug($"Certificate validation for MQTT {protocol} connection: {context.Certificate.Subject}"); + return ignoreCertificateErrors; } }); - - - Log.Information($"MQTT Client Created with TLS on port {mqttPort}."); - ConnectionStatusChanged?.Invoke($"MQTT Client Created with TLS"); + } + _mqttOptions = mqttClientOptionsBuilder.Build(); + if (_mqttClient != null) + { + _mqttClient.ApplicationMessageReceivedAsync += OnMessageReceivedAsync; + } } - else - { - mqttClientOptionsBuilder.WithTcpServer(mqttBroker, mqttportInt); - Log.Information("MQTT Client Created with TCP."); - ConnectionStatusChanged?.Invoke($"MQTT Client Created with TCP"); - } - - _mqttOptions = mqttClientOptionsBuilder.Build(); - if (_mqttClient != null) + catch (Exception ex) { - _mqttClient.ApplicationMessageReceivedAsync += OnMessageReceivedAsync; + Log.Error(ex, "Failed to initialize MqttClientWrapper"); + throw; // Rethrowing the exception to handle it outside or log it as fatal depending on your error handling strategy. } - } - #endregion Public Constructors #region Public Events @@ -118,7 +119,7 @@ public async Task ConnectAsync() if (_mqttClient.IsConnected || _isAttemptingConnection) { Log.Information("MQTT client is already connected or connection attempt is in progress."); - + return; } @@ -134,7 +135,7 @@ public async Task ConnectAsync() Log.Information("Connected to MQTT broker."); if (_mqttClient.IsConnected) ConnectionStatusChanged?.Invoke("MQTT Status: Connected"); - + break; } catch (Exception ex) @@ -196,7 +197,7 @@ public static List GetEntityNames(string deviceId) $"sensor.{deviceId}_issharing", $"sensor.{deviceId}_hasunreadmessages", $"switch.{deviceId}_isbackgroundblurred" - + }; return entityNames; diff --git a/MainWindow.xaml.cs b/MainWindow.xaml.cs index 4cf3f3c..e4e9992 100644 --- a/MainWindow.xaml.cs +++ b/MainWindow.xaml.cs @@ -278,7 +278,8 @@ public MainWindow() _settings.MqttUsername, _settings.MqttPassword, _settings.UseTLS, - _settings.IgnoreCertificateErrors + _settings.IgnoreCertificateErrors, + _settings.UseWebsockets ); // Set the action to be performed when a new token is updated @@ -427,7 +428,8 @@ private void UpdateMqttClientWrapper() _settings.MqttUsername, _settings.MqttPassword, _settings.UseTLS, - _settings.IgnoreCertificateErrors + _settings.IgnoreCertificateErrors, + _settings.UseWebsockets ); @@ -1202,19 +1204,19 @@ private void ToggleThemeButton_Click(object sender, RoutedEventArgs e) private void Websockets_Checked(object sender, RoutedEventArgs e) { - - _settings.UseWebsockets = true; - // Disable the mqtt port box - MqttPort.IsEnabled = false; - - + + _settings.UseWebsockets = true; + // Disable the mqtt port box + // MqttPort.IsEnabled = false; + + } private void Websockets_Unchecked(object sender, RoutedEventArgs e) { - - _settings.UseWebsockets = false; - MqttPort.IsEnabled = true; - + + _settings.UseWebsockets = false; + // MqttPort.IsEnabled = true; + } } } \ No newline at end of file diff --git a/TEAMS2HA.csproj b/TEAMS2HA.csproj index cadb32b..bbe24b1 100644 --- a/TEAMS2HA.csproj +++ b/TEAMS2HA.csproj @@ -5,8 +5,8 @@ net7.0-windows enable true - 1.1.0.295 - 1.1.0.295 + 1.1.0.303 + 1.1.0.303 Assets\Square150x150Logo.scale-200.ico Teams2HA Square150x150Logo.scale-200.png From b232a362355490830259c694a3a99dd1441825ff Mon Sep 17 00:00:00 2001 From: Jimmy White Date: Sun, 3 Mar 2024 13:47:57 +0000 Subject: [PATCH 4/5] wss updates --- API/MqttClientWrapper.cs | 37 ++++++++++++++++++++++++++++++++++--- TEAMS2HA.csproj | 4 ++-- 2 files changed, 36 insertions(+), 5 deletions(-) diff --git a/API/MqttClientWrapper.cs b/API/MqttClientWrapper.cs index d3ba26d..13531a2 100644 --- a/API/MqttClientWrapper.cs +++ b/API/MqttClientWrapper.cs @@ -79,10 +79,41 @@ public MqttClientWrapper(string clientId, string mqttBroker, string mqttPort, st IgnoreCertificateRevocationErrors = ignoreCertificateErrors, CertificateValidationHandler = context => { - Log.Debug($"Certificate validation for MQTT {protocol} connection: {context.Certificate.Subject}"); - return ignoreCertificateErrors; + // Log the certificate subject + Log.Debug("Certificate Subject: {0}", context.Certificate.Subject); + + // This assumes you are trying to inspect the certificate directly; + // MQTTnet may not provide a direct IsValid flag or ChainErrors like .NET's X509Chain. + // Instead, you handle validation and log details manually: + + bool isValid = true; // You should define the logic to set this based on your validation requirements + + // Check for specific conditions, if necessary, such as expiry, issuer, etc. + // For example, if you want to ensure the certificate is issued by a specific entity: + //if (context.Certificate.Issuer != "CN=R3, O=Let's Encrypt, C=US") + //{ + // Log.Debug("Unexpected certificate issuer: {0}", context.Certificate.Issuer); + // isValid = false; // Set to false if the issuer is not the expected one + //} + + // Log any errors from the SSL policy errors if they exist + if (context.SslPolicyErrors != System.Net.Security.SslPolicyErrors.None) + { + Log.Debug("SSL policy errors: {0}", context.SslPolicyErrors.ToString()); + isValid = false; // Consider invalid if there are any SSL policy errors + } + + // You can decide to ignore certain errors by setting isValid to true regardless of the checks, + // but be careful as this might introduce security vulnerabilities. + if (ignoreCertificateErrors) + { + isValid = true; // Ignore certificate errors if your settings dictate + } + + return isValid; // Return the result of your checks } - }); + + }); } _mqttOptions = mqttClientOptionsBuilder.Build(); diff --git a/TEAMS2HA.csproj b/TEAMS2HA.csproj index bbe24b1..fa1061c 100644 --- a/TEAMS2HA.csproj +++ b/TEAMS2HA.csproj @@ -5,8 +5,8 @@ net7.0-windows enable true - 1.1.0.303 - 1.1.0.303 + 1.1.0.311 + 1.1.0.311 Assets\Square150x150Logo.scale-200.ico Teams2HA Square150x150Logo.scale-200.png From 0a5cc5cf5ec8d5a0c7974325ff8a0b49e7c8e5d2 Mon Sep 17 00:00:00 2001 From: Jimmy White Date: Sun, 3 Mar 2024 15:29:04 +0000 Subject: [PATCH 5/5] mqtt tidy ups --- API/MqttClientWrapper.cs | 72 ++++++++++++++++++++++------------------ MainWindow.xaml.cs | 18 ++++++---- TEAMS2HA.csproj | 4 +-- 3 files changed, 52 insertions(+), 42 deletions(-) diff --git a/API/MqttClientWrapper.cs b/API/MqttClientWrapper.cs index 13531a2..2702119 100644 --- a/API/MqttClientWrapper.cs +++ b/API/MqttClientWrapper.cs @@ -113,7 +113,7 @@ public MqttClientWrapper(string clientId, string mqttBroker, string mqttPort, st return isValid; // Return the result of your checks } - }); + }); } _mqttOptions = mqttClientOptionsBuilder.Build(); @@ -233,14 +233,17 @@ public static List GetEntityNames(string deviceId) return entityNames; } - public async Task PublishAsync(string topic, string payload, bool retain = true) + +public async Task PublishAsync(string topic, string payload, bool retain = true) { try { + // Log the topic, payload, and retain flag Log.Information($"Publishing to topic: {topic}"); Log.Information($"Payload: {payload}"); Log.Information($"Retain flag: {retain}"); + // Build the MQTT message var message = new MqttApplicationMessageBuilder() .WithTopic(topic) .WithPayload(payload) @@ -248,55 +251,58 @@ public async Task PublishAsync(string topic, string payload, bool retain = true) .WithRetainFlag(retain) .Build(); + // Publish the message using the MQTT client await _mqttClient.PublishAsync(message); Log.Information("Publish successful."); } catch (Exception ex) { + // Log any errors that occur during MQTT publish Log.Information($"Error during MQTT publish: {ex.Message}"); // Depending on the severity, you might want to rethrow the exception or handle it here. } } + - public async Task SubscribeAsync(string topic, MqttQualityOfServiceLevel qos) + public async Task SubscribeAsync(string topic, MqttQualityOfServiceLevel qos) + { + var subscribeOptions = new MqttClientSubscribeOptionsBuilder() + .WithTopicFilter(f => f.WithTopic(topic).WithQualityOfServiceLevel(qos)) + .Build(); + try { - var subscribeOptions = new MqttClientSubscribeOptionsBuilder() - .WithTopicFilter(f => f.WithTopic(topic).WithQualityOfServiceLevel(qos)) - .Build(); - try - { - await _mqttClient.SubscribeAsync(subscribeOptions); - } - catch (Exception ex) - { - Log.Information($"Error during MQTT subscribe: {ex.Message}"); - // Depending on the severity, you might want to rethrow the exception or handle it here. - } - Log.Information("Subscribing." + subscribeOptions); + await _mqttClient.SubscribeAsync(subscribeOptions); } - - #endregion Public Methods - - #region Private Methods - - private async Task HandleReceivedApplicationMessage(MqttApplicationMessageReceivedEventArgs e) + catch (Exception ex) { - if (MessageReceived != null) - { - await MessageReceived(e); - Log.Information($"Received message on topic {e.ApplicationMessage.Topic}: {e.ApplicationMessage.ConvertPayloadToString()}"); - } + Log.Information($"Error during MQTT subscribe: {ex.Message}"); + // Depending on the severity, you might want to rethrow the exception or handle it here. } + Log.Information("Subscribing." + subscribeOptions); + } - private Task OnMessageReceivedAsync(MqttApplicationMessageReceivedEventArgs e) + #endregion Public Methods + + #region Private Methods + + private async Task HandleReceivedApplicationMessage(MqttApplicationMessageReceivedEventArgs e) + { + if (MessageReceived != null) { + await MessageReceived(e); Log.Information($"Received message on topic {e.ApplicationMessage.Topic}: {e.ApplicationMessage.ConvertPayloadToString()}"); - // Trigger the event to notify subscribers - MessageReceived?.Invoke(e); - - return Task.CompletedTask; } + } - #endregion Private Methods + private Task OnMessageReceivedAsync(MqttApplicationMessageReceivedEventArgs e) + { + Log.Information($"Received message on topic {e.ApplicationMessage.Topic}: {e.ApplicationMessage.ConvertPayloadToString()}"); + // Trigger the event to notify subscribers + MessageReceived?.Invoke(e); + + return Task.CompletedTask; } + + #endregion Private Methods +} } \ No newline at end of file diff --git a/MainWindow.xaml.cs b/MainWindow.xaml.cs index e4e9992..42d0bc7 100644 --- a/MainWindow.xaml.cs +++ b/MainWindow.xaml.cs @@ -355,7 +355,7 @@ public async Task InitializeMQTTConnection() { Dispatcher.Invoke(() => MQTTConnectionStatus.Text = "MQTT Status: Connected"); Log.Debug("MQTT Client Connected in InitializeMQTTConnection"); - SetupMqttSensors(); + await SetupMqttSensors(); } return; // Exit the method if connected } @@ -476,7 +476,7 @@ private async void ReestablishConnections() { await mqttClientWrapper.ConnectAsync(); await mqttClientWrapper.SubscribeAsync("homeassistant/switch/+/set", MqttQualityOfServiceLevel.AtLeastOnce); - SetupMqttSensors(); + await SetupMqttSensors(); } if (!_teamsClient.IsConnected) { @@ -1028,16 +1028,20 @@ private async Task SaveSettingsAsync() await ReconnectToMqttServerAsync(); await PublishConfigurations(_latestMeetingUpdate, _settings); + await SetupMqttSensors(); + + } private async void SaveSettings_Click(object sender, RoutedEventArgs e) { Log.Debug("SaveSettings_Click: Save Settings Clicked" + _settings.ToString); - foreach(var setting in _settings.GetType().GetProperties()) - { - Log.Debug(setting.Name + " " + setting.GetValue(_settings)); - } + // uncomment below for testing ** insecure as tokens exposed in logs! ** + //foreach(var setting in _settings.GetType().GetProperties()) + //{ + // Log.Debug(setting.Name + " " + setting.GetValue(_settings)); + //} await SaveSettingsAsync(); } @@ -1046,7 +1050,7 @@ private async Task SetStartupAsync(bool startWithWindows) } - private async void SetupMqttSensors() + private async Task SetupMqttSensors() { // Create a dummy MeetingUpdate with default values var dummyMeetingUpdate = new MeetingUpdate diff --git a/TEAMS2HA.csproj b/TEAMS2HA.csproj index fa1061c..d31491c 100644 --- a/TEAMS2HA.csproj +++ b/TEAMS2HA.csproj @@ -5,8 +5,8 @@ net7.0-windows enable true - 1.1.0.311 - 1.1.0.311 + 1.1.0.317 + 1.1.0.317 Assets\Square150x150Logo.scale-200.ico Teams2HA Square150x150Logo.scale-200.png