Skip to main content

Python client library for the JBL MA-series (MA510/MA710/MA7100HP/MA9100HP) IP control protocol

Project description

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

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

jbl_ma_control-1.2.0.tar.gz (24.0 kB view details)

Uploaded Source

Built Distribution

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

jbl_ma_control-1.2.0-py3-none-any.whl (22.1 kB view details)

Uploaded Python 3

File details

Details for the file jbl_ma_control-1.2.0.tar.gz.

File metadata

  • Download URL: jbl_ma_control-1.2.0.tar.gz
  • Upload date:
  • Size: 24.0 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.14.3

File hashes

Hashes for jbl_ma_control-1.2.0.tar.gz
Algorithm Hash digest
SHA256 53f8821e1e459b2b3514f025804562dca3104feffb703982a5b1929ce4175c41
MD5 8fd00d570a45055106c9d279ae7b4809
BLAKE2b-256 c65b74ae5d0c0406a29c1974b5f54b8db06e8689db00ebe54cfaa2dcd50d22dc

See more details on using hashes here.

File details

Details for the file jbl_ma_control-1.2.0-py3-none-any.whl.

File metadata

  • Download URL: jbl_ma_control-1.2.0-py3-none-any.whl
  • Upload date:
  • Size: 22.1 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.14.3

File hashes

Hashes for jbl_ma_control-1.2.0-py3-none-any.whl
Algorithm Hash digest
SHA256 8e6a257a9a28b0a7996548e46e2dc83d771bf08a5341842202782b60310b57eb
MD5 f7a64c1d829455e21872506eccbdf9c3
BLAKE2b-256 367282407a6f6b518fac64e9cd8d14312cfdfbf8899e60163282f00b7cb333c5

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