Skip to main content

jbl-ma-control

A pure-Python client library for the JBL MA-series AV receivers (MA510, MA710, MA7100HP, MA9100HP) over their IP control protocol.

It implements every command and option in Harman's IP control interface and commands for JBL MA510/MA710/MA7100HP/MA9100HP (document v1.7): power, input selection, volume, mute, surround decoding, equalization, room EQ, Dolby/DTS options, streaming status, IR passthrough, reboot, factory reset and more.

  • No third-party runtime dependencies.
  • Fully type-hinted (ships py.typed).
  • Thorough error handling with a typed exception hierarchy.
  • Framing layer is transport-independent and unit-tested; the client is tested end-to-end against an in-process mock AVR.

Installation

pip install jbl-ma-control

Quick start

from jbl_ma import JBLClient, InputSource, SurroundMode

# The AVR always listens on TCP port 50000.
with JBLClient("192.168.1.50") as avr:
    model = avr.initialize()          # identify ourselves; returns the Model
    print("Connected to", model.name)

    avr.power_on()
    avr.set_input_source(InputSource.HDMI1)
    avr.set_volume(35)                # 0..99
    avr.set_surround_mode(SurroundMode.ALL_STEREO)
    avr.set_bass(4)                   # dB, -12..12
    avr.unmute()

Async

For asyncio applications (including Home Assistant) use AsyncJBLClient, which exposes the same method names as coroutines:

import asyncio
from jbl_ma import AsyncJBLClient, InputSource

async def main():
    async with AsyncJBLClient("192.168.1.50") as avr:
        await avr.initialize()
        await avr.power_on()
        await avr.set_input_source(InputSource.HDMI1)
        await avr.set_volume(35)

asyncio.run(main())

Concurrent coroutines sharing one AsyncJBLClient are safe — transactions are serialized internally so request/response pairs never interleave.

Push updates (no polling)

The AVR sends unsolicited frames whenever its state changes from the front panel or IR remote. AsyncJBLClient runs a background reader while connected, so an on_event callback receives those changes as they happen — you don't have to poll:

def on_change(frame):
    print("state changed:", hex(frame.command), frame.data)

async with AsyncJBLClient("192.168.1.50", on_event=on_change) as avr:
    await avr.initialize()
    await asyncio.sleep(3600)   # on_change fires whenever the AVR changes

on_event may be a plain function or a coroutine function. Note the protocol has no request/response correlation id, so a state-change frame that arrives in the exact window while a command's response is awaited may be consumed as that response; this is a protocol limitation, not a library one.

Manual connection management

The client is a context manager; you can also manage the connection manually:

avr = JBLClient("192.168.1.50", connect=False)
avr.connect()
try:
    print(avr.get_volume())
finally:
    avr.close()

Protocol notes

Every transmission uses the frame format from the PDF:

Controller -> AVR : <0x23><CmdID><DataLen><Data...><0x0D>
AVR -> Controller : <0x02 0x23><CmdID><RspCode><DataLen><Data...><0x0D>
  • After connecting you should call initialize() so the AVR registers this controller. It returns the connected Model.

  • Call heartbeat() periodically to keep the AVR out of its auto-standby timer; initialize() works as a heartbeat too.

  • reboot() is acknowledged and then the unit restarts, dropping the connection. On the MA510 (verified on hardware) it comes back up in standby rather than powered on — call power_on() afterwards if you need it on, and allow time for it to reboot before reconnecting.

  • The AVR also emits unsolicited frames when its state changes from the front panel or IR remote. Pass an on_event callback to receive them:

    def on_change(frame):
        print("state change:", frame.command, frame.data)
    
    avr = JBLClient("192.168.1.50", on_event=on_change)
    

API overview

