Skip to main content

A Python library for interacting with Jura coffee machines over Bluetooth

Project description

py-jura

Python library for controlling JURA coffee machines over Bluetooth

Tests PyPI Python Version License Ruff mypy

Brew drinks, check machine status, read maintenance counters, and track live brew progress - all from Python, over Bluetooth, with a simple async API.

[!NOTE] Hardware testing has only been performed on a JURA E8 (EF533, firmware EF533M V01.18). All other machine families should work, but are untested. Feedback and bug reports from other models are very welcome.

Installation

pip install py-jura
# or with uv
uv add py-jura

Requires Python 3.11+ and a Bluetooth adapter supported by bleak.

Quick start

import asyncio
from py_jura import JuraMachine, MachineBlockedError, Product

async def main():
    async with JuraMachine("AA:BB:CC:DD:EE:FF") as machine:
        print(f"Connected to {machine.display_name}")  # e.g. "E8"

        try:
            await machine.brew(Product.ESPRESSO, strength=6, water_ml=40)
        except MachineBlockedError as e:
            print("Machine not ready:", [a.key for a in e.alerts])

asyncio.run(main())

JuraMachine is an async context manager. On entry it scans for the device, reads the BLE advertisement to identify the machine model and extract the encryption key, then connects and starts a background heartbeat. On exit it sends a graceful disconnect.

Supported machines

Series Models
E-line E4, E6, E7, E8
S-line S8
Z-line Z6, Z8, Z10
J-line J6, J8, J10, J8 twin, J10 twin
X-line X4, X6, X8, X10, X4c, X8c, X10c
W-line W4, W8
C-line C3, C8, C9
ENA ENA 4, ENA 5, ENA 8
GIGA GIGA 5, GIGA 6, GIGA X3/X7/X8/X9/X10, W10
Professional GIGA X3/X7/X8/X9 Professional, W3
D-line D4, D6
WE-line WE6, WE8

API reference

JuraMachine(address, max_retries=3)

Parameter Description
address BLE address - "AA:BB:CC:DD:EE:FF" on Linux/Windows, UUID string on macOS
max_retries Reconnection attempts before raising MachineDisconnectedError (default 3)

Identity properties - available immediately after connecting:

Property Type Description
.address str BLE address passed at construction
.article_number int Article number from BLE advertisement
.display_name str Customer-facing model name, e.g. "E8" or "GIGA X8"
async with JuraMachine("AA:BB:CC:DD:EE:FF") as machine:
    print(machine.article_number)  # 15084
    print(machine.display_name)    # "E8"

Method summary

Method Returns Description
get_status() MachineStatus Active alerts and ready state
brew(product, ...) - Start brewing a product
cancel_brew() - Cancel the current brew
get_progress() BrewProgress Real-time brew state
get_stats() MachineStats Lifetime brew counts
get_daily_stats() MachineStats Today's brew counts
get_maintenance() MaintenanceStats Maintenance counters and wear
get_about() MachineInfo Firmware version strings
lock() / unlock() - Barista mode (touchscreen lock)
shutdown() - Power off the machine

get_status() → MachineStatus

Reads the machine's current alert state.

status = await machine.get_status()
print(status.is_ready)
for alert in status.alerts:
    print(alert.key, "blocking" if alert.blocking else "info")
Field Type Description
.is_ready bool True when no blocking alerts are active
.alerts list[Alert] Active alerts; each has .key (e.g. "fill_water") and .blocking

brew(product, *, strength, water_ml, temperature, milk_ml, milk_break_ml) → None

Starts brewing. All option parameters are optional - omitted values use the machine's defaults.

await machine.brew(Product.ESPRESSO)
await machine.brew(Product.ESPRESSO, strength=8, water_ml=35, temperature=Temperature.HIGH)
await machine.brew(Product.CAPPUCCINO, milk_ml=120)
Parameter Type Description
product Product Product to brew
strength int Strength level (valid range is product- and model-specific)
water_ml int Water volume in ml
temperature Temperature LOW, NORMAL, or HIGH
milk_ml int Milk volume in ml
milk_break_ml int Milk break duration in ml-equivalent

Raises: UnsupportedProductError, ValueError (option out of range), MachineBlockedError


cancel_brew() → None

Cancels the in-progress brew.


get_progress() → BrewProgress

Reads the real-time brewing state.

progress = await machine.get_progress()
print(progress.is_idle)   # True when idle
print(progress.is_done)   # True when product is ready
print(progress.product)   # Product enum member, or None
Field Description
.is_idle True when state is 0x00
.is_done True when state is 0x24 (ready) or 0x3E (enjoy)
.product Resolved Product enum member, or None
.state Raw state byte (0x00 idle, 0x21 heating, 0x24 ready, 0x3E enjoy)

get_stats() → MachineStats

Reads lifetime brew counts.

stats = await machine.get_stats()
print(stats.total_count)
print(stats.product_counts[Product.ESPRESSO])
Field Description
.total_count Total brews across all products
.product_counts dict[Product, int]; products with zero brews are omitted

get_daily_stats() returns the same structure for today's counts only.


get_maintenance() → MaintenanceStats

Reads maintenance counters and wear percentages.

maint = await machine.get_maintenance()
print(maint.counters["cleaning"])    # times performed
print(maint.percentages["decalc"])  # remaining capacity 0–100
Field Description
.counters dict[str, int] - how many times each maintenance was performed
.percentages dict[str, int] - remaining capacity 0–100 (0 = overdue, 100 = just done)

Keys: "cleaning", "decalc", "filter_change", "cappu_rinse", "coffee_rinse", "cappu_clean" (model-dependent).


get_about() → MachineInfo

Reads firmware version strings.

