@@ -30,6 +30,9 @@ public partial class MainWindow : Window
3030 private TextBlock ? _statusText ;
3131 private TextBlock ? _connectionLabel ;
3232 private Button ? _retryButton ;
33+ private TextBox ? _mqttFallbackBox ;
34+ private Button ? _btnMqttTest ;
35+ private TextBlock ? _mqttStatusLabel ;
3336
3437 // ── Color Palette ──────────────────────────────────────────
3538 private static readonly IBrush BgBrush = new SolidColorBrush ( Color . FromArgb ( 255 , 26 , 26 , 46 ) ) ;
@@ -152,6 +155,208 @@ public MainWindow(Config config, HaApiClient api)
152155 }
153156 } ) ;
154157
158+ // ── MQTT Settings Card ─────────────────────────────────
159+ var mqttStatusLabel = new TextBlock
160+ {
161+ Text = GetMqttStatusText ( config ) ,
162+ FontSize = 12 ,
163+ Foreground = GrayBrush ,
164+ TextWrapping = TextWrapping . Wrap ,
165+ Margin = new Thickness ( 0 , 4 , 0 , 0 )
166+ } ;
167+ _mqttStatusLabel = mqttStatusLabel ;
168+
169+ var mqttEnableCheck = new CheckBox
170+ {
171+ Content = "MQTT aktivieren" ,
172+ IsChecked = config . MqttEnabled ,
173+ Foreground = Brushes . White ,
174+ FontSize = 13
175+ } ;
176+
177+ var mqttBrokerBox = new TextBox
178+ {
179+ Text = config . MqttBroker ,
180+ Watermark = "homeassistant.local" ,
181+ FontSize = 12 ,
182+ Margin = new Thickness ( 0 , 2 )
183+ } ;
184+ var mqttPortBox = new TextBox
185+ {
186+ Text = config . MqttPort . ToString ( ) ,
187+ Watermark = "1883" ,
188+ FontSize = 12 ,
189+ Margin = new Thickness ( 0 , 2 )
190+ } ;
191+ var mqttUserBox = new TextBox
192+ {
193+ Text = config . MqttUsername ,
194+ Watermark = "Benutzername" ,
195+ FontSize = 12 ,
196+ Margin = new Thickness ( 0 , 2 )
197+ } ;
198+ var mqttPassBox = new TextBox
199+ {
200+ Text = config . MqttPassword ,
201+ Watermark = "Passwort" ,
202+ FontSize = 12 ,
203+ PasswordChar = '•' ,
204+ Margin = new Thickness ( 0 , 2 )
205+ } ;
206+ var mqttFallbackBox = new TextBox
207+ {
208+ Text = config . MqttBrokerFallback ,
209+ Watermark = "z.B. 192.168.1.100" ,
210+ FontSize = 12 ,
211+ Margin = new Thickness ( 0 , 2 ) ,
212+ [ ToolTip . TipProperty ] = "Alternative MQTT-Broker-Adresse (z.B. lokale IP), falls die Hauptadresse nicht erreichbar ist"
213+ } ;
214+ _mqttFallbackBox = mqttFallbackBox ;
215+
216+ var mqttSslCheck = new CheckBox
217+ {
218+ Content = "SSL/TLS verwenden" ,
219+ IsChecked = config . MqttUseSsl ,
220+ Foreground = Brushes . White ,
221+ FontSize = 13
222+ } ;
223+
224+ var mqttAutoBtn = new Button
225+ {
226+ Content = "🔧 Auto-Konfiguration" ,
227+ [ ToolTip . TipProperty ] = "MQTT-Broker automatisch über HA REST API konfigurieren" ,
228+ FontSize = 12 ,
229+ Background = AccentBrush ,
230+ Foreground = Brushes . White ,
231+ CornerRadius = new CornerRadius ( 6 ) ,
232+ Padding = new Thickness ( 12 , 6 )
233+ } ;
234+
235+ var mqttTestBtn = new Button
236+ {
237+ Content = "🔌 Verbindung testen" ,
238+ [ ToolTip . TipProperty ] = "MQTT-Verbindung mit den aktuellen Einstellungen testen" ,
239+ FontSize = 12 ,
240+ Background = AccentBrush ,
241+ Foreground = Brushes . White ,
242+ CornerRadius = new CornerRadius ( 6 ) ,
243+ Padding = new Thickness ( 12 , 6 )
244+ } ;
245+ _btnMqttTest = mqttTestBtn ;
246+
247+ var mqttSaveBtn = new Button
248+ {
249+ Content = "💾 Speichern" ,
250+ [ ToolTip . TipProperty ] = "MQTT-Einstellungen speichern" ,
251+ FontSize = 12 ,
252+ Background = AccentBrush ,
253+ Foreground = Brushes . White ,
254+ CornerRadius = new CornerRadius ( 6 ) ,
255+ Padding = new Thickness ( 12 , 6 )
256+ } ;
257+
258+ // ── Wire MQTT events ────────────────────────────────────
259+ mqttAutoBtn . Click += async ( s , e ) =>
260+ {
261+ mqttAutoBtn . IsEnabled = false ;
262+ mqttStatusLabel . Text = "⏳ Verbinde..." ;
263+ try
264+ {
265+ var fallbackHost = _mqttFallbackBox ? . Text ? . Trim ( ) ;
266+ fallbackHost = string . IsNullOrEmpty ( fallbackHost ) ? null : fallbackHost ;
267+ var result = await MqttSetupHelper . AutoConfigureAsync ( config . HaUrl , config . HaToken , fallbackHost ) ;
268+ if ( result . Success )
269+ {
270+ mqttEnableCheck . IsChecked = true ;
271+ mqttBrokerBox . Text = result . BrokerHost ?? "" ;
272+ mqttPortBox . Text = result . BrokerPort . ToString ( ) ;
273+ mqttUserBox . Text = result . Username ?? "" ;
274+ mqttPassBox . Text = result . Password ?? "" ;
275+ mqttSslCheck . IsChecked = result . UseSsl ;
276+ config . MqttEnabled = true ;
277+ config . MqttBroker = result . BrokerHost ?? "" ;
278+ config . MqttPort = result . BrokerPort ;
279+ config . MqttUsername = result . Username ?? "" ;
280+ config . MqttPassword = result . Password ?? "" ;
281+ config . MqttUseSsl = result . UseSsl ;
282+ config . MqttBrokerFallback = _mqttFallbackBox ? . Text ? . Trim ( ) ?? "" ;
283+ config . MqttAutoConfigured = true ;
284+ config . Save ( ) ;
285+ mqttStatusLabel . Text = $ "✓ MQTT konfiguriert ({ result . BrokerHost } :{ result . BrokerPort } )";
286+ }
287+ else if ( result . MosquittoNotInstalled )
288+ mqttStatusLabel . Text = "⚠️ Mosquitto nicht gefunden. Bitte in HA installieren." ;
289+ else
290+ mqttStatusLabel . Text = $ "⚠️ Fehler: { result . ErrorMessage ?? "Unbekannt" } ";
291+ }
292+ catch ( Exception ex ) { mqttStatusLabel . Text = $ "✗ Fehler: { ex . Message } "; }
293+ finally { mqttAutoBtn . IsEnabled = true ; }
294+ } ;
295+
296+ mqttTestBtn . Click += async ( s , e ) =>
297+ {
298+ mqttTestBtn . IsEnabled = false ;
299+ mqttStatusLabel . Text = "⏳ Teste MQTT-Verbindung..." ;
300+ try
301+ {
302+ var broker = mqttBrokerBox . Text ? . Trim ( ) ?? "" ;
303+ if ( string . IsNullOrEmpty ( broker ) )
304+ {
305+ mqttStatusLabel . Text = "⚠️ Bitte Broker-Adresse eingeben" ;
306+ mqttTestBtn . IsEnabled = true ;
307+ return ;
308+ }
309+ if ( ! int . TryParse ( mqttPortBox . Text ? . Trim ( ) , out var port ) || port <= 0 )
310+ port = 1883 ;
311+ var user = string . IsNullOrEmpty ( mqttUserBox . Text ? . Trim ( ) ) ? null : mqttUserBox . Text . Trim ( ) ;
312+ var pass = string . IsNullOrEmpty ( mqttPassBox . Text ) ? null : mqttPassBox . Text ;
313+ var ssl = mqttSslCheck . IsChecked ?? false ;
314+ var ok = await MqttSetupHelper . TestConnectionAsync ( broker , port , user , pass , ssl ) ;
315+ if ( ok )
316+ mqttStatusLabel . Text = $ "✓ MQTT-Verbindung erfolgreich ({ broker } :{ port } )";
317+ else
318+ mqttStatusLabel . Text = $ "✗ Verbindung zu { broker } :{ port } fehlgeschlagen";
319+ }
320+ catch ( Exception ex ) { mqttStatusLabel . Text = $ "✗ Fehler: { ex . Message } "; }
321+ finally { mqttTestBtn . IsEnabled = true ; }
322+ } ;
323+
324+ mqttSaveBtn . Click += ( s , e ) =>
325+ {
326+ config . MqttEnabled = mqttEnableCheck . IsChecked ?? false ;
327+ config . MqttBroker = mqttBrokerBox . Text ? . Trim ( ) ?? "" ;
328+ if ( int . TryParse ( mqttPortBox . Text ? . Trim ( ) , out var p ) )
329+ config . MqttPort = p ;
330+ config . MqttUsername = mqttUserBox . Text ? . Trim ( ) ?? "" ;
331+ config . MqttPassword = mqttPassBox . Text ?? "" ;
332+ config . MqttUseSsl = mqttSslCheck . IsChecked ?? false ;
333+ config . MqttBrokerFallback = _mqttFallbackBox ? . Text ? . Trim ( ) ?? "" ;
334+ config . MqttAutoConfigured = false ;
335+ config . Save ( ) ;
336+ mqttStatusLabel . Text = "✓ MQTT-Einstellungen gespeichert" ;
337+ } ;
338+
339+ root . Children . Add ( new Border
340+ {
341+ Background = PanelBrush ,
342+ CornerRadius = new CornerRadius ( 8 ) ,
343+ Padding = new Thickness ( 16 , 12 ) ,
344+ Margin = new Thickness ( 16 , 12 , 16 , 0 ) ,
345+ Child = new StackPanel
346+ {
347+ Spacing = 6 ,
348+ Children =
349+ {
350+ new TextBlock { Text = "📡 MQTT-Einstellungen" , FontWeight = FontWeight . SemiBold , FontSize = 14 , Foreground = Brushes . White } ,
351+ mqttEnableCheck ,
352+ BuildMqttGrid ( mqttBrokerBox , mqttPortBox , mqttUserBox , mqttPassBox , mqttFallbackBox ) ,
353+ mqttSslCheck ,
354+ new StackPanel { Orientation = Orientation . Horizontal , Spacing = 8 , Children = { mqttAutoBtn , mqttTestBtn , mqttSaveBtn } } ,
355+ mqttStatusLabel
356+ }
357+ }
358+ } ) ;
359+
155360 // ── Footer ──────────────────────────────────────────────
156361 root . Children . Add ( new StackPanel
157362 {
@@ -401,6 +606,51 @@ private static List<QuickActionItem> LoadQuickActions(Config config)
401606 catch { }
402607 return result ;
403608 }
609+
610+ private static string GetMqttStatusText ( Config config )
611+ {
612+ if ( ! config . MqttEnabled )
613+ return "○ Deaktiviert" ;
614+ if ( ! string . IsNullOrEmpty ( config . MqttBroker ) )
615+ return $ "● Verbunden ({ config . MqttBroker } :{ config . MqttPort } )";
616+ return "● Getrennt" ;
617+ }
618+
619+ private static Grid BuildMqttGrid ( TextBox brokerBox , TextBox portBox , TextBox userBox , TextBox passBox , TextBox fallbackBox )
620+ {
621+ var grid = new Grid
622+ {
623+ ColumnDefinitions = ColumnDefinitions . Parse ( "120,*" ) ,
624+ RowDefinitions = RowDefinitions . Parse ( "Auto,Auto,Auto,Auto,Auto" )
625+ } ;
626+
627+ grid . Children . Add ( new TextBlock { Text = "Broker:" , [ Grid . RowProperty ] = 0 , [ Grid . ColumnProperty ] = 0 , VerticalAlignment = VerticalAlignment . Center , Foreground = GrayBrush , FontSize = 12 } ) ;
628+ grid . Children . Add ( brokerBox ) ;
629+ Grid . SetColumn ( brokerBox , 1 ) ; Grid . SetRow ( brokerBox , 0 ) ;
630+ brokerBox . Margin = new Thickness ( 4 , 2 ) ;
631+
632+ grid . Children . Add ( new TextBlock { Text = "Port:" , [ Grid . RowProperty ] = 1 , [ Grid . ColumnProperty ] = 0 , VerticalAlignment = VerticalAlignment . Center , Foreground = GrayBrush , FontSize = 12 } ) ;
633+ grid . Children . Add ( portBox ) ;
634+ Grid . SetColumn ( portBox , 1 ) ; Grid . SetRow ( portBox , 1 ) ;
635+ portBox . Margin = new Thickness ( 4 , 2 ) ;
636+
637+ grid . Children . Add ( new TextBlock { Text = "Benutzername:" , [ Grid . RowProperty ] = 2 , [ Grid . ColumnProperty ] = 0 , VerticalAlignment = VerticalAlignment . Center , Foreground = GrayBrush , FontSize = 12 } ) ;
638+ grid . Children . Add ( userBox ) ;
639+ Grid . SetColumn ( userBox , 1 ) ; Grid . SetRow ( userBox , 2 ) ;
640+ userBox . Margin = new Thickness ( 4 , 2 ) ;
641+
642+ grid . Children . Add ( new TextBlock { Text = "Passwort:" , [ Grid . RowProperty ] = 3 , [ Grid . ColumnProperty ] = 0 , VerticalAlignment = VerticalAlignment . Center , Foreground = GrayBrush , FontSize = 12 } ) ;
643+ grid . Children . Add ( passBox ) ;
644+ Grid . SetColumn ( passBox , 1 ) ; Grid . SetRow ( passBox , 3 ) ;
645+ passBox . Margin = new Thickness ( 4 , 2 ) ;
646+
647+ grid . Children . Add ( new TextBlock { Text = "Fallback-Adresse:" , [ Grid . RowProperty ] = 4 , [ Grid . ColumnProperty ] = 0 , VerticalAlignment = VerticalAlignment . Center , Foreground = GrayBrush , FontSize = 12 } ) ;
648+ grid . Children . Add ( fallbackBox ) ;
649+ Grid . SetColumn ( fallbackBox , 1 ) ; Grid . SetRow ( fallbackBox , 4 ) ;
650+ fallbackBox . Margin = new Thickness ( 4 , 2 ) ;
651+
652+ return grid ;
653+ }
404654}
405655
406656public class QuickActionItem
0 commit comments