Documentation

Developer reference for Puffco Peak Pro and Proxy BLE telemetry and control.

Python 3.9–3.13 Bleak GATT Lorax VFS MIT License

Introduction

puffco-py is a standalone, lightweight, asynchronous Python library for communicating with Puffco Peak Pro (V1, V2, Limited Editions) and Puffco Proxy vaporizers using Bluetooth Low Energy (BLE).

Modern Puffco devices use a proprietary firmware protocol called the Lorax Virtual File System (VFS). Rather than using fixed GATT characteristics for every sensor, Puffco multiplexes virtual file paths over a single write/notify characteristic pair. puffco-py implements this protocol natively, providing:

  • Low latency streaming — Adaptive polling at up to ~16 FPS (60ms) during active heating sessions.
  • Cryptographic authentication — Automatically solves the SHA-256 access seed challenge required by modern firmware.
  • Desktop GUI friendly — Dedicated background-worker thread client (ThreadedPuffcoClient) for Tkinter, PyQt, and macOS status bar widgets.
  • Hardware telemetry — Live bowl temperature, battery %, charging status, chamber detection (3D, 3DXL, Proxy), and lifetime dab counter.

Installation

Requires Python 3.9 or higher. Install from PyPI or build from source.

Standard Install (PyPI)

bash
pip install puffco-py

Optional Extras

Include CLI formatting and macOS menu bar widget dependencies:

bash
pip install "puffco-py[cli,gui]"

From Source

bash
git clone https://github.com/SoyMilkIsOk/puffco-py.git
cd puffco-py
pip install -e .

Hardware & Platform Support

Device Chambers Firmware Status
Peak Pro V2 3DXL, Standard 3D v5.0–v5.4.x ✓ Supported
Peak Pro V1 Standard 3D, Ceramic v4.x, v5.x ✓ Supported
Puffco Proxy Proxy 3D Chamber All production ✓ Supported
ℹ️
BLE Permissions:
macOS: Enable Bluetooth for your terminal in System Settings > Privacy & Security > Bluetooth.
Linux: Ensure bluez is installed. You may need to add your user to the bluetooth group.

Async Python Quickstart

PuffcoClient is an async context manager powered by asyncio and bleak. It auto-discovers the nearest device, authenticates, and starts telemetry.

python
import asyncio
from puffco_py import PuffcoClient

async def main():
    async with PuffcoClient() as client:
        print(f"Connected to: {client.telemetry.device_name}")
        print(f"Chamber: {client.telemetry.chamber_name} | Battery: {client.telemetry.battery_pct}%")

        def on_telemetry(t):
            print(f"\rTemp: {t.live_temp_f:.1f}°F | State: {t.state_name}", end="")

        client.add_telemetry_listener(on_telemetry)
        await client.start_telemetry_stream()
        await asyncio.sleep(15)

asyncio.run(main())

Threaded / GUI Quickstart

For desktop GUIs (Tkinter, PyQt, PySide) or synchronous scripts, use ThreadedPuffcoClient. It runs the async BLE event loop on a background thread and exposes simple synchronous methods:

python
import time
from puffco_py import ThreadedPuffcoClient

client = ThreadedPuffcoClient()
client.add_telemetry_listener(lambda t: print(f"Live Temp: {t.live_temp_f:.1f}°F"))

client.start()

if client.wait_connected(timeout=10.0):
    print(client.telemetry.summary())

    client.set_profile(slot=1)
    client.start_session()
    time.sleep(10)
    client.boost()
    time.sleep(10)
    client.stop_session()

client.stop()

CLI Commands

The package includes an out-of-the-box CLI utility named puffco-py:

Command Options Description
puffco-py scan -t, --timeout <sec> Scan BLE advertisements for nearby Puffco devices.
puffco-py monitor --mac <addr> Live stream an interactive terminal HUD with temp, battery, and state.
puffco-py info --mac <addr> Dump hardware serial, firmware version, dab count, and profiles.
puffco-py sesh <action> start, stop, boost Send immediate heating commands to the connected device.
💡
If multiple devices are in range, supply --mac AA:BB:CC:DD:EE:FF to target a specific unit.

macOS Menu Bar Widget

A native macOS status bar app is provided in examples/04_mac_menu_widget.py (powered by rumps):

bash
pip install "puffco-py[gui]"
python3 examples/04_mac_menu_widget.py

The widget displays live bowl temperature in your menu bar and provides one-click dropdown actions to start, boost, or abort sessions.

Features

  • Lorax VFS Protocol — Native support for Puffco firmware using Lorax Virtual File System sequence multiplexing and SHA-256 challenge-response authentication.
  • Sub-100ms Streaming — Adaptive polling engine streams ~16 FPS (60ms) during active heating and throttles to 250ms when idle.
  • Thread-Safe & GUI Ready — ThreadedPuffcoClient for synchronous UI toolkits (Tkinter, PyQt, PySide) without blocking dispatch threads.
  • macOS Status Bar Widget — Ready-to-run native menu bar app powered by rumps, showing live temps and one-click session triggers.
  • Auto-Discovery — Scans BLE advertisements and filters nearest devices by manufacturer OUI and Lorax GATT UUIDs.
  • Full Session Control — Start sessions, abort, trigger heat boosts, change profiles, set temperatures, and toggle stealth mode.

