The MQTT Manager now supports remote control of EliteG19s via MQTT messages, implementing all methods from the IRemoteControl interface.
The MQTT command handler uses a direct dispatch pattern with a dictionary mapping command names to delegates, providing type-safe, high-performance command execution without reflection.
Commands are sent to topics following this pattern:
eliteg19s/journal/{commander_name}/command/{CommandName}
Where:
{commander_name} is the sanitized commander name (lowercase, special characters replaced with underscores){CommandName} is the exact name of the method from IRemoteControl interface (case-insensitive)Simple command (no arguments):
Topic: eliteg19s/journal/cmdr_example/command/VolumeUp
Payload: (empty)
Command with a named property (preferred):
Topic: eliteg19s/journal/cmdr_example/command/ChangeVolume
Payload: {"delta": 10}
Play radio station (named property form):
Topic: eliteg19s/journal/cmdr_example/command/ListenToRadiostation
Payload: {"station": "BBC Radio 1"}
Note: Do not rely on positional args arrays; use named properties as shown above.
String command (plain text form — normalized to { "value": "..." }):
Topic: eliteg19s/journal/cmdr_example/command/ShowMessage
Payload: Hello from MQTT!
Command execution results are published to:
eliteg19s/journal/{commander_name}/command/{CommandName}/result
Behavior summary:
GetStatus or GetWhatsPlaying), that object is serialized to JSON and published to the result topic.{ "status": "OK" }.error property, e.g. { "error": "message" }.This lets callers perform request/response style interactions over MQTT: publish a command, then listen on the matching {CommandName}/result topic for the JSON result or an error object.
Example result payloads:
json { "status": "OK" }
json { "system": "Elite Dangerous", "commander": "CmdrExample", "location": "Sol" }
json { "error": "Unknown command: FooBar" }
VolumeUp - Increase volumeVolumeDown - Decrease volumeChangeVolume - Change volume by delta (-100 to 100)
{ "delta": <deltaPercent> }{ "deltaPercent": 10 }Mute - Toggle muteStopMusic - Stop any playing musicPauseMusic - Pause current musicGoToNextTrack - Next trackGoToPreviousTrack - Previous trackShuffleMusic - Shuffle musicMusicSkipForward - Skip forward 30 secondsMusicSkipBack - Skip back 30 secondsListenToRadiostation - Play radio station
{ "station": "stationName" }{ "station": "BBC Radio 1" }ListenToSpotifyPlaylist - Play Spotify playlist
{ "playlist": "playlistName" }{ "playlist": "Chill Hits" }ListenToPodcast - Play podcast
{ "podcast": "podcastName" }{ "podcast": "Daily Tech News" }ListenToMusicFolder - Play from folder
{ "folder": "folderPath" }{ "path": "C:/Music/Albums" }StreamMusicUrl - Stream from URL
{ "url": "https://..." }{ "url": "https://example.com/stream.mp3" }ListenToPlaylist - Play M3U playlist
{ "path": "path/to/playlist.m3u" }{ "path": "C:/Playlists/mylist.m3u" }SwitchScreen - Switch to named screen
{ "screen": "screenName" }{ "screen": "NowPlaying" }ShowAudioVisualization - Show audio visualization
{ "visualization": "visualizationName" }{ "visualization": "Spectrum" }ButtonUp, ButtonDown, ButtonLeft, ButtonRightButtonMenu, ButtonOK, ButtonCancelButton - Simulate any button
{ "button": "buttonName" }{ "button": "PlayPause" }ListenToSpaceTrafficControl - Enable/disable STC
{ "enabled": true|false }{ "enabled": true }SpaceTrafficVolumeUp - Increase STC volumeSpaceTrafficVolumeDown - Decrease STC volumeChangeSpaceTrafficVolume - Change STC volume
{ "delta": <deltaPercent> }{ "deltaPercent": -5 }NPCVolumeUp - Increase NPC TTS volumeNPCVolumeDown - Decrease NPC TTS volumeChangeNPCVolume - Change NPC volume
{ "delta": <deltaPercent> }{ "deltaPercent": 3 }MarkPosition - Mark current positionNextWaypoint - Select next waypointClearShoppingList - Clear shopping listAddToShoppingList - Add commodity to shopping list
{ "commodity": "commodityName", "amount": <int> }{ "commodity": "Gold", "amount": 10 }ReadShoppingList - Read shopping list aloudOrreryZoomIn - Zoom in orrery viewOrreryZoomOut - Zoom out orrery viewOrreryFaster - Speed up timeOrrerySlower - Slow down timeOrreryResetView - Reset orrery viewFindCommodity - Find commodity to buy/sell
{ "commodity": "commodityName", "isBuying": true|false }{ "commodity": "Gold", "sell": true }SelectInteractiveOption - Select option from interactive message
{ "option": "optionText" }{ "option": "Accept" }GetWhatsPlaying - Get currently playing audio metadata
GetStatus - Get current game status
ShowMessage - Display message on screen
{ "message": "text to display" }{ "text": "Hello Commander" }Speak - Ship AI speaks message
{ "message": "text to speak", "useCache": true|false }{ "text": "Hello Commander", "interrupt": true }Log - Write info log
{ "message": "log text" }{ "text": "A debug message" }LogError - Write error log
{ "message": "error text" }{ "text": "An error occurred" }GetShipAIMessage - Get localized Ship AI message
{ "key": "message.key", "args": [ ... ] } (the args member here is an explicit named property used as structured parameters for the message key; this is NOT a positional fallback)import paho.mqtt.client as mqtt
import json
# Connect to MQTT broker
client = mqtt.Client()
client.connect("localhost", 1883, 60)
# Simple command
client.publish("eliteg19s/journal/cmdr_example/command/VolumeUp", "")
# Command with named-property arguments (preferred)
payload = json.dumps({"delta": 15})
client.publish("eliteg19s/journal/cmdr_example/command/ChangeVolume", payload)
# String command (plain text form is normalized to { "value": "..." })
client.publish("eliteg19s/journal/cmdr_example/command/ShowMessage", "Hello Commander!")
# Play radio station (named property preferred)
payload = json.dumps({"station": "BBC Radio 1"})
client.publish("eliteg19s/journal/cmdr_example/command/ListenToRadiostation", payload)
# Listen for result (optional) - subscribe to the result topic before publishing
def on_message(client, userdata, msg):
print("Result topic:", msg.topic, msg.payload.decode())
client.subscribe("eliteg19s/journal/cmdr_example/command/ChangeVolume/result")
client.on_message = on_message
client.disconnect()
# Simple command
mosquitto_pub -t "eliteg19s/journal/cmdr_example/command/VolumeUp" -m ""
# Command with named-property arguments (preferred)
mosquitto_pub -t "eliteg19s/journal/cmdr_example/command/ChangeVolume" -m '{"delta": 10}'
# String message
mosquitto_pub -t "eliteg19s/journal/cmdr_example/command/ShowMessage" -m "Test message"
# Switch screen (named property)
mosquitto_pub -t "eliteg19s/journal/cmdr_example/command/SwitchScreen" -m '{"screen": "Menu"}'
# Optionally listen for the result in another terminal:
mosquitto_sub -t "eliteg19s/journal/cmdr_example/command/ChangeVolume/result"
Enable verbose logging in EliteG19s to see:
MQTT command received on topic: ...Executing command: ... with X argumentsCommand executed successfully: ...Error handling remote command: ...Home Assistant does not currently create native timer entities from MQTT discovery payloads. Instead, publish the EliteG19s alarm attributes via MQTT (already handled by the application) and let Home Assistant manage its own helpers.
timer.eliteg19s_next_alarm.input_text helpers to surface alarm metadata:input_text.eliteg19s_alarm_typeinput_text.eliteg19s_alarm_nameinput_text.eliteg19s_alarm_destinationalias: EliteG19s – Sync Next Alarm Timer
mode: restart
trigger:
- platform: mqtt
topic: eliteg19s/journal/magicmau/alarms/next/attributes
variables:
payload: "{{ trigger.payload_json | default({}) }}"
remaining: "{{ payload.remaining_seconds | int(0) }}"
has_alarm: "{{ payload.alarm_id is defined and payload.alarm_id }}"
action:
- choose:
- conditions: "{{ has_alarm and remaining > 0 }}"
sequence:
- service: timer.start
target:
entity_id: timer.eliteg19s_next_alarm
data:
duration:
seconds: "{{ remaining }}"
- service: input_text.set_value
target:
entity_id: input_text.eliteg19s_alarm_type
data:
value: "{{ payload.type | default('Unknown') }}"
- service: input_text.set_value
target:
entity_id: input_text.eliteg19s_alarm_name
data:
value: "{{ payload.name | default('') }}"
- service: input_text.set_value
target:
entity_id: input_text.eliteg19s_alarm_destination
data:
value: "{{ payload.destination | default('') }}"
default:
- service: timer.cancel
target:
entity_id: timer.eliteg19s_next_alarm
- service: timer.finish
target:
entity_id: timer.eliteg19s_next_alarm
- service: input_text.set_value
target:
entity_id:
- input_text.eliteg19s_alarm_type
- input_text.eliteg19s_alarm_name
- input_text.eliteg19s_alarm_destination
data:
value: ""
The flow below listens to each per-alarm MQTT attributes topic (eliteg19s/journal/<commander>/alarms/<alarm_id>/attributes) and starts or cancels a matching Home Assistant timer helper. Create timer helpers following the pattern timer.elite_alarm_<alarm_id> (the <alarm_id> segment is taken directly from the MQTT topic) before enabling the flow. Update the MQTT topic, broker, and Home Assistant server details after importing.
[
{
"id": "d6f6c1d7d3b40a9f",
"type": "tab",
"label": "EliteG19s Alarms → HA Timers",
"disabled": false,
"info": "Synchronise per-alarm MQTT attributes with Home Assistant timer helpers."
},
{
"id": "1bc1c2f0d3d8c9b1",
"type": "mqtt in",
"z": "d6f6c1d7d3b40a9f",
"name": "EliteG19s Alarm Attributes",
"topic": "eliteg19s/journal/magicmau/alarms/+/attributes",
"qos": "0",
"datatype": "auto",
"broker": "f1b0f0f8a2d5c3f4",
"nl": false,
"rap": true,
"rh": 0,
"x": 220,
"y": 120,
"wires": [["9b7fc4f4b1a3d6ce"]]
},
{
"id": "9b7fc4f4b1a3d6ce",
"type": "json",
"z": "d6f6c1d7d3b40a9f",
"name": "Parse JSON",
"property": "payload",
"action": "obj",
"pretty": false,
"x": 490,
"y": 120,
"wires": [["4b6f55bcb4a43e42"]]
},
{
"id": "4b6f55bcb4a43e42",
"type": "function",
"z": "d6f6c1d7d3b40a9f",
"name": "Prepare Timer Calls",
"func": "const match = msg.topic.match(/alarms\\/([^/]+)\\/attributes$/);
if (!match) {
return null;
}
const timerId = match[1];
const remaining = Number(msg.payload?.remaining_seconds ?? 0);
const entityId = `timer.elite_alarm_${timerId}`;
const startMsg = {
entity_id: entityId,
payload: {
data: {
entity_id: entityId,
duration: {
seconds: remaining
}
}
}
};
const cancelMsg = {
entity_id: entityId,
payload: {
data: {
entity_id: entityId
}
}
};
if (remaining > 0) {
return [startMsg, null];
}
return [null, cancelMsg];
",
"outputs": 2,
"noerr": 0,
"initialize": "",
"finalize": "",
"libs": [],
"x": 770,
"y": 120,
"wires": [["e9b9352fa3a9b87a"], ["d87b2f0aa5c1a4e3"]]
},
{
"id": "e9b9352fa3a9b87a",
"type": "api-call-service",
"z": "d6f6c1d7d3b40a9f",
"name": "Start Timer",
"server": "f4d0c3a1b2f5d6e7",
"version": 3,
"debugenabled": false,
"service_domain": "timer",
"service": "start",
"entityId": "",
"data": "payload.data",
"dataType": "jsonata",
"mergecontext": "",
"mustacheAltTags": false,
"outputProperties": [],
"queue": "none",
"x": 1040,
"y": 100,
"wires": [[]]
},
{
"id": "d87b2f0aa5c1a4e3",
"type": "api-call-service",
"z": "d6f6c1d7d3b40a9f",
"name": "Cancel Timer",
"server": "f4d0c3a1b2f5d6e7",
"version": 3,
"debugenabled": false,
"service_domain": "timer",
"service": "cancel",
"entityId": "",
"data": "payload.data",
"dataType": "jsonata",
"mergecontext": "",
"mustacheAltTags": false,
"outputProperties": [],
"queue": "none",
"x": 1040,
"y": 160,
"wires": [[]]
},
{
"id": "f1b0f0f8a2d5c3f4",
"type": "mqtt-broker",
"name": "MQTT Broker",
"broker": "mqtt.local",
"port": "1883",
"clientid": "",
"usetls": false,
"protocolVersion": "4",
"keepalive": "60",
"cleansession": true,
"birthTopic": "",
"birthQos": "0",
"birthPayload": "",
"birthMsg": {},
"closeTopic": "",
"closePayload": "",
"willTopic": "",
"willQos": "0",
"willPayload": "",
"willMsg": {},
"sessionExpiry": ""
},
{
"id": "f4d0c3a1b2f5d6e7",
"type": "server",
"name": "Home Assistant",
"addon": true
}
]
Tip: Adjust the MQTT topic, timer entity naming convention, and helper creation strategy to suit your environment. The
timer.elite_alarm_<alarm_id>naming pattern keeps helpers aligned with the sanitized IDs that EliteG19s publishes.