info = await machine.get_about()
print(info.machine_version)    # e.g. "J-EF533-V02.04"
print(info.bluefrog_version)   # BLE module firmware

lock() / unlock() → None

Locks or unlocks the touchscreen (Barista mode).


shutdown() → None

Sends the machine shutdown command.


Products

Full product list (46 products)
Product Drink
RISTRETTO Ristretto
ESPRESSO Espresso
ESPRESSO_DOPPIO Espresso Doppio
COFFEE Coffee
CAPPUCCINO Cappuccino
ESPRESSO_MACCHIATO Espresso Macchiato
LATTE_MACCHIATO Latte Macchiato
MILK_COFFEE Milk Coffee
MILK_PORTION Milk Portion
MILK_FOAM Milk Foam
FLAT_WHITE Flat White
CAFE_BARISTA Café Barista
LUNGO_BARISTA Lungo Barista
CORTADO Cortado
LONG_BLACK Long Black
AMERICANO Americano
XL_LUNGO XL Lungo
MOCACCINO Mocaccino
RAF_COFFEE Raf Coffee
CHOCOLATE_MILK_FOAM Chocolate Milk Foam
HOT_WATER Hot Water
HOT_WATER_GREEN_TEA Hot Water (green tea temp)
POT Pot
POT_SPEED Pot (Speed)
COFFEE_BIG Coffee (large)
CAPPUCCINO_BIG Cappuccino (large)
MILK_COFFEE_BIG Milk Coffee (large)
LATTE_MACCHIATO_BIG Latte Macchiato (large)
MILK_BIG Milk (large)
HOT_WATER_BIG Hot Water (large)
TWO_RISTRETTI 2× Ristretto
TWO_ESPRESSI 2× Espresso
TWO_COFFEES 2× Coffee
TWO_CAPPUCCINI 2× Cappuccino
TWO_MILK_COFFEES 2× Milk Coffee
TWO_ESPRESSO_MACCHIATI 2× Espresso Macchiato
TWO_LATTE_MACCHIATI 2× Latte Macchiato
TWO_MILK_FOAM 2× Milk Foam
TWO_MILK_PORTIONS 2× Milk Portion
TWO_CAFE_BARISTAS 2× Café Barista
TWO_LUNGO_BARISTAS 2× Lungo Barista
TWO_LUNGOS 2× Lungo
TWO_CORTADOS 2× Cortado
TWO_ESPRESSI_ENA 2× Espresso (ENA)
TWO_COFFEES_ENA 2× Coffee (ENA)
TWO_FLAT_WHITES 2× Flat White

Which products are available depends on the machine model. Attempting to brew an unsupported product raises UnsupportedProductError.

ARTICLE_NAMES

A dict[int, str] mapping every article number to its customer-facing model name. Useful for scanning and discovery flows where you don't connect to the machine yet.

from py_jura import ARTICLE_NAMES

ARTICLE_NAMES[15084]  # "E8"
ARTICLE_NAMES[15234]  # "E7"

Exceptions

Exception When raised
MachineNotFoundError Device not found, or unrecognised article number
MachineDisconnectedError Not connected, or reconnection failed after max_retries
MachineBlockedError Blocking alerts active; brew not started - check .alerts
UnsupportedProductError Product not available on this machine model
JuraError Base class for all py-jura exceptions

Development

git clone https://github.com/g4bri3lDev/py-jura.git
cd py-jura
uv sync --all-extras

uv run pytest tests/                    # unit tests
uv run pytest tests/ --cov=src/py_jura  # with coverage
uv run pytest -m hardware               # hardware tests (real machine required)
uv run ruff check .
uv run mypy src/py_jura

Contributing

Contributions are welcome. Please open an issue before starting significant work so we can align on approach. Run tests and linting before submitting a PR.

Acknowledgements

The BLE protocol implementation is based on reverse-engineering work by the Jutta-Proto project, specifically protocol-bt-cpp - a C++ JURA protocol implementation released under GPL-3.0.

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

py_jura-0.2.1.tar.gz (108.2 kB view details)

Uploaded Source

Built Distribution

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

py_jura-0.2.1-py3-none-any.whl (164.6 kB view details)

Uploaded Python 3

File details

Details for the file py_jura-0.2.1.tar.gz.

File metadata

  • Download URL: py_jura-0.2.1.tar.gz
  • Upload date:
  • Size: 108.2 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.7

File hashes

Hashes for py_jura-0.2.1.tar.gz
Algorithm Hash digest
SHA256 fad4a9e03afd5d7f4617472534edbcc3ee64afd48f355e2a46cd0c589d148810
MD5 d3d5caf64ef54c6d23cd565caf83ff6e
BLAKE2b-256 98e986ba34bceed3364c631d8d67003a7fcc87fd8891748adcee7943ecd313aa

See more details on using hashes here.

Provenance

The following attestation bundles were made for py_jura-0.2.1.tar.gz:

Publisher: release.yml on g4bri3lDev/py-jura

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file py_jura-0.2.1-py3-none-any.whl.

File metadata

  • Download URL: py_jura-0.2.1-py3-none-any.whl
  • Upload date:
  • Size: 164.6 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.7

File hashes

Hashes for py_jura-0.2.1-py3-none-any.whl
Algorithm Hash digest
SHA256 adf882eb356c12c96975844008a540766ec1f2453681a72428ff3af0a254d046
MD5 915fd6e834e01591c4cddb6245191624
BLAKE2b-256 776923ecb4b0e403fb21659f2ce71d0575f7f00c767c124a2c290db209988c95

See more details on using hashes here.

Provenance

The following attestation bundles were made for py_jura-0.2.1-py3-none-any.whl:

Publisher: release.yml on g4bri3lDev/py-jura

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

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