Skip to content

Commit 933e932

Browse files
author
J.A.R.V.I.S.
committed
feat: v4.3.0 - MQTT support, Media Player, PC Status sensor
- Add MQTT support with zero-config auto-setup from HA - Add Media Player entity (now playing, play/pause/volume control) - Add PC Status binary sensor (instant on/off detection) - Add MQTT setup wizard on first launch - Add MQTT settings in main settings - Smart routing: MQTT for sensors/commands, WebSocket for notifications - LWT (Last Will) for instant offline detection - MQTT auto-reconnect with exponential backoff - MQTT Auto-Discovery for all entities - Platform-specific media detection (macOS AppleScript/nowplaying-cli) - MqttSetupHelper auto-configures from HA API - 27 new localization keys across 6 languages - Updated CHANGELOG, README, README_EN
1 parent 8fe16b5 commit 933e932

20 files changed

Lines changed: 1737 additions & 16 deletions

CHANGELOG.md

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,20 @@
11
# Changelog
22

3+
## [v4.3.0] - 2026-05-24
4+
5+
### MQTT Support (optional)
6+
- MQTT support (optional, auto-configure from Home Assistant)
7+
- Media Player entity (now playing info, play/pause/volume controls)
8+
- PC Status binary sensor (instant online/offline detection)
9+
- Zero-config MQTT setup wizard on first launch
10+
- MQTT settings in main settings
11+
- Smart routing: MQTT for sensors + commands, WebSocket for notifications
12+
- Last Will Testament (instant offline detection)
13+
- Auto-reconnect with exponential backoff
14+
15+
### 🌍 Neue Lokalisierungs-Keys
16+
- 27 neue MQTT/MediaPlayer/PCStatus-Keys in allen 6 Sprachen
17+
318
## [v4.2.0] - 2026-05-23
419

520
### 📊 Neue Sensoren

README.md

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
# HA DeskLink macOS v4.2
1+
# HA DeskLink macOS v4.3
22