Area Methods
System initialize(), heartbeat(), reboot(), factory_reset(), get_software_version()
Power get_power(), set_power(), power_on(), standby()
Display get_display_dim(), set_display_dim()
Source get_input_source(), set_input_source()
Volume get_volume(), set_volume(), get_mute(), set_mute(), mute(), unmute()
Sound get_surround_mode(), set_surround_mode(), get_dolby_audio_mode(), set_dolby_audio_mode(), get_dialog_enhanced(), set_dialog_enhanced(), get_drc(), set_drc()
EQ get_treble(), set_treble(), get_bass(), set_bass(), get_room_eq(), set_room_eq()
Party mode get_party_mode(), set_party_mode(), get_party_volume(), set_party_volume()
Streaming get_streaming_state()
IR remote send_ir()

All enums (InputSource, SurroundMode, DisplayDim, RoomEQ, DolbyAudioMode, PowerState, StreamingServer, PlayState, SoftwareModule, Model, IRCommand) and value ranges are exported from the top-level jbl_ma package.

Model availability: some options only exist on certain models (for example HDMI 5/6, Phono, party mode, DTS Neural:X and Dirac Live). The library exposes every documented value; sending one an attached model does not support results in the AVR replying with ParameterNotRecognizedError or CommandInvalidError.

Error handling

All errors derive from jbl_ma.JBLError:

from jbl_ma import JBLClient, JBLCommandError, CommandInvalidError, RoomEQ

with JBLClient("192.168.1.50") as avr:
    try:
        avr.set_room_eq(RoomEQ.DIRAC_LIVE)
    except CommandInvalidError:
        print("No room-correction filter has been uploaded yet.")
    except JBLCommandError as exc:
        print("AVR rejected the command, code", hex(exc.code))
Exception Meaning
JBLConnectionError Socket refused / reset / closed
JBLTimeoutError No complete response within the timeout
JBLProtocolError A malformed frame was sent or received
CommandNotRecognizedError Response code 0xC1
ParameterNotRecognizedError Response code 0xC2
CommandInvalidError Response code 0xC3 (command invalid at this time)
InvalidDataLengthError Response code 0xC4

Low-level framing

The jbl_ma.protocol module can encode and decode frames without any I/O, which is useful for testing or building your own transport:

from jbl_ma import encode_command, decode_responses, Command

frame = encode_command(Command.MASTER_VOLUME, b"\x28")   # set volume to 40
responses, leftover = decode_responses(b"\x02\x23\x06\x00\x01\x28\x0d")

Development

pip install -e ".[dev]"
pytest

License

MIT

Metadata

Release files for jbl-ma-control 1.2.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for jbl-ma-control 1.2.0
File Size Uploaded
jbl_ma_control-1.2.0.tar.gz 24.0 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for jbl-ma-control 1.2.0
File Interpreter ABI Platform
jbl_ma_control-1.2.0-py3-none-any.whl Python 3 none any Details

Total release size: 46.2 kB

Release files / jbl_ma_control-1.2.0.tar.gz

Download URL jbl_ma_control-1.2.0.tar.gz
Size 24.0 kB
Tags Source
SHA-256 checksum
How to use checksums
53f8821e1e459b2b3514f025804562dca3104feffb703982a5b1929ce4175c41
BLAKE2b-256 checksum
How to use checksums
c65b74ae5d0c0406a29c1974b5f54b8db06e8689db00ebe54cfaa2dcd50d22dc
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/6.2.0 CPython/3.14.3

Release files / jbl_ma_control-1.2.0-py3-none-any.whl

Download URL jbl_ma_control-1.2.0-py3-none-any.whl
Size 22.1 kB
Tags Python 3
SHA-256 checksum
How to use checksums
8e6a257a9a28b0a7996548e46e2dc83d771bf08a5341842202782b60310b57eb
BLAKE2b-256 checksum
How to use checksums
367282407a6f6b518fac64e9cd8d14312cfdfbf8899e60163282f00b7cb333c5
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/6.2.0 CPython/3.14.3

Release history Release notifications | RSS feed

This release

1.2.0 This release

2 release files

1.1.0

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page