Last active
March 11, 2026 01:14
-
-
Save c-kick/e15c233262e0050dd3f56a6070525862 to your computer and use it in GitHub Desktop.
Samsung soundbar control via SmartThings cloud API
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #!/usr/bin/env python3 | |
| """Samsung soundbar control via SmartThings cloud API. | |
| Controls advanced audio features (night mode, voice amplifier, bass boost) | |
| that are NOT available through the local JSON-RPC API on port 1516. | |
| Works by reading the SmartThings OAuth token from Home Assistant's config | |
| storage, then calling the SmartThings execute capability with OCF resource | |
| paths. No external dependencies — stdlib only. | |
| Tested on: HW-Q930D (firmware SAT-MT8532D24WWC-1030.9) | |
| Should work on: other 2024 Samsung soundbars (Q990D, Q800D, S800D, etc.) | |
| Setup: | |
| 1. Soundbar must be added to SmartThings | |
| 2. HA SmartThings integration must be configured (provides the OAuth token) | |
| 3. Set DEVICE_ID below to your soundbar's SmartThings device ID | |
| (find it via: https://api.smartthings.com/v1/devices with your token, | |
| or in the SmartThings app under device info) | |
| 4. Place this script in /config/scripts/ (HA container path) | |
| 5. Add shell_commands to configuration.yaml: | |
| shell_command: | |
| soundbar_nightmode_on: "python3 /config/scripts/soundbar_control.py set nightmode 1" | |
| soundbar_nightmode_off: "python3 /config/scripts/soundbar_control.py set nightmode 0" | |
| soundbar_voiceamp_on: "python3 /config/scripts/soundbar_control.py set voiceamplifier 1" | |
| soundbar_voiceamp_off: "python3 /config/scripts/soundbar_control.py set voiceamplifier 0" | |
| soundbar_bassboost_on: "python3 /config/scripts/soundbar_control.py set bassboost 1" | |
| soundbar_bassboost_off: "python3 /config/scripts/soundbar_control.py set bassboost 0" | |
| 6. Add template switches: | |
| template: | |
| - switch: | |
| - name: "Soundbar Night Mode" | |
| unique_id: soundbar_nightmode | |
| icon: mdi:weather-night | |
| state: "{{ this.state | default('off') }}" | |
| turn_on: | |
| - action: shell_command.soundbar_nightmode_on | |
| turn_off: | |
| - action: shell_command.soundbar_nightmode_off | |
| 7. Reload Shell Commands + Template Entities from Developer Tools > YAML | |
| Usage: | |
| soundbar_control.py set <setting> <0|1> | |
| soundbar_control.py get <setting> | |
| Settings: nightmode, voiceamplifier, bassboost | |
| Note: commands work even when the soundbar is powered off — the setting | |
| is applied via the SmartThings cloud and takes effect on next power-on. | |
| """ | |
| import json | |
| import sys | |
| import time | |
| import urllib.request | |
| import ssl | |
| STORAGE_PATH = "/config/.storage/core.config_entries" | |
| DEVICE_ID = "cd8511a0-80f5-698d-1084-f21e6a1bf2f5" | |
| ST_API = "https://api.smartthings.com/v1" | |
| SETTINGS_MAP = { | |
| "nightmode": "x.com.samsung.networkaudio.nightmode", | |
| "voiceamplifier": "x.com.samsung.networkaudio.voiceamplifier", | |
| "bassboost": "x.com.samsung.networkaudio.bassboost", | |
| } | |
| def get_smartthings_token(): | |
| with open(STORAGE_PATH) as f: | |
| data = json.load(f) | |
| for entry in data.get("data", {}).get("entries", []): | |
| if entry.get("domain") == "smartthings": | |
| return entry["data"]["token"]["access_token"] | |
| raise RuntimeError("SmartThings config entry not found") | |
| def st_request(token, path, body=None): | |
| url = f"{ST_API}{path}" | |
| headers = { | |
| "Authorization": f"Bearer {token}", | |
| "Content-Type": "application/json", | |
| } | |
| data = json.dumps(body).encode() if body else None | |
| method = "POST" if body else "GET" | |
| ctx = ssl.create_default_context() | |
| for attempt in range(5): | |
| try: | |
| req = urllib.request.Request(url, data=data, headers=headers, method=method) | |
| with urllib.request.urlopen(req, context=ctx, timeout=15) as resp: | |
| return json.loads(resp.read()) | |
| except urllib.error.URLError: | |
| if attempt < 4: | |
| time.sleep(2) | |
| continue | |
| raise | |
| def set_setting(token, setting, value): | |
| param = SETTINGS_MAP[setting] | |
| body = { | |
| "commands": [{ | |
| "component": "main", | |
| "capability": "execute", | |
| "command": "execute", | |
| "arguments": [ | |
| "/sec/networkaudio/advancedaudio", | |
| {param: int(value)}, | |
| ], | |
| }] | |
| } | |
| result = st_request(token, f"/devices/{DEVICE_ID}/commands", body) | |
| status = result.get("results", [{}])[0].get("status", "UNKNOWN") | |
| print(json.dumps({"setting": setting, "value": int(value), "status": status})) | |
| return status == "COMPLETED" | |
| def get_settings(token): | |
| """Read advanced audio settings. Triggers a read then fetches status.""" | |
| # Trigger read | |
| body = { | |
| "commands": [{ | |
| "component": "main", | |
| "capability": "execute", | |
| "command": "execute", | |
| "arguments": ["/sec/networkaudio/advancedaudio"], | |
| }] | |
| } | |
| st_request(token, f"/devices/{DEVICE_ID}/commands", body) | |
| # Fetch result | |
| import time | |
| time.sleep(1) | |
| result = st_request( | |
| token, | |
| f"/devices/{DEVICE_ID}/components/main/capabilities/execute/status", | |
| ) | |
| data = result.get("data", {}).get("value", {}) | |
| if data and isinstance(data, dict): | |
| payload = data.get("payload", {}) | |
| print(json.dumps(payload, indent=2)) | |
| else: | |
| print(json.dumps({"error": "no data returned", "raw": data})) | |
| def main(): | |
| if len(sys.argv) < 3: | |
| print(f"Usage: {sys.argv[0]} get|set <setting> [value]", file=sys.stderr) | |
| sys.exit(1) | |
| action = sys.argv[1] | |
| setting = sys.argv[2].lower() | |
| if setting not in SETTINGS_MAP and action == "set": | |
| print(f"Unknown setting: {setting}. Use: {', '.join(SETTINGS_MAP)}", file=sys.stderr) | |
| sys.exit(1) | |
| token = get_smartthings_token() | |
| if action == "set": | |
| if len(sys.argv) < 4: | |
| print("Usage: set <setting> <0|1>", file=sys.stderr) | |
| sys.exit(1) | |
| value = int(sys.argv[3]) | |
| if not set_setting(token, setting, value): | |
| sys.exit(1) | |
| elif action == "get": | |
| get_settings(token) | |
| else: | |
| print(f"Unknown action: {action}", file=sys.stderr) | |
| sys.exit(1) | |
| if __name__ == "__main__": | |
| main() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
thanks this worked out pretty well for the night mode