gmg-local
Local UDP client for Green Mountain Grills Wi-Fi pellet grills.
No cloud account, no vendor API, no internet. It talks to the grill on your LAN and nothing else.
Not on PyPI yet - built and tested, awaiting a decision to publish. Origin and attribution: NOTICE.md.
from gmg_local import discover
for grill in discover():
state = grill.status()
print(grill.serial_number, state["temp"], "F")
Why this exists
Every public Python implementation of this protocol froze on 2023-01-28. The most-installed Home Assistant integration for these grills reads grill temperature from a single byte, so a 350 °F grill reports as 94 °F - the one number the device exists to tell you.
u16(94, 1) == 350 # not 94
Install
pip install -e . # from a clone; not on PyPI yet
Requires Python 3.11+. No dependencies.
API
discover(timeout=1, ip_bind_address="0.0.0.0", target=None) |
Broadcast for grills. Pass target to unicast across VLANs, where broadcast dies at the L2 edge. |
Grill.status(retries=5) |
One poll, decoded. Retries on silence and on junk. |
Grill.firmware(retries=5) |
The firmware string (UN!), verbatim - e.g. UNJB02SUF0_2.3. Same retry contract as status(). |
model_for(firmware) |
Pure. "Jim Bowie" for a JB firmware prefix, None for anything unrecognised. |
Grill.set_temp(f) / set_temp_probe(f, n) |
Targets, in Fahrenheit. |
Grill.power_on() / power_on_cool() / power_off() |
Cold smoke is power_on_cool. |
parse_status(frame) |
Pure. No sockets, no clock - testable without a grill. |
poll_interval_for(state) |
10s while cooking, 60s while off. |
What the status frame contains
A healthy grill answers UR001! with 52 bytes; this library decodes through
byte 33.
| offset | field | notes |
|---|---|---|
| 2, 4, 6 | grill temp, probe 1, setpoint | 16-bit LE pairs |
| 8 | API version | |
| 16, 18 | probe 2 temp and setpoint | |
| 24-27 | warn state | 32-bit LE |
| 28 | probe 1 setpoint | |
| 30 | power state | 0 Off, 1 On, 2 Fan, 3 Cold Smoke |
| 32 | fire state | 0 Default, 1 Off, 2 Startup, 3 Running, 4 Cool Down, 5 Fail, 198 Cold Smoke |
| 33 | fire state progress | see below |
| 34-51 | undecoded | gmg 0.0.4 labels 48 and 50 as pellet alarms - unverified |
Byte 33
Emitted as fireStatePercentage. GMG's own cloud API names this field
fireStateProgress, paired with fireState.
It is a four-step progress marker through whatever fire state the grill is
currently in. Across a full instrumented cycle it ran 25 → 50 → 75 → 100
during Startup, hitting 100 at the exact second the grill switched to Running,
then 75 → 50 → 0 through Cool Down, hitting 0 as the fan stopped.
Two readings are ruled out by that data:
- Not a startup-stage index. A stage counter does not run backwards.
- Not a pellet-hopper level. A hopper does not fill during ignition and
empty when the fan stops. One public implementation labels it
hopper_pctand ships it as a pellet gauge; it reads 100% on a hot grill and 0% on a cold one.
What each 25% step physically means is unknown, and is not in the packet. Display the step number; do not invent labels.
Things the hardware does that will surprise you
- Probe writes are silently discarded while the grill is off. Write 203, read back 0. No error, no acknowledgement.
- The grill serves one client at a time. Concurrent conversations lose messages, so all I/O here is serialised behind a lock.
- Short datagrams happen. They are a transport artifact, not data. Parsing one either raises or invents fields; this library retries instead.
- Temperature keeps rising after the fire is cut - roughly 18 °F over a minute in one observed shutdown.
Tests
pip install -e ".[dev]" && pytest
The fixtures are reconstructed - real decoded observations re-encoded at the documented offsets - not captured frames. They prove the parser is self-consistent with observed behaviour, not that the offsets are correct. Offsets are corroborated across four independent implementations.
Preserving a genuine 52-byte capture is the prerequisite for decoding bytes 34-51, and should replace these fixtures when one exists.
Credits
See NOTICE.md. Original integration by jwhitby91; protocol
cross-checked against brandenc40/green-mountain-grill and Christopher
McKay's gmg.
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 gmg_local-0.3.0.tar.gz.
File metadata
- Download URL: gmg_local-0.3.0.tar.gz
- Upload date:
- Size: 21.3 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
6c366fa6bcceffebc6a405a89c085c8ec169122079c953bd3b863798d8728dee
|
|
| MD5 |
6facd79d33a0918b1fcf22b2ae3654cf
|
|
| BLAKE2b-256 |
5f2bc0dc24cbc9b4ff2bd7143990d69eed7250cb2c575bf77f384167e29d86f7
|
Provenance
The following attestation bundles were made for gmg_local-0.3.0.tar.gz:
Publisher:
publish.yml on y3klab/gmg-local
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
gmg_local-0.3.0.tar.gz -
Subject digest:
6c366fa6bcceffebc6a405a89c085c8ec169122079c953bd3b863798d8728dee - Sigstore transparency entry: 2299640474
- Sigstore integration time:
-
Permalink:
y3klab/gmg-local@522aa98653f548a36c13e89341c93bc426e911a6 -
Branch / Tag:
refs/tags/v0.3.0 - Owner: https://github.com/y3klab
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@522aa98653f548a36c13e89341c93bc426e911a6 -
Trigger Event:
release
-
Statement type:
File details
Details for the file gmg_local-0.3.0-py3-none-any.whl.
File metadata
- Download URL: gmg_local-0.3.0-py3-none-any.whl
- Upload date:
- Size: 13.7 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
c6e06d24c6c3b4a1b5234b025e85d4636acefa91a9d73bb9d97b58987869e2a0
|
|
| MD5 |
ff0b21874fc06bcfd9affadba20f7e6c
|
|
| BLAKE2b-256 |
e8a9eab60dc5419b5416367d057c7c8135045935739fe7bcdac76314839df970
|
Provenance
The following attestation bundles were made for gmg_local-0.3.0-py3-none-any.whl:
Publisher:
publish.yml on y3klab/gmg-local
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
gmg_local-0.3.0-py3-none-any.whl -
Subject digest:
c6e06d24c6c3b4a1b5234b025e85d4636acefa91a9d73bb9d97b58987869e2a0 - Sigstore transparency entry: 2299640507
- Sigstore integration time:
-
Permalink:
y3klab/gmg-local@522aa98653f548a36c13e89341c93bc426e911a6 -
Branch / Tag:
refs/tags/v0.3.0 - Owner: https://github.com/y3klab
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@522aa98653f548a36c13e89341c93bc426e911a6 -
Trigger Event:
release
-
Statement type: