puffco-py 💨
A standalone, modern, asynchronous Python client and real-time telemetry engine for Puffco Peak Pro (V1, V2, Limited Editions) and Puffco Proxy smart vaporizers over Bluetooth Low Energy (BLE).
✨ Features
- ⚡ Lorax VFS Protocol: Native support for modern Puffco firmware (Lorax Virtual File System) with asynchronous sequence packet multiplexing and SHA-256 challenge-response authentication.
- ⏱️ Sub-100ms Low Latency: Adaptive polling engine capable of ~16 FPS streaming during active heating sessions (60ms) and low-power polling during idle (250ms).
- 🎮 Full Session Control: Start sessions, abort/stop, trigger heat boosts, change active profile slots, set custom target temperatures, and toggle stealth mode.
- 🧵 Thread-Safe & GUI Ready: Includes
ThreadedPuffcoClientfor effortless integration with synchronous scripts, desktop GUIs (Tkinter, PyQt), and macOS menu bar widgets (rumps). - 🔍 Auto-Discovery: Automatic scanning and filtering for nearby Puffco hardware based on manufacturer OUI prefixes and Lorax service UUIDs.
- 🛠️ Built-in CLI: Terminal utilities (
puffco-py scan,puffco-py monitor,puffco-py sesh) ready out of the box.
💡 Comparison & Advancements (vs. puffcoble)
We acknowledge the earlier exploratory work of Fr0st3h / PuffcoBLE (puffcoble), which demonstrated basic BLE reads. puffco-py was independently created from scratch to provide a modern, production-grade SDK and real-time telemetry engine:
- Reactive Telemetry Engine: Rather than issuing manual one-shot reads,
puffco-pycontinuously streams live bowl temperature, session countdowns, and battery state via an adaptive background loop with event-driven Observer listeners (add_telemetry_listener,add_state_listener). - Strongly-Typed Domain Models: Built on rich dataclasses (
PuffcoTelemetry,PuffcoDeviceInfo) and enums (DeviceState,ChamberType) with automatic Fahrenheit/Celsius conversions, replacing raw untyped primitives. - Threaded GUI Bridge: Includes
PuffcoThreadedClientrunning the event loop on an isolated daemon thread, making it seamless to integrate with Tkinter, PyQt, PySide, and macOS menu bar widgets without fighting asyncio event loop threading issues. - Interactive Terminal Suite: Built-in CLI with live terminal dashboards (
puffco-py monitor) powered byrich, device discovery (puffco-py scan), diagnostics (puffco-py info), and session controls. - Broader Python Compatibility: Supports Python 3.9+ (compared to 3.11+).
- Automated Test Suite: Full offline unit test suite with mock packet framing, sequence serialization, and SHA-256 challenge authentication verification.
📦 Installation
Install from source or local checkout:
git clone https://github.com/SoyMilkIsOk/puffco-py.git
cd puffco-py
pip install .
To include optional CLI and GUI dependencies:
pip install ".[cli,gui]"
🚀 Quickstart
1. Async Python (Core Client)
import asyncio
from puffco_py import PuffcoClient
async def main():
# Automatically scans for and connects to the nearest Puffco device
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}%")
# Stream live telemetry updates
def on_update(telemetry):
print(f"\rTemp: {telemetry.live_temp_f:.1f}°F | State: {telemetry.state_name}", end="")
client.add_telemetry_listener(on_update)
await client.start_telemetry_stream()
# Keep streaming for 15 seconds
await asyncio.sleep(15)
client.remove_telemetry_listener(on_update)
asyncio.run(main())
2. Synchronous & GUI Python (Threaded Client)
If you are building a desktop GUI (Tkinter, PyQt, PySide, wxPython) or a synchronous script, use ThreadedPuffcoClient. It executes all asynchronous BLE communications inside a dedicated background worker thread with its own event loop, exposing simple thread-safe methods to your main UI thread without blocking event dispatching:
import time
from puffco_py import ThreadedPuffcoClient
client = ThreadedPuffcoClient()
# Register real-time callbacks from the background thread
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())
# Synchronous thread-safe controls from main UI thread
client.set_profile(slot=1)
client.start_session()
time.sleep(10)
client.boost()
time.sleep(10)
client.stop_session()
client.stop()
🖥️ Command-Line Interface (CLI)
The package installs a standalone terminal tool puffco-py:
# 1. Scan for nearby devices (5s timeout)
puffco-py scan
# 2. Stream a live terminal telemetry HUD (Ctrl+C to exit)
puffco-py monitor
# 3. View hardware serial, firmware version, lifetime dabs, and heat profiles
puffco-py info
# 4. Trigger or abort heat sessions
puffco-py sesh start
puffco-py sesh boost
puffco-py sesh stop
CLI Command Reference
| Command | Arguments | Description |
|---|---|---|
puffco-py scan |
-t, --timeout <sec> (default: 5.0) |
Scans BLE advertisements and prints discovered devices with MAC addresses and RSSI signal strength. |
puffco-py monitor |
--mac <MAC/UUID> (optional) |
Live streams bowl temperature, target temp, battery %, chamber type, and session state. |
puffco-py info |
--mac <MAC/UUID> (optional) |
Connects and dumps hardware serial, firmware version, lifetime dab count, stealth status, and all 4 profile slot configurations. |
puffco-py sesh <action> |
start, stop, or boost--mac <MAC/UUID> (optional) |
Sends immediate heating commands to the connected device. |
🍏 Native macOS Menu Bar Widget
A fully functional, native status bar widget is provided in examples/04_mac_menu_widget.py (powered by rumps).
┌────────────────────────────────────────────────────────┐
│ 🔥 520°F (35s) (or: 💨 480°F | 85% when idle) │
└┬───────────────────────────────────────────────────────┘
│ Status: Heating (Sesh Active)
│ Battery: 85% (⚡ Charging)
│ Chamber: 3DXL
│ Total Dabs: 1,420
│ ───────────────────────────
│ 🚀 Start Heat Sesh
│ ⚡ Boost Heat (+15s / +10°F)
│ 🛑 Abort Sesh
└─────────────────────────────
Running the Menu Bar App:
# Install GUI extra dependencies (rumps on macOS)
pip install ".[gui]"
# Run the widget
python3 examples/04_mac_menu_widget.py
📁 Examples & Guides
The examples/ folder contains self-contained reference scripts:
| Example | Execution Model | Description |
|---|---|---|
01_live_telemetry.py |
Async (asyncio) |
Minimal quickstart: connects and streams live bowl temperature & battery. |
02_session_control.py |
Async (asyncio) |
Inspects profile slots, switches active profile, toggles stealth mode. |
03_threaded_gui.py |
Threaded / Sync | Background worker pattern for desktop GUIs (Tkinter, PyQt, PySide). |
04_mac_menu_widget.py |
Desktop GUI (rumps) |
Full macOS menu bar status indicator and session remote control. |
See examples/README.md for in-depth explanations, prerequisites, and troubleshooting tips.
📚 Python API Reference
Client Methods (PuffcoClient / ThreadedPuffcoClient)
| Method | Async / Sync | Description |
|---|---|---|
start_session() |
Both | Initiates a heating session using the currently active profile. |
stop_session() |
Both | Aborts and cancels an active heating session immediately. |
boost() |
Both | Triggers heat boost during an active session (+15s / +10°F). |
set_profile(slot) |
Both | Changes active heat profile slot (integer 0 to 3). |
set_stealth_mode(enabled) |
Both | Toggles LED lights on (False) or off (True). |
start_telemetry_stream(...) |
Async | Starts continuous polling engine (60ms active sesh, 250ms idle). |
add_telemetry_listener(fn) |
Both | Registers a callback fn(telemetry) called upon state change. |
Telemetry Model (PuffcoTelemetry)
Available via client.telemetry:
live_temp_f: Current real-time temperature of the heating bowl in °F.target_temp_f: Target temperature of the active profile in °F.battery_pct: Battery level (0–100%).is_charging: Boolean indicating USB-C or wireless Qi charging state.chamber_name: Connected chamber type ("3D","3DXL","Proxy", or"None").operating_state: Current state enum (IDLE,HEAT_PREHEAT,HEAT_ACTIVE,HEAT_FADE).is_heating: Boolean flag that isTruewhenever the chamber is actively powered.time_remaining: Remaining seconds in active heating cycle.lifetime_dabs: Lifetime total dab counter (odometer).profiles: List of 4 saved profile configurations (Profile(slot, name, temp_f, duration_s)).
🔬 How the Protocol Works
Puffco devices communicate over Bluetooth Low Energy (BLE) using the Lorax Virtual File System (VFS):
central central (PC/Mac/RPi) Puffco Device
│ │
│──────── 1. Connect (Bleak GATT) ───────────>│
│ │
│<─────── 2. Lorax Version / Notify ──────────│
│ │
│── 3. GET_ACCESS_SEED (Opcode 0x00) ────────>│
│<─ 16-Byte Random Seed Challenge ────────────│
│ │
│── 4. UNLOCK_ACCESS (Opcode 0x01) ──────────>│
│ Token = SHA256(MasterKey + Seed)[:16] │
│<─ Status: SUCCESS (0x00) ───────────────────│
│ │
│── 5. READ_SHORT ("/p/app/htr/temp") ───────>│
│<─ Live Chamber Temp (Float / Int Tenths) ───│
- Service UUID:
e276967f-ea8a-478a-a92e-d78f5dd15dd5 - Command Characteristic:
60133d5c-5727-4f2c-9697-d842c5292a3c(Write Without Response) - Reply Characteristic:
8dc5ec05-8f7d-45ad-99db-3fbde65dbd9c(Notify / Indicate) - VFS Paths:
/p/app/stat/id: Operating state byte (Idle, Preheat, Active, Fade)./p/app/htr/temp: Live bowl temperature./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 odometer dab count.
🧪 Running Tests
Run the offline unit test suite:
python3 -m unittest discover tests/
🙏 Credits & Acknowledgments
This library builds upon foundational protocol research and reverse-engineering from the community:
- Fr0st3h / PuffcoBLE: Early Python library exploring Lorax path reads and LED lighting controls.
- PuffcoPC by meekzyr: The original project exploring Puffco Bluetooth communication and pairing.
- home-assistant-puffco: Fantastic Home Assistant integration documenting Lorax paths and characteristics.
- Bleak: The backbone asynchronous BLE library for Python.
📜 License
This project is licensed under the MIT License.
⚖️ Legal Disclaimer
puffco-py is an independent, community-developed open-source reverse-engineering project. It is NOT affiliated with, authorized, maintained, sponsored, or endorsed by Puff Corp. (registered in Delaware) or any of its affiliates. All trademarks—including Puffco, Peak, Peak Pro, Proxy, 3D Chamber, and 3DXL—are registered trademarks of Puff Corp. Nominative use of these names is strictly for identification and interoperability purposes under Fair Use.
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file puffco_py-0.1.0.tar.gz.
File metadata
- Download URL: puffco_py-0.1.0.tar.gz
- Upload date:
- Size: 27.3 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via:
twine/7.0.0 CPython/3.11.4
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
362e839567439dffefca2a827b6fb708c44af08f7b4c072b146d9cc9c1cea4f3
|
|
| MD5 |
f8136f1040c25f397046eeb6b1720d06
|
|
| BLAKE2b-256 |
ff8ae82b470d78bfd5111f99c133b7e1c946ec88adea761b6159e1af538a9375
|
File details
Details for the file puffco_py-0.1.0-py3-none-any.whl.
File metadata
- Download URL: puffco_py-0.1.0-py3-none-any.whl
- Upload date:
- Size: 23.6 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via:
twine/7.0.0 CPython/3.11.4
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
abf6b20ccb2d732f01f14cf112f10fde0452dd5c5c0e713e7bc3c3f2611ea07d
|
|
| MD5 |
95fd8c3e633a1138a3401a1ca9fc163a
|
|
| BLAKE2b-256 |
2dd73f5b6b4355c5a4d804244d0efb2e078a62444ec61bff6e425ebdadf4ab83
|