1515using Avalonia . Markup . Xaml ;
1616using HaDeskLink . Views ;
1717using System ;
18+ using System . Collections . Generic ;
1819using System . IO ;
20+ using System . Text . Json ;
1921using System . Threading . Tasks ;
2022
2123namespace HaDeskLink ;
@@ -24,8 +26,10 @@ public class DeskLinkApp : Application
2426{
2527 private HaWebSocketClient ? _ws ;
2628 private System . Threading . Timer ? _sensorTimer ;
29+ private System . Threading . Timer ? _mediaTimer ;
2730 public static DeskLinkApp ? Instance { get ; private set ; }
2831 private HaApiClient ? _api ;
32+ private MqttClient ? _mqttClient ;
2933
3034 public HaApiClient GetApiClient ( ) => _api ?? throw new InvalidOperationException ( "API not initialized" ) ;
3135
@@ -48,17 +52,103 @@ public override void OnFrameworkInitializationCompleted()
4852 // Start WebSocket
4953 StartWebSocket ( config , api ) ;
5054
55+ // Start MQTT if enabled
56+ if ( config . MqttEnabled && ! string . IsNullOrEmpty ( config . MqttBroker ) )
57+ {
58+ _ = StartMqttAsync ( config ) ;
59+ }
60+
5161 // Start sensor timer
5262 _sensorTimer = new System . Threading . Timer ( async _ =>
5363 {
54- try { await UpdateSensors ( api ) ; }
64+ try { await UpdateSensors ( api , config ) ; }
5565 catch ( Exception ex ) { System . Console . WriteLine ( $ "[SensorTimer] Error: { ex . Message } ") ; }
5666 } , null ,
5767 System . TimeSpan . Zero , System . TimeSpan . FromSeconds ( config . SensorInterval ) ) ;
68+
69+ // Start media state timer (MQTT only)
70+ _mediaTimer = new System . Threading . Timer ( async _ =>
71+ {
72+ try { await UpdateMediaState ( ) ; }
73+ catch { }
74+ } , null ,
75+ System . TimeSpan . FromSeconds ( 2 ) , System . TimeSpan . FromSeconds ( 5 ) ) ;
76+
77+ // Handle graceful shutdown — send pc_status = "off" + disconnect MQTT
78+ if ( ApplicationLifetime is IClassicDesktopStyleApplicationLifetime lifetime )
79+ {
80+ lifetime . ShutdownRequested += async ( s , e ) =>
81+ {
82+ try
83+ {
84+ if ( _api != null )
85+ {
86+ var pcOff = new SensorData ( "pc_status" , "PC Status" , "off" , "" , "connectivity" , "mdi:desktop-classic" )
87+ {
88+ SensorKind = SensorType . BinarySensor ,
89+ EntityCategory = null
90+ } ;
91+ await _api . UpdateSensorStatesAsync ( new List < SensorData > { pcOff } ) ;
92+ }
93+ }
94+ catch { }
95+
96+ // Disconnect MQTT gracefully
97+ if ( _mqttClient != null )
98+ {
99+ try { _mqttClient . Dispose ( ) ; }
100+ catch { }
101+ }
102+ } ;
103+ }
58104 }
59105 base . OnFrameworkInitializationCompleted ( ) ;
60106 }
61107
108+ private async Task StartMqttAsync ( Config config )
109+ {
110+ try
111+ {
112+ var regPath = Path . Combine ( Config . GetConfigDir ( ) , "registration.json" ) ;
113+ string deviceId = "" ;
114+ if ( File . Exists ( regPath ) )
115+ {
116+ var reg = JsonDocument . Parse ( File . ReadAllText ( regPath ) ) ;
117+ deviceId = reg . RootElement . GetProperty ( "webhook_id" ) . GetString ( ) ?? "" ;
118+ }
119+
120+ var configDir = Config . GetConfigDir ( ) ;
121+ var version = System . Reflection . Assembly . GetExecutingAssembly ( ) . GetName ( ) . Version ? . ToString ( ) ?? "4.3.0" ;
122+
123+ _mqttClient = new MqttClient (
124+ config . MqttBroker ,
125+ config . MqttPort ,
126+ config . MqttUsername ,
127+ config . MqttPassword ,
128+ config . MqttUseSsl ,
129+ configDir ,
130+ version ,
131+ cmd => { try { _ = CommandHandler . ExecuteAsync ( cmd ) ; } catch { } }
132+ ) ;
133+
134+ await _mqttClient . ConnectAsync ( ) ;
135+
136+ // Publish discovery for all sensors + media player
137+ var sensors = SensorManager . GetAllSensors ( ) ;
138+ await _mqttClient . PublishDiscoveryAsync ( sensors ) ;
139+
140+ // Publish initial states
141+ await _mqttClient . PublishSensorStatesAsync ( sensors ) ;
142+
143+ System . Console . WriteLine ( "[MQTT] Connected and discovery published" ) ;
144+ }
145+ catch ( Exception ex )
146+ {
147+ System . Console . WriteLine ( $ "[MQTT] Failed to connect: { ex . Message } ") ;
148+ _mqttClient = null ;
149+ }
150+ }
151+
62152 private void StartWebSocket ( Config config , HaApiClient api )
63153 {
64154 if ( string . IsNullOrEmpty ( config . HaToken ) )
@@ -95,12 +185,44 @@ private void StartWebSocket(Config config, HaApiClient api)
95185 catch ( System . Exception ex ) { System . Console . WriteLine ( $ "WebSocket-Start fehlgeschlagen: { ex . Message } ") ; }
96186 }
97187
98- private async Task UpdateSensors ( HaApiClient api )
188+ private async Task UpdateSensors ( HaApiClient api , Config config )
99189 {
100190 try
101191 {
102192 var sensors = SensorManager . GetAllSensors ( ) ;
193+
194+ // Always send via WebSocket (mobile_app webhook) for redundancy
103195 await api . UpdateSensorStatesAsync ( sensors ) ;
196+
197+ // Also publish via MQTT if connected
198+ if ( _mqttClient != null && _mqttClient . IsConnected )
199+ {
200+ await _mqttClient . PublishSensorStatesAsync ( sensors ) ;
201+ }
202+ }
203+ catch { }
204+ }
205+
206+ private async Task UpdateMediaState ( )
207+ {
208+ if ( _mqttClient == null || ! _mqttClient . IsConnected )
209+ return ;
210+
211+ try
212+ {
213+ var mediaState = MediaPlayer . GetCurrentMediaState ( ) ;
214+ await _mqttClient . PublishMediaStateAsync (
215+ mediaState . State ,
216+ System . Text . Json . JsonSerializer . Serialize ( new
217+ {
218+ mediaState . Title ,
219+ mediaState . Artist ,
220+ mediaState . Album ,
221+ mediaState . Source ,
222+ mediaState . Volume ,
223+ mediaState . Muted
224+ } )
225+ ) ;
104226 }
105227 catch { }
106228 }
0 commit comments