33
[![Build](https://img.shields.io/github/actions/workflow/status/TechFlipsi/ha-desklink-mac/build.yml?branch=main&label=Build)](https://github.com/TechFlipsi/ha-desklink-mac/actions)
44
[![Version](https://img.shields.io/github/v/release/TechFlipsi/ha-desklink-mac?label=Version)](https://github.com/TechFlipsi/ha-desklink-mac/releases/latest)
@@ -31,6 +31,20 @@ Wenn du macOS nutzt, teste bitte diese Version und melde Bugs – **die Communit
3131

3232
---
3333

34+
## MQTT (v4.3)
35+
36+
HA DeskLink v4.3 bringt **optionale MQTT-Unterstützung** für erweiterte Features:
37+
38+
- 🔊 **Media Player Entity** – Dein Mac erscheint als Media Player in Home Assistant mit now-playing Info, Play/Pause und Lautstärke-Regelung
39+
- 📡 **PC Status Binary Sensor** – Sofortige Online/Offline-Erkennung via Last Will Testament (LWT)
40+
-**Befehle an schlafenden Mac** – MQTT-Befehle erreichen den Mac auch im Energiesparmodus
41+
- 🔍 **Automatische Geräteerkennung** – Media Player und PC Status erscheinen automatisch in HA
42+
- 🔒 **Zuverlässigere Verbindung** – Auto-Reconnect mit exponentiellem Backoff
43+
- 🪄 **Zero-Config Setup** – Beim ersten Start wird automatisch nach Mosquitto gesucht und die Verbindung eingerichtet
44+
- 🧭 **Smart Routing** – MQTT für Sensoren + Befehle, WebSocket bleibt für Benachrichtigungen
45+
46+
MQTT ist **optional** – HA DeskLink funktioniert auch ohne MQTT wie gewohnt weiter.
47+
3448
## v4.2 New Features
3549

3650
- 🖥️ **Dashboard** – Öffnet HA-Dashboard im Standard-Browser (einmaliges Login, Session bleibt erhalten)

README_EN.md

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
# HA DeskLink macOS v4.2
1+
# HA DeskLink macOS v4.3
22

33
[![Build](https://img.shields.io/github/actions/workflow/status/TechFlipsi/ha-desklink-mac/build.yml?branch=main&label=Build)](https://github.com/TechFlipsi/ha-desklink-mac/actions)
44
[![Version](https://img.shields.io/github/v/release/TechFlipsi/ha-desklink-mac?label=Version)](https://github.com/TechFlipsi/ha-desklink-mac/releases/latest)
@@ -34,6 +34,20 @@ Please report bugs at [Issues](https://github.com/TechFlipsi/ha-desklink-mac/iss
3434
- 🔄 **Auto-Update** – checks for updates on startup
3535
- 🍎 **macOS-exclusive Sensors** – Battery cycle count, Power adapter, Keyboard backlight, GPU model, Display resolution
3636

37+
## MQTT (v4.3)
38+
39+
HA DeskLink v4.3 brings **optional MQTT support** for advanced features:
40+
41+
- 🔊 **Media Player Entity** – Your Mac appears as a Media Player in Home Assistant with now-playing info, play/pause and volume control
42+
- 📡 **PC Status Binary Sensor** – Instant online/offline detection via Last Will Testament (LWT)
43+
-**Commands to Sleeping Mac** – MQTT commands reach the Mac even in sleep mode
44+
- 🔍 **Automatic Device Discovery** – Media Player and PC Status appear automatically in HA
45+
- 🔒 **More Reliable Connection** – Auto-reconnect with exponential backoff
46+
- 🪄 **Zero-Config Setup** – On first launch, automatically detects Mosquitto and configures the connection
47+
- 🧭 **Smart Routing** – MQTT for sensors + commands, WebSocket stays for notifications
48+
49+
MQTT is **optional** – HA DeskLink works without MQTT as usual.
50+
3751
## Sensors
3852

3953
| Sensor | Description | macOS Exclusive |

VERSION

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
4.1.0
1+
4.3.0

src/HaDeskLink/Config.cs

Lines changed: 40 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,16 @@ public class Config
4141
/// </summary>
4242
public string? HaTokenEncrypted { get; set; }
4343

44+
// MQTT Configuration (optional, auto-configured)
45+
public bool MqttEnabled { get; set; } = false;
46+
public string MqttBroker { get; set; } = "";
47+
public int MqttPort { get; set; } = 1883;
48+
public string MqttUsername { get; set; } = "";
49+
public string MqttPassword { get; set; } = ""; // runtime only, never saved to config file
50+
public string MqttPasswordEncrypted { get; set; } = ""; // encrypted version for persistence
51+
public bool MqttUseSsl { get; set; } = false;
52+
public bool MqttAutoConfigured { get; set; } = false; // set by auto-setup
53+
4454
private string ConfigPath => Path.Combine(ConfigDir, "config.json");
4555

4656
/// <summary>
@@ -223,6 +233,22 @@ public static Config Load()
223233
}
224234
}
225235

236+
// Migration: if MqttPasswordEncrypted is empty but MqttPassword has a value,
237+
// encrypt MqttPassword and clear the plaintext
238+
if (string.IsNullOrEmpty(config.MqttPasswordEncrypted) && !string.IsNullOrEmpty(config.MqttPassword))
239+
{
240+
config.MqttPasswordEncrypted = EncryptAesGcm(config.MqttPassword);
241+
config.MqttPassword = ""; // Clear plaintext
242+
config.Save(); // Save encrypted version immediately
243+
}
244+
else if (!string.IsNullOrEmpty(config.MqttPasswordEncrypted))
245+
{
246+
// Decrypt the MQTT password for use in the app
247+
var decrypted = DecryptAesGcm(config.MqttPasswordEncrypted);
248+
if (!string.IsNullOrEmpty(decrypted))
249+
config.MqttPassword = decrypted;
250+
}
251+
226252
return config;
227253
}
228254

@@ -242,6 +268,11 @@ public void Save()
242268
}
243269
}
244270

271+
if (!string.IsNullOrEmpty(MqttPassword))
272+
{
273+
MqttPasswordEncrypted = EncryptAesGcm(MqttPassword);
274+
}
275+
245276
var saveConfig = new Config
246277
{
247278
HaUrl = HaUrl,
@@ -251,7 +282,15 @@ public void Save()
251282
UpdateChannel = UpdateChannel,
252283
Language = Language,
253284
QuickActions = QuickActions,
254-
HaTokenEncrypted = HaTokenEncrypted
285+
HaTokenEncrypted = HaTokenEncrypted,
286+
MqttEnabled = MqttEnabled,
287+
MqttBroker = MqttBroker,
288+
MqttPort = MqttPort,
289+
MqttUsername = MqttUsername,
290+
MqttPassword = "", // NEVER save plaintext password
291+
MqttPasswordEncrypted = MqttPasswordEncrypted,
292+
MqttUseSsl = MqttUseSsl,
293+
MqttAutoConfigured = MqttAutoConfigured
255294
};
256295

257296
var json = JsonSerializer.Serialize(saveConfig, new JsonSerializerOptions { WriteIndented = true });

src/HaDeskLink/DeskLinkApp.cs

Lines changed: 124 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,9 @@
1515
using Avalonia.Markup.Xaml;
1616
using HaDeskLink.Views;
1717
using System;
18+
using System.Collections.Generic;
1819
using System.IO;
20+
using System.Text.Json;
1921
using System.Threading.Tasks;
2022

2123
namespace 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
}

src/HaDeskLink/HaApiClient.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -317,6 +317,6 @@ private static string GetVersion()
317317
if (File.Exists(vfile)) return File.ReadAllText(vfile).Trim();
318318
}
319319
catch { }
320-
return "4.1.0";
320+
return "4.3.0";
321321
}
322322
}

src/HaDeskLink/HaDeskLink.csproj

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
<TargetFramework>net8.0</TargetFramework>
66
<AssemblyName>HA_DeskLink</AssemblyName>
77
<RootNamespace>HaDeskLink</RootNamespace>
8-
<Version>4.1.0</Version>
8+
<Version>4.3.0</Version>
99
<Nullable>enable</Nullable>
1010
<RuntimeIdentifier>osx-arm64</RuntimeIdentifier>
1111
<DefineConstants>MACOS</DefineConstants>
@@ -17,6 +17,8 @@
1717
<PackageReference Include="Avalonia.Themes.Fluent" Version="11.2.3" />
1818
<PackageReference Include="Avalonia.Fonts.Inter" Version="11.2.3" />
1919
<PackageReference Include="System.Security.Cryptography.ProtectedData" Version="8.0.0" />
20+
<PackageReference Include="MQTTnet" Version="4.3.7.1207" />
21+
<PackageReference Include="MQTTnet.Extensions.ManagedClient" Version="4.3.7.1207" />
2022
</ItemGroup>
2123

2224
<ItemGroup>

src/HaDeskLink/Lang/de.json

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,5 +24,32 @@
2424
"command_volume_mute": "Stummschalten",
2525
"command_media_play_pause": "Wiedergabe/Pause",
2626
"command_media_next": "Nächster Titel",
27-
"command_media_previous": "Vorheriger Titel"
27+
"command_media_previous": "Vorheriger Titel",
28+
"mqtt_settings": "MQTT-Einstellungen",
29+
"mqtt_enabled": "MQTT aktivieren",
30+
"mqtt_broker": "Broker",
31+
"mqtt_port": "Port",
32+
"mqtt_username": "Benutzername",
33+
"mqtt_password": "Passwort",
34+
"mqtt_use_ssl": "SSL/TLS verwenden",
35+
"mqtt_auto_configure": "Automatisch konfigurieren",
36+
"mqtt_connecting": "Verbinde…",
37+
"mqtt_connected": "Verbunden",
38+
"mqtt_disconnected": "Getrennt",
39+
"mqtt_disabled": "Deaktiviert",
40+
"mqtt_setup_title": "MQTT-Einrichtung",
41+
"mqtt_setup_description": "Erweiterte Funktionen aktivieren:",
42+
"mqtt_feature_media_player": "Media Player — PC als Abspielgerät",
43+
"mqtt_feature_pc_status": "PC-Status — sofort offline erkannt",
44+
"mqtt_feature_commands": "Befehle an schlafenden PC",
45+
"mqtt_feature_discovery": "Automatische Geräterkennung",
46+
"mqtt_feature_reliable": "Zuverlässigere Verbindung",
47+
"mqtt_no_mosquitto": "Mosquitto nicht gefunden. Bitte in HA installieren (Einstellungen → Add-ons → Mosquitto Broker).",
48+
"mqtt_use_mqtt": "MQTT nutzen",
49+
"mqtt_skip": "Ohne MQTT fortfahren",
50+
"mqtt_retry": "Erneut prüfen",
51+
"mqtt_configured": "MQTT konfiguriert",
52+
"mqtt_error": "Fehler",
53+
"sensor_pc_status": "PC Status",
54+
"media_player": "Media Player"
2855
}

0 commit comments

Comments
 (0)