Skip to main content

Local control of the Logitech Harmony Hub: library, CLI, and MCP server

Project description

harmonyhub-py

Version License: MIT Python

harmonyhub-py talks directly to your hub over WebSocket on the local network — no Logitech cloud, no account, no internet required after the initial one-time provisioning.


Features

  • Pure local control — WebSocket on port 8088; provisioning POST is answered by the hub itself
  • Async Python libraryasync with HarmonyHubClient("192.168.1.x") as hub: ...
  • Rich CLI — human-readable tables or --json for scripts and pipes
  • MCP server — expose your hub as tools to Claude or any MCP client
  • Logical-key routing — maps volume_up, channel_down, ok, etc. to the right device automatically
  • Hub discovery — SSDP M-SEARCH + parallel /24 port scan
  • Simulator — fake hub for tests; no real hardware needed

Installation

pip install harmonyhub-py

Requires Python 3.14+.


Quick start

1. Discover your hub

harmony discover
           Discovered Harmony Hubs
┏━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━┳━━━━━━━━━━━┓
┃ Host           ┃ Friendly Name ┃ Remote ID ┃
┡━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━╇━━━━━━━━━━━┩
│ 192.168.178.50 │ Harmony Hub   │ 12345678  │
└────────────────┴───────────────┴───────────┘

2. Point the CLI at the hub

export HARMONY_HUB_HOST=192.168.178.50
harmony status
Activity: PowerOff
Last channel: 5 (source: library)
Connected: True

3. Use it

harmony activities list
harmony activities start "Watch TV"
harmony key volume-up
harmony channel 101

CLI reference

harmony [--host IP] [--json]
Command Description
harmony discover Find hubs on the LAN via SSDP + /24 port scan
harmony info Hub identity: remote ID, firmware, friendly name
harmony status Current activity, last channel, connection state
harmony power-off Run the PowerOff activity
harmony listen Stream real-time hub events to stdout
harmony activities list/current/start List, query, or switch activities
harmony devices list/commands List devices and their IR commands
harmony device power-on/off <device> Power a single device on or off
harmony key volume-up/down/mute/ok/back/digit Send a logical key with automatic routing
harmony channel <n> Switch channel (digits_then_enter or change_channel)
harmony send --device DEV --command CMD Send a raw IR command to a specific device
harmony config pull/show/diff Fetch, display, or diff the hub config
harmony sequence list/run List and fire hub-defined macro sequences
harmony doctor End-to-end diagnostic: network → provisioning → WebSocket → config

Use --json for machine-readable output on any read command. All log output goes to stderr; stdout stays scriptable.

For the full subcommand reference see docs/routing.md.

Exit codes

Code Meaning
0 success
2 usage / validation error
10 hub unavailable on the network
11 protocol error (timeout, malformed payload)
12 command or alias not found
13 routing ambiguous — pass an explicit --device

Shell completions (zsh)

harmony --install-completion zsh
exec zsh

Python library

import asyncio
from harmonyhub import HarmonyHubClient


async def main() -> None:
    async with HarmonyHubClient("192.168.178.50") as hub:
        info = await hub.get_info()
        print(f"Hub: {info.friendly_name} (remote-id: {info.remote_id})")

        for activity in await hub.list_activities():
            print(f"  {activity.label} (id={activity.id})")

        await hub.start_activity("Watch TV")
        await hub.send_key("volume_up")
        result = await hub.set_channel("101")
        print(f"Channel via {result.method}")


asyncio.run(main())

MCP server

harmonyhub-py ships with an MCP server that exposes the hub as tools for Claude or any MCP-compatible client.

Claude Desktop

Add to claude_desktop_config.json:

{
  "mcpServers": {
    "harmony": {
      "command": "harmony-mcp",
      "args": [],
      "env": { "HARMONY_HUB_HOST": "192.168.178.50" }
    }
  }
}

VS Code (agent mode)

{
  "mcp": {
    "servers": {
      "harmony": {
        "command": "harmony-mcp",
        "type": "stdio",
        "env": { "HARMONY_HUB_HOST": "192.168.178.50" }
      }
    }
  }
}

Available MCP tools

harmony_get_status · harmony_list_activities · harmony_start_activity · harmony_power_off · harmony_list_devices · harmony_list_device_commands · harmony_device_power_on · harmony_device_power_off · harmony_send_key · harmony_send_command · harmony_set_channel · harmony_refresh_config

Resources: harmony://config · harmony://activities · harmony://devices · harmony://status


Agent skill

skill/harmonyhub/SKILL.md follows the Agent Skills open standard and works across GitHub Copilot (VS Code, CLI, cloud agent), Claude Code, and Cursor — write once, use everywhere.

It teaches the model to translate voice-style requests ("schalte Pro7 ein", "lauter", "alles aus") into the matching MCP tool calls or harmony CLI invocations. Replies stay in the user's language — no tool names, no JSON.

Skill installation

Project skill (one repository):

cp -r skill/harmonyhub /your-project/.agents/skills/harmonyhub
# or keep in sync via symlink:
ln -s "$(pwd)/skill/harmonyhub" /your-project/.agents/skills/harmonyhub

Personal skill (every project on your machine):

cp -r skill/harmonyhub ~/.agents/skills/harmonyhub      # generic / Copilot
cp -r skill/harmonyhub ~/.claude/skills/harmonyhub       # Claude Code

[!TIP] If you cloned harmonyhub-py, the skill is already installed as .agents/skills/harmonyhub — no extra step needed.

See docs/skill.md for full installation notes.


Configuration

Config file: ~/.config/harmony-local/config.toml

[hub]
host = "192.168.178.50"

[connection]
mode = "persistent"            # persistent | ondemand
protocol = "websocket"         # websocket | xmpp
keepalive_interval_s = 50
request_timeout_s = 10

[channel]
mode = "digits_then_enter"     # digits_then_enter | change_channel
inter_digit_delay_ms = 150
send_enter = true

[volume]
repeat = 1                     # IR presses per logical key (e.g. 4 → 2 dB steps on a Yamaha AVR)
hold_ms = 0                    # extra hold per press in ms
inter_press_delay_ms = 80      # gap between repeats when repeat > 1

[activity_routes."Watch TV"]
volume_device = "Denon AVR"
channel_device = "Vodafone Receiver"
navigation_device = "Vodafone Receiver"
number_device = "Vodafone Receiver"

Environment variable overrides:

Variable Overrides
HARMONY_HUB_HOST hub.host
HARMONY_PROTOCOL connection.protocol
HARMONY_CONNECTION_MODE connection.mode

Precedence: CLI flag > env var > config file > default.


Limitations

The hub is a one-way IR transmitter for most operations, so the library is honest about what it cannot know:

Question Verdict
Which activity is active? Reliable — read directly from the hub
What is the current channel? Unknown to the hub; tracked only when set via this library (last_channel_source flagged)
Is a specific device powered on? Inferred from the active activity; unverified
Did the user press the original remote? Invisible to the hub

Run harmony status to inspect what is actually known.


Development

git clone https://github.com/jenreh/harmonyhub-py
cd harmonyhub-py
uv sync --extra dev
task test     # pytest with coverage
task lint     # ruff + mypy
task format   # ruff format

[!NOTE] A FakeHub simulator (harmonyhub.simulator) is included for use in tests — no real Harmony Hub required.


Documentation

File Summary
docs/protocol.md Raw WebSocket/HTTP payloads; reverse-engineered hub wire format
docs/routing.md How logical keys resolve to a device — precedence rules and config examples
docs/skill.md Installation guide for the natural-language agent skill
docs/troubleshooting.md Common errors (HTTP 401, timeout, provisioning failures) and fixes

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

harmonyhub_py-0.2.5.tar.gz (43.6 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

harmonyhub_py-0.2.5-py3-none-any.whl (42.4 kB view details)

Uploaded Python 3

File details

Details for the file harmonyhub_py-0.2.5.tar.gz.

File metadata

  • Download URL: harmonyhub_py-0.2.5.tar.gz
  • Upload date:
  • Size: 43.6 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: uv/0.11.14 {"installer":{"name":"uv","version":"0.11.14","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}

File hashes

Hashes for harmonyhub_py-0.2.5.tar.gz
Algorithm Hash digest
SHA256 b14847e428ecd925ede94229f775053e0bcec289c761d0309b1bd71b33eba718
MD5 bfaed4e43bc26fc8cc30d7721d57cc7b
BLAKE2b-256 090ca18ac965e9a006f130e33e4ed76489212c81892bac96744d1ddd64367b3d

See more details on using hashes here.

File details

Details for the file harmonyhub_py-0.2.5-py3-none-any.whl.

File metadata

  • Download URL: harmonyhub_py-0.2.5-py3-none-any.whl
  • Upload date:
  • Size: 42.4 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: uv/0.11.14 {"installer":{"name":"uv","version":"0.11.14","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}

File hashes

Hashes for harmonyhub_py-0.2.5-py3-none-any.whl
Algorithm Hash digest
SHA256 602d9138ac94fb18893bc2908b33bcb87ef20f7ebb67203af29818b208e8bbbe
MD5 7394a31c4db97c468f2f85e81d9013dc
BLAKE2b-256 7e02ac0b09c054092c7d974c9df0ed321a517731bc323ec9a8418a94b83b23e0

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page