Python API Reference

PuffcoClient Methods

Method Return Description
async connect(timeout=10.0) bool Connects via Bleak GATT and solves the Lorax SHA-256 access seed challenge.
async disconnect() None Gracefully terminates GATT connection and stops polling.
async start_session() None Initiates a heating session using the active profile.
async stop_session() None Aborts an active heating session immediately.
async boost() None Applies heat boost (+15s duration and +10°F).
async set_profile(slot: int) bool Switches active profile slot (0–3).
async set_temperature(temp_f, slot=None) bool Sets target temperature for the active profile (or specified slot).
async set_profile_duration(slot, seconds) bool Sets session timer duration (15–180s) for a profile slot.
async set_profile_name(slot, name) bool Sets display name (up to 16 chars) for a profile slot.
async save_profile(slot, profile) bool Atomically updates name, temperature, and duration for a profile.
async set_boost_temperature(temp_f) bool Configures heat boost temperature increment in °F (+5 to +50°F).
async set_boost_duration(seconds) bool Configures heat boost session extension in seconds (+5 to +60s).
async set_stealth_mode(enabled: bool) bool Toggles device mood lights on or off.
add_telemetry_listener(callback) None Registers a callback(telemetry) called on state changes.

Telemetry Model

Accessible via client.telemetry:

Property Type Description
device_name str Configured user device name.
device_model str Identified hardware model: "Peak Pro (V2)", "Peak Pro (V1)", "Puffco Proxy", "Puffco Pivot".
live_temp_f float Current real-time bowl temperature in °F.
target_temp_f float Target temperature of the active profile.
battery_pct int Battery state of charge (0–100%).
is_charging bool True if plugged in via USB-C or on Qi dock.
chamber_name str Detected chamber: "3D", "3DXL", "Proxy", or "None".
operating_state Enum IDLE, HEAT_PREHEAT, HEAT_ACTIVE, HEAT_FADE.
time_remaining int Remaining session seconds during active heating.
total_time int Total programmed duration of active profile.
lifetime_dabs int Total completed sessions stored in hardware memory.
boost_temp_f int Configured heat boost temperature increment (°F).
boost_duration_s int Configured heat boost session extension (seconds).

Lorax VFS Protocol

Puffco firmware implements a sequence-multiplexed file system over BLE:

GATT Entity UUID Properties
Service e276967f-ea8a-478a-a92e-d78f5dd15dd5 Primary Lorax BLE Service
Command 60133d5c-5727-4f2c-9697-d842c5292a3c Write Without Response
Reply 8dc5ec05-8f7d-45ad-99db-3fbde65dbd9c Notify / Indicate

VFS Paths

  • /p/app/stat/id — Operating state byte (0=Idle, 1=Preheat, 2=Sesh, 3=Fade)
  • /p/app/htr/temp — Chamber temperature sensor reading
  • /p/app/mc — Mode control (0x07=Start, 0x08=Stop, 0x09=Boost)
  • /p/bat/soc — Battery state of charge percentage
  • /p/app/odom/0/nc — Lifetime completed dabs counter

Authentication Flow

  1. GATT Connection — Bleak discovers device via Lorax service UUID and establishes an MTU-negotiated link.
  2. Seed Challenge — Client sends opcode 0x00 (GET_ACCESS_SEED) to request a 16-byte random seed.
  3. SHA-256 Auth — Client computes SHA256(MasterKey + Seed)[:16] and sends opcode 0x01 (UNLOCK_ACCESS).
  4. Stream Telemetry — Client polls VFS paths for live temperature, state, and battery level.

Platform Troubleshooting & Gotchas

1. The Single-Central Rule (Exclusive Connection)

Puffco hardware only supports one active BLE connection at a time. If the official Puffco iOS/Android app or Puffco Web App (Chrome) is running in the background on your phone, the device will reject connection attempts from puffco-py.

Solution: Completely force-close the mobile app and close any active Web Bluetooth browser tabs before running your Python scripts or CLI commands.

2. Linux & Raspberry Pi (BlueZ Permissions)

The Linux kernel restricts raw BLE socket creation to root by default. If device scanning returns zero results or throws PermissionDenied:

bash
sudo setcap 'cap_net_raw,cap_net_admin+eip' $(readlink -f $(which python3))
sudo systemctl status bluetooth

3. macOS Bluetooth Permissions

On macOS Ventura, Sonoma, and Sequoia, terminal applications require explicit authorization under TCC privacy controls. Go to System Settings > Privacy & Security > Bluetooth and ensure your terminal app (Terminal, iTerm2, or VS Code) is toggled ON.

4. Windows 10 / 11 Bluetooth Pairing

The Windows WinRT BLE stack requires the Puffco Peak Pro to be paired in Windows Settings before third-party applications can enumerate GATT services. Open Settings > Bluetooth & devices > Add device, put your Peak Pro into pairing mode, and pair it once in Windows.

5. Running Offline Tests

bash
pytest tests/ -v   # or: python3 -m unittest discover tests/