matebot 0.2.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- matebot-0.2.0/.github/workflows/ci.yml +20 -0
- matebot-0.2.0/.github/workflows/docker.yml +35 -0
- matebot-0.2.0/.github/workflows/release.yml +20 -0
- matebot-0.2.0/.gitignore +8 -0
- matebot-0.2.0/AGENTS.md +101 -0
- matebot-0.2.0/CLAUDE.md +2 -0
- matebot-0.2.0/Dockerfile +19 -0
- matebot-0.2.0/LICENSE +21 -0
- matebot-0.2.0/PKG-INFO +232 -0
- matebot-0.2.0/README.md +198 -0
- matebot-0.2.0/docker-compose.example.yml +19 -0
- matebot-0.2.0/docs/screenshots/journal-detail.png +0 -0
- matebot-0.2.0/docs/screenshots/journal-list.png +0 -0
- matebot-0.2.0/docs/screenshots/telegram-chat.png +0 -0
- matebot-0.2.0/flake.lock +27 -0
- matebot-0.2.0/flake.nix +137 -0
- matebot-0.2.0/pyproject.toml +48 -0
- matebot-0.2.0/src/matebot/__init__.py +3 -0
- matebot-0.2.0/src/matebot/bags.py +77 -0
- matebot-0.2.0/src/matebot/cli.py +293 -0
- matebot-0.2.0/src/matebot/commands.py +272 -0
- matebot-0.2.0/src/matebot/config.py +91 -0
- matebot-0.2.0/src/matebot/conversation.py +263 -0
- matebot-0.2.0/src/matebot/digest.py +80 -0
- matebot-0.2.0/src/matebot/hints.py +53 -0
- matebot-0.2.0/src/matebot/machine.py +195 -0
- matebot-0.2.0/src/matebot/messengers/__init__.py +28 -0
- matebot-0.2.0/src/matebot/messengers/base.py +58 -0
- matebot-0.2.0/src/matebot/messengers/discord.py +88 -0
- matebot-0.2.0/src/matebot/messengers/telegram.py +118 -0
- matebot-0.2.0/src/matebot/plot.py +100 -0
- matebot-0.2.0/src/matebot/sitegen.py +119 -0
- matebot-0.2.0/src/matebot/slog.py +225 -0
- matebot-0.2.0/src/matebot/state.py +42 -0
- matebot-0.2.0/src/matebot/sync.py +163 -0
- matebot-0.2.0/src/matebot/watcher.py +137 -0
- matebot-0.2.0/src/matebot/web/app.js +395 -0
- matebot-0.2.0/src/matebot/web/index.html +43 -0
- matebot-0.2.0/src/matebot/web/style.css +166 -0
- matebot-0.2.0/src/matebot/web/vendor/chart.umd.js +20 -0
- matebot-0.2.0/src/matebot/web/vendor/chartjs-plugin-annotation.min.js +7 -0
- matebot-0.2.0/tests/fixtures/000004.slog +0 -0
- matebot-0.2.0/tests/fixtures/status_frames.jsonl +64 -0
- matebot-0.2.0/tests/test_bags.py +47 -0
- matebot-0.2.0/tests/test_cleaning.py +39 -0
- matebot-0.2.0/tests/test_commands.py +211 -0
- matebot-0.2.0/tests/test_conversation.py +170 -0
- matebot-0.2.0/tests/test_digest.py +43 -0
- matebot-0.2.0/tests/test_hints.py +38 -0
- matebot-0.2.0/tests/test_plot.py +17 -0
- matebot-0.2.0/tests/test_sitegen.py +42 -0
- matebot-0.2.0/tests/test_slog.py +80 -0
- matebot-0.2.0/tests/test_sync_retry.py +43 -0
- matebot-0.2.0/tests/test_watcher.py +70 -0
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
name: CI
|
|
2
|
+
on:
|
|
3
|
+
push:
|
|
4
|
+
branches: [main]
|
|
5
|
+
pull_request:
|
|
6
|
+
|
|
7
|
+
jobs:
|
|
8
|
+
test:
|
|
9
|
+
runs-on: ubuntu-latest
|
|
10
|
+
strategy:
|
|
11
|
+
matrix:
|
|
12
|
+
python: ["3.11", "3.12", "3.13"]
|
|
13
|
+
steps:
|
|
14
|
+
- uses: actions/checkout@v4
|
|
15
|
+
- uses: actions/setup-python@v5
|
|
16
|
+
with:
|
|
17
|
+
python-version: ${{ matrix.python }}
|
|
18
|
+
- run: pip install ".[all,dev]"
|
|
19
|
+
- run: ruff check src tests
|
|
20
|
+
- run: pytest -q
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
name: Docker
|
|
2
|
+
on:
|
|
3
|
+
push:
|
|
4
|
+
branches: [main]
|
|
5
|
+
tags: ["v*"]
|
|
6
|
+
|
|
7
|
+
jobs:
|
|
8
|
+
build:
|
|
9
|
+
runs-on: ubuntu-latest
|
|
10
|
+
permissions:
|
|
11
|
+
contents: read
|
|
12
|
+
packages: write
|
|
13
|
+
steps:
|
|
14
|
+
- uses: actions/checkout@v4
|
|
15
|
+
- uses: docker/setup-qemu-action@v3
|
|
16
|
+
- uses: docker/setup-buildx-action@v3
|
|
17
|
+
- uses: docker/login-action@v3
|
|
18
|
+
with:
|
|
19
|
+
registry: ghcr.io
|
|
20
|
+
username: ${{ github.actor }}
|
|
21
|
+
password: ${{ secrets.GITHUB_TOKEN }}
|
|
22
|
+
- uses: docker/metadata-action@v5
|
|
23
|
+
id: meta
|
|
24
|
+
with:
|
|
25
|
+
images: ghcr.io/${{ github.repository_owner }}/matebot
|
|
26
|
+
tags: |
|
|
27
|
+
type=raw,value=latest,enable={{is_default_branch}}
|
|
28
|
+
type=semver,pattern={{version}}
|
|
29
|
+
- uses: docker/build-push-action@v6
|
|
30
|
+
with:
|
|
31
|
+
context: .
|
|
32
|
+
platforms: linux/amd64,linux/arm64
|
|
33
|
+
push: true
|
|
34
|
+
tags: ${{ steps.meta.outputs.tags }}
|
|
35
|
+
labels: ${{ steps.meta.outputs.labels }}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
name: Release
|
|
2
|
+
on:
|
|
3
|
+
push:
|
|
4
|
+
tags: ["v*"]
|
|
5
|
+
|
|
6
|
+
jobs:
|
|
7
|
+
pypi:
|
|
8
|
+
runs-on: ubuntu-latest
|
|
9
|
+
environment: pypi
|
|
10
|
+
permissions:
|
|
11
|
+
id-token: write # trusted publishing (OIDC), no tokens stored
|
|
12
|
+
contents: read
|
|
13
|
+
steps:
|
|
14
|
+
- uses: actions/checkout@v4
|
|
15
|
+
- uses: actions/setup-python@v5
|
|
16
|
+
with:
|
|
17
|
+
python-version: "3.13"
|
|
18
|
+
- run: pip install build
|
|
19
|
+
- run: python -m build
|
|
20
|
+
- uses: pypa/gh-action-pypi-publish@release/v1
|
matebot-0.2.0/.gitignore
ADDED
matebot-0.2.0/AGENTS.md
ADDED
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
# Agent instructions for MATEbot
|
|
2
|
+
|
|
3
|
+
You are likely here because a human asked their AI assistant to help with
|
|
4
|
+
MATEbot — either to **set it up** for their GaggiMate espresso machine or to
|
|
5
|
+
**work on the code**. Both playbooks follow.
|
|
6
|
+
|
|
7
|
+
## What this is
|
|
8
|
+
|
|
9
|
+
MATEbot watches a GaggiMate machine over its local WebSocket API, messages the
|
|
10
|
+
user after every shot (Telegram or Discord) to log rating/beans/grind/doses,
|
|
11
|
+
writes those notes back into the machine's own shot history, and optionally
|
|
12
|
+
archives everything to a git repo with a generated shot-journal site.
|
|
13
|
+
|
|
14
|
+
## Playbook: helping someone set it up
|
|
15
|
+
|
|
16
|
+
Adapt to the user's skill level — ask what they have before prescribing.
|
|
17
|
+
The decision tree:
|
|
18
|
+
|
|
19
|
+
1. **Where will it run?** It needs any always-on device on the same network
|
|
20
|
+
as the machine: a Raspberry Pi, NAS, home server, old laptop. No port
|
|
21
|
+
forwarding, no domain — outbound internet only.
|
|
22
|
+
2. **Pick an install path:**
|
|
23
|
+
- Most users → **Docker**: copy `docker-compose.example.yml`, fill in env
|
|
24
|
+
vars, `docker compose up -d`. Image: `ghcr.io/alexnly/matebot:latest`
|
|
25
|
+
(multi-arch, Raspberry Pi works).
|
|
26
|
+
- Python users → `pip install "matebot[telegram] @ git+https://github.com/AlexNly/MATEbot"`,
|
|
27
|
+
then `matebot run` (systemd unit or `screen` for persistence).
|
|
28
|
+
- NixOS users → flake input + `services.matebot` module (see README).
|
|
29
|
+
3. **Telegram bot** (the fiddly part for novices — walk them through it):
|
|
30
|
+
1. In Telegram, talk to `@BotFather` → `/newbot` → pick a name → copy the
|
|
31
|
+
token (looks like `123456:ABC-...`). Never paste tokens into git.
|
|
32
|
+
2. Have the user send their new bot any message.
|
|
33
|
+
3. Get the chat id: `https://api.telegram.org/bot<TOKEN>/getUpdates` →
|
|
34
|
+
`result[0].message.chat.id`.
|
|
35
|
+
4. `TELEGRAM_BOT_TOKEN` and `TELEGRAM_CHAT_ID` go into the environment
|
|
36
|
+
(compose file, EnvironmentFile, or shell).
|
|
37
|
+
4. **Machine address**: set `MATEBOT_MACHINE_HOST`. Prefer the machine's IP
|
|
38
|
+
over `gaggimate.local` (mDNS is flaky on some networks) and recommend a
|
|
39
|
+
static DHCP lease in the router.
|
|
40
|
+
5. **Verify**: start the bot, run a ≥10 s brew-mode shot (an empty blind
|
|
41
|
+
basket works; water/steam modes do not trigger). The questionnaire should
|
|
42
|
+
arrive within seconds of the shot ending.
|
|
43
|
+
6. **Optional extras**, in ascending effort: journal repo + GitHub Pages
|
|
44
|
+
(README "Publishing your journal"), dial-in hints (on by default), bean
|
|
45
|
+
bag tracking (`/newbag`), smart plug hooks for cold-start `/wake`
|
|
46
|
+
(README "Smart plug cold start").
|
|
47
|
+
|
|
48
|
+
Common failure modes: token/chat id swapped or quoted wrong; bot and machine
|
|
49
|
+
on different networks/VLANs; machine powered off at a dumb power strip;
|
|
50
|
+
shots shorter than `MATEBOT_MIN_SHOT_S` (default 10 s) being ignored;
|
|
51
|
+
firmware older than v1.7 (no binary shot logs — unsupported).
|
|
52
|
+
|
|
53
|
+
## Playbook: working on the code
|
|
54
|
+
|
|
55
|
+
- Layout: `src/matebot/` — `machine.py` (WS/HTTP client), `watcher.py` (shot
|
|
56
|
+
detection), `conversation.py` (questionnaire engine, messenger-agnostic),
|
|
57
|
+
`messengers/` (Telegram/Discord backends), `commands.py` (slash commands),
|
|
58
|
+
`hints.py`, `bags.py`, `digest.py` (features), `sync.py` (git archive),
|
|
59
|
+
`sitegen.py` + `web/` (journal site), `plot.py` (Telegram PNG chart),
|
|
60
|
+
`slog.py` (binary format decoder — start here to understand the data).
|
|
61
|
+
- Dev loop: `pip install -e ".[all,dev]"` then `pytest -q` and
|
|
62
|
+
`ruff check src tests`. Or `nix develop`.
|
|
63
|
+
- Contribution flow: branch → PR → CI must pass (branch protection, no direct
|
|
64
|
+
pushes to `main`) → squash merge. Keep commits/PRs single-purpose.
|
|
65
|
+
- Tests are required for behavior changes. The conversation engine is tested
|
|
66
|
+
against a `FakeMessenger`; the watcher against a recorded frame fixture
|
|
67
|
+
(`tests/fixtures/status_frames.jsonl`); the decoder against a real shot
|
|
68
|
+
(`tests/fixtures/000004.slog` — golden values in `test_slog.py`).
|
|
69
|
+
|
|
70
|
+
### GaggiMate firmware gotchas (hard-won; do not rediscover these)
|
|
71
|
+
|
|
72
|
+
- `req:history:notes:save`: the request-level `id` becomes the notes filename
|
|
73
|
+
verbatim. Older firmware reads the zero-padded name (`000059.json`),
|
|
74
|
+
current nightlies the unpadded one (`59.json`) — MATEbot saves under BOTH.
|
|
75
|
+
`rating` must be a number, every other field a string.
|
|
76
|
+
- `req:change-mode` gets **no response** — never wait for one (fire-and-forget
|
|
77
|
+
via `send_event`; confirmation comes from the status stream).
|
|
78
|
+
- Missing files under `/api/history/` return **HTTP 200 with gzipped SPA
|
|
79
|
+
index.html**, not 404 — validate content (`SHOT` magic / leading `{`) on
|
|
80
|
+
every fetch.
|
|
81
|
+
- The ESP32 drops WebSocket clients whenever its send queue fills (e.g. the
|
|
82
|
+
web UI is open). Reconnect loops must be bulletproof; nothing may
|
|
83
|
+
crash-loop on a dropped socket.
|
|
84
|
+
- New-shot ids come from polling `/api/history/index.bin` (`flags & 1`,
|
|
85
|
+
`id > last_known`) — never from `req:history:list` (heavy on the ESP32)
|
|
86
|
+
and never from timestamps (`startEpoch` is wrong without NTP).
|
|
87
|
+
- Shot files are finalized up to ~1 min after the pump stops (bluetooth
|
|
88
|
+
scale settle time).
|
|
89
|
+
|
|
90
|
+
### License boundary (important)
|
|
91
|
+
|
|
92
|
+
GaggiMate's own source (including its web UI) is **CC BY-NC-SA** — it must
|
|
93
|
+
never be copied into this MIT repo. Matching its *look* (colors, layout,
|
|
94
|
+
axis ranges) via independent implementation is fine and is what
|
|
95
|
+
`web/app.js` and `plot.py` do.
|
|
96
|
+
|
|
97
|
+
### Secrets
|
|
98
|
+
|
|
99
|
+
Tokens live in the environment only. `settings.json` written to data repos
|
|
100
|
+
must keep `wifiSsid`, `wifiPassword`, `apPassword`, `haPassword` redacted
|
|
101
|
+
(`machine.py` does this — keep it that way).
|
matebot-0.2.0/CLAUDE.md
ADDED
matebot-0.2.0/Dockerfile
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
FROM python:3.13-slim
|
|
2
|
+
|
|
3
|
+
# git + ssh are only needed for the optional data-repo sync
|
|
4
|
+
RUN apt-get update \
|
|
5
|
+
&& apt-get install -y --no-install-recommends git openssh-client \
|
|
6
|
+
&& rm -rf /var/lib/apt/lists/*
|
|
7
|
+
|
|
8
|
+
WORKDIR /app
|
|
9
|
+
COPY pyproject.toml README.md LICENSE ./
|
|
10
|
+
COPY src ./src
|
|
11
|
+
RUN pip install --no-cache-dir ".[all]"
|
|
12
|
+
|
|
13
|
+
RUN useradd -m -u 1000 matebot
|
|
14
|
+
USER matebot
|
|
15
|
+
ENV MATEBOT_STATE_DIR=/data
|
|
16
|
+
VOLUME /data
|
|
17
|
+
|
|
18
|
+
ENTRYPOINT ["matebot"]
|
|
19
|
+
CMD ["run"]
|
matebot-0.2.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Alexander Nicolay
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
matebot-0.2.0/PKG-INFO
ADDED
|
@@ -0,0 +1,232 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: matebot
|
|
3
|
+
Version: 0.2.0
|
|
4
|
+
Summary: The proactive companion for GaggiMate espresso machines: post-shot logging bot, .slog decoder, shot-journal site generator
|
|
5
|
+
Project-URL: Homepage, https://github.com/AlexNly/matebot
|
|
6
|
+
Project-URL: Issues, https://github.com/AlexNly/matebot/issues
|
|
7
|
+
Author: Alexander Nicolay
|
|
8
|
+
License-Expression: MIT
|
|
9
|
+
License-File: LICENSE
|
|
10
|
+
Keywords: coffee,discord,espresso,gaggimate,homelab,telegram
|
|
11
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
12
|
+
Classifier: Programming Language :: Python :: 3
|
|
13
|
+
Classifier: Topic :: Home Automation
|
|
14
|
+
Requires-Python: >=3.11
|
|
15
|
+
Requires-Dist: aiohttp>=3.9
|
|
16
|
+
Provides-Extra: all
|
|
17
|
+
Requires-Dist: discord-py>=2.3; extra == 'all'
|
|
18
|
+
Requires-Dist: matplotlib>=3.8; extra == 'all'
|
|
19
|
+
Requires-Dist: matrix-nio>=0.24; extra == 'all'
|
|
20
|
+
Requires-Dist: python-telegram-bot<23,>=21; extra == 'all'
|
|
21
|
+
Provides-Extra: dev
|
|
22
|
+
Requires-Dist: pytest-asyncio>=0.23; extra == 'dev'
|
|
23
|
+
Requires-Dist: pytest>=8; extra == 'dev'
|
|
24
|
+
Requires-Dist: ruff>=0.4; extra == 'dev'
|
|
25
|
+
Provides-Extra: discord
|
|
26
|
+
Requires-Dist: discord-py>=2.3; extra == 'discord'
|
|
27
|
+
Provides-Extra: matrix
|
|
28
|
+
Requires-Dist: matrix-nio>=0.24; extra == 'matrix'
|
|
29
|
+
Provides-Extra: plots
|
|
30
|
+
Requires-Dist: matplotlib>=3.8; extra == 'plots'
|
|
31
|
+
Provides-Extra: telegram
|
|
32
|
+
Requires-Dist: python-telegram-bot<23,>=21; extra == 'telegram'
|
|
33
|
+
Description-Content-Type: text/markdown
|
|
34
|
+
|
|
35
|
+
# matebot
|
|
36
|
+
|
|
37
|
+
A post-shot companion for [GaggiMate](https://gaggimate.eu) espresso machines.
|
|
38
|
+
|
|
39
|
+
I kept forgetting to log my shots. Not for lack of caring — but after every
|
|
40
|
+
shot the ritual was: pull out the phone, open the web UI, find Shot History,
|
|
41
|
+
tap edit, type everything in. Most days that didn't happen, and by 23:47,
|
|
42
|
+
lying in bed, the grind setting of today's best shot was gone for good.
|
|
43
|
+
|
|
44
|
+
matebot turns the workflow around: when a shot finishes, the machine messages
|
|
45
|
+
*you* and asks the few things you'd otherwise forget — rating, taste, beans,
|
|
46
|
+
grind, doses. Thirty seconds of tapping while you sip. The answers are written
|
|
47
|
+
straight back into GaggiMate's own Shot Notes, exactly as if you'd typed them
|
|
48
|
+
into the web UI.
|
|
49
|
+
|
|
50
|
+
<p align="center">
|
|
51
|
+
<img src="docs/screenshots/telegram-chat.png" width="420" alt="matebot asking for a shot rating on Telegram right after the shot finished">
|
|
52
|
+
</p>
|
|
53
|
+
|
|
54
|
+
## What it does
|
|
55
|
+
|
|
56
|
+
- Watches the machine over its WebSocket API and detects finished brew shots
|
|
57
|
+
(backflush/descale/flush runs and anything under 10 s are ignored).
|
|
58
|
+
- Runs a short questionnaire via **Telegram** or **Discord** (Matrix planned),
|
|
59
|
+
with one-tap "same as last shot" defaults for beans, grind and dose.
|
|
60
|
+
- Saves the answers into the machine's shot history — GaggiMate stays the
|
|
61
|
+
source of truth, with or without matebot.
|
|
62
|
+
- Optionally archives every shot (`.slog` + notes), your brew profiles and
|
|
63
|
+
machine settings (credentials redacted) to a git repository after each shot.
|
|
64
|
+
- Generates a static **shot journal** from that archive, ready for GitHub
|
|
65
|
+
Pages. Because the journal lives outside the machine, it survives firmware
|
|
66
|
+
updates and downgrades, a dying SD card, or a water-damaged machine.
|
|
67
|
+
- Ships a standalone `.slog` decoder (`matebot decode shot.slog --csv`).
|
|
68
|
+
- After a sour, bitter or low-rated shot it suggests the next dial-in step
|
|
69
|
+
(grind → ratio → temperature, one variable at a time), following
|
|
70
|
+
[modsmthng's Automatic Pro cheat sheet](https://modsmthng.github.io/Automatic-Pro/)
|
|
71
|
+
— disable with `MATEBOT_HINTS=0`.
|
|
72
|
+
|
|
73
|
+
## The shot journal
|
|
74
|
+
|
|
75
|
+
[Live example](https://alexnly.github.io/GAGGIMATE-0614/) — every shot with
|
|
76
|
+
the familiar combined pressure/flow/temperature chart, phase markers, ratings
|
|
77
|
+
and notes.
|
|
78
|
+
|
|
79
|
+
<p align="center">
|
|
80
|
+
<img src="docs/screenshots/journal-list.png" width="49%" alt="Shot journal list with ratings, ratios and peak pressure">
|
|
81
|
+
<img src="docs/screenshots/journal-detail.png" width="49%" alt="Per-shot detail with combined pressure/flow/temperature chart">
|
|
82
|
+
</p>
|
|
83
|
+
|
|
84
|
+
## Install
|
|
85
|
+
|
|
86
|
+
### Docker
|
|
87
|
+
|
|
88
|
+
```bash
|
|
89
|
+
mkdir matebot && cd matebot
|
|
90
|
+
curl -O https://raw.githubusercontent.com/AlexNly/matebot/main/docker-compose.example.yml
|
|
91
|
+
cp docker-compose.example.yml docker-compose.yml
|
|
92
|
+
# edit: machine host, bot token, chat id
|
|
93
|
+
docker compose up -d
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
Images are multi-arch (`amd64` + `arm64`, so a Raspberry Pi works):
|
|
97
|
+
`ghcr.io/alexnly/matebot:latest`. There is no cloud service behind this —
|
|
98
|
+
the bot needs to run on something in your home network that is always on.
|
|
99
|
+
A Pi Zero 2 W and a USB charger is the whole data center.
|
|
100
|
+
|
|
101
|
+
### pip
|
|
102
|
+
|
|
103
|
+
```bash
|
|
104
|
+
pip install "matebot[telegram] @ git+https://github.com/AlexNly/matebot"
|
|
105
|
+
matebot run
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
### NixOS (flake)
|
|
109
|
+
|
|
110
|
+
```nix
|
|
111
|
+
inputs.matebot.url = "github:AlexNly/matebot";
|
|
112
|
+
|
|
113
|
+
services.matebot = {
|
|
114
|
+
enable = true;
|
|
115
|
+
machineHost = "192.168.1.50";
|
|
116
|
+
environmentFile = "/etc/secrets/matebot"; # TELEGRAM_BOT_TOKEN=... / TELEGRAM_CHAT_ID=...
|
|
117
|
+
dataRepo = "/var/lib/gaggimate-journal"; # optional
|
|
118
|
+
};
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
## Setup
|
|
122
|
+
|
|
123
|
+
1. Telegram: create a bot with [@BotFather](https://t.me/BotFather)
|
|
124
|
+
(`/newbot`), copy the token. Message your bot once, then read your chat id
|
|
125
|
+
from `https://api.telegram.org/bot<TOKEN>/getUpdates`.
|
|
126
|
+
Discord: create an application + bot, invite it to a server, enable the
|
|
127
|
+
*message content* intent, copy the channel id.
|
|
128
|
+
2. Point `MATEBOT_MACHINE_HOST` at your GaggiMate. A DHCP reservation is more
|
|
129
|
+
reliable than `gaggimate.local`.
|
|
130
|
+
3. Pull a shot.
|
|
131
|
+
|
|
132
|
+
## Chat commands
|
|
133
|
+
|
|
134
|
+
Besides the post-shot questionnaire, the bot answers commands (any messenger):
|
|
135
|
+
|
|
136
|
+
```
|
|
137
|
+
/wake turn the machine on — pings you when it's at temperature
|
|
138
|
+
/sleep back to standby
|
|
139
|
+
/status mode, boiler temperature, water level
|
|
140
|
+
/last the last logged shot (with journal link if configured)
|
|
141
|
+
/fix redo the questionnaire for the last shot
|
|
142
|
+
/help list commands
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
## Smart plug cold start (optional)
|
|
146
|
+
|
|
147
|
+
GaggiMate in standby still draws power, so many people cut it at a smart
|
|
148
|
+
plug — which normally kills `/wake`. Give MATEbot the plug's on/off commands
|
|
149
|
+
and `/wake` becomes a true cold start (plug on → wait for the machine to
|
|
150
|
+
boot → brew mode → ready ping), while `/sleep` powers everything down:
|
|
151
|
+
|
|
152
|
+
```bash
|
|
153
|
+
# Tasmota (Nous A1T, Eightree, ...)
|
|
154
|
+
MATEBOT_WAKE_HOOK='curl -sf "http://192.168.1.60/cm?cmnd=Power%20On"'
|
|
155
|
+
MATEBOT_SLEEP_HOOK='curl -sf "http://192.168.1.60/cm?cmnd=Power%20Off"'
|
|
156
|
+
|
|
157
|
+
# Shelly
|
|
158
|
+
MATEBOT_WAKE_HOOK='curl -sf "http://192.168.1.60/relay/0?turn=on"'
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
Any shell command works (Home Assistant webhook, `tinytuya`, zigbee2mqtt…).
|
|
162
|
+
|
|
163
|
+
## Configuration
|
|
164
|
+
|
|
165
|
+
Environment variables, or the same keys in `~/.config/matebot/config.toml`:
|
|
166
|
+
|
|
167
|
+
| Variable | Default | Meaning |
|
|
168
|
+
|---|---|---|
|
|
169
|
+
| `MATEBOT_MACHINE_HOST` | `gaggimate.local` | GaggiMate hostname/IP |
|
|
170
|
+
| `MATEBOT_MESSENGER` | `telegram` | `telegram` or `discord` |
|
|
171
|
+
| `TELEGRAM_BOT_TOKEN` / `TELEGRAM_CHAT_ID` | — | Telegram credentials |
|
|
172
|
+
| `DISCORD_BOT_TOKEN` / `DISCORD_CHANNEL_ID` | — | Discord credentials |
|
|
173
|
+
| `MATEBOT_DATA_REPO` | — | Path to a git clone; enables archive + journal |
|
|
174
|
+
| `MATEBOT_SITE_TITLE` | `Shot Journal` | Title of the generated journal |
|
|
175
|
+
| `MATEBOT_JOURNAL_URL` | — | Public journal URL, used for `/last` deep links |
|
|
176
|
+
| `MATEBOT_HINTS` | `1` | Dial-in hints after sour/bitter/low-rated shots |
|
|
177
|
+
| `MATEBOT_STATE_DIR` | `~/.local/state/matebot` | Bot state (defaults, resume) |
|
|
178
|
+
| `MATEBOT_MIN_SHOT_S` | `10` | Ignore shots shorter than this |
|
|
179
|
+
| `MATEBOT_IGNORE_PROFILES` | `(?i)backflush\|descale\|flush\|clean` | Profile regex to skip |
|
|
180
|
+
|
|
181
|
+
## CLI
|
|
182
|
+
|
|
183
|
+
```
|
|
184
|
+
matebot run # the bot (--replay frames.jsonl --dry-run to test)
|
|
185
|
+
matebot decode SHOT.slog # .slog -> JSON (--csv for CSV)
|
|
186
|
+
matebot sitegen shots/ -o docs/ --title "My Shot Journal"
|
|
187
|
+
matebot sync # one-off journal sync (shots, profiles, settings, site)
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
## Publishing your journal on GitHub Pages
|
|
191
|
+
|
|
192
|
+
1. Create a repo for your data, clone it where matebot runs, set
|
|
193
|
+
`MATEBOT_DATA_REPO` to the clone.
|
|
194
|
+
2. On GitHub: Settings → Pages → Deploy from branch → `main` / `/docs`.
|
|
195
|
+
3. Every shot now updates `https://<you>.github.io/<repo>/`.
|
|
196
|
+
|
|
197
|
+
## How it talks to the machine
|
|
198
|
+
|
|
199
|
+
Local network only — nothing leaves your LAN except the messenger API and
|
|
200
|
+
your own git remote. Requires GaggiMate firmware ≥ v1.7 (binary shot logs).
|
|
201
|
+
|
|
202
|
+
- `ws://<machine>/ws` — `evt:status` for shot detection,
|
|
203
|
+
`req:history:notes:save` for notes, `req:profiles:list` for backup
|
|
204
|
+
- `GET /api/history/index.bin`, `<id>.slog`, `<id>.json` — shot downloads
|
|
205
|
+
- `GET /api/settings` — settings backup; WiFi/AP/Home-Assistant credentials
|
|
206
|
+
are redacted before anything is written to disk
|
|
207
|
+
|
|
208
|
+
The `.slog` v5 binary format (512-byte header, 26-byte samples at 250 ms) is
|
|
209
|
+
documented in [`src/matebot/slog.py`](src/matebot/slog.py).
|
|
210
|
+
|
|
211
|
+
### WhatsApp?
|
|
212
|
+
|
|
213
|
+
There is no reasonable self-hosted WhatsApp bot API. Two workable paths:
|
|
214
|
+
bridge your Telegram/Matrix chat via [mautrix](https://docs.mau.fi/bridges/),
|
|
215
|
+
or Meta's WhatsApp Business Cloud API (requires a business account). Native
|
|
216
|
+
support: contributions welcome.
|
|
217
|
+
|
|
218
|
+
## Credits
|
|
219
|
+
|
|
220
|
+
- [GaggiMate](https://gaggimate.eu) by jniebuhr — the machine controller this
|
|
221
|
+
companion talks to.
|
|
222
|
+
- The journal's shot chart recreates the look of GaggiMate's own web UI
|
|
223
|
+
(independent reimplementation — GaggiMate's UI code is CC BY-NC-SA and none
|
|
224
|
+
of it is copied). Rendered with [Chart.js](https://www.chartjs.org/) and
|
|
225
|
+
chartjs-plugin-annotation (MIT, vendored).
|
|
226
|
+
- The dial-in hints follow the guide from
|
|
227
|
+
[Automatic Pro cheat sheet](https://modsmthng.github.io/Automatic-Pro/) by
|
|
228
|
+
**modsmthng**.
|
|
229
|
+
|
|
230
|
+
## License
|
|
231
|
+
|
|
232
|
+
MIT. Not affiliated with the GaggiMate project.
|
matebot-0.2.0/README.md
ADDED
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
# matebot
|
|
2
|
+
|
|
3
|
+
A post-shot companion for [GaggiMate](https://gaggimate.eu) espresso machines.
|
|
4
|
+
|
|
5
|
+
I kept forgetting to log my shots. Not for lack of caring — but after every
|
|
6
|
+
shot the ritual was: pull out the phone, open the web UI, find Shot History,
|
|
7
|
+
tap edit, type everything in. Most days that didn't happen, and by 23:47,
|
|
8
|
+
lying in bed, the grind setting of today's best shot was gone for good.
|
|
9
|
+
|
|
10
|
+
matebot turns the workflow around: when a shot finishes, the machine messages
|
|
11
|
+
*you* and asks the few things you'd otherwise forget — rating, taste, beans,
|
|
12
|
+
grind, doses. Thirty seconds of tapping while you sip. The answers are written
|
|
13
|
+
straight back into GaggiMate's own Shot Notes, exactly as if you'd typed them
|
|
14
|
+
into the web UI.
|
|
15
|
+
|
|
16
|
+
<p align="center">
|
|
17
|
+
<img src="docs/screenshots/telegram-chat.png" width="420" alt="matebot asking for a shot rating on Telegram right after the shot finished">
|
|
18
|
+
</p>
|
|
19
|
+
|
|
20
|
+
## What it does
|
|
21
|
+
|
|
22
|
+
- Watches the machine over its WebSocket API and detects finished brew shots
|
|
23
|
+
(backflush/descale/flush runs and anything under 10 s are ignored).
|
|
24
|
+
- Runs a short questionnaire via **Telegram** or **Discord** (Matrix planned),
|
|
25
|
+
with one-tap "same as last shot" defaults for beans, grind and dose.
|
|
26
|
+
- Saves the answers into the machine's shot history — GaggiMate stays the
|
|
27
|
+
source of truth, with or without matebot.
|
|
28
|
+
- Optionally archives every shot (`.slog` + notes), your brew profiles and
|
|
29
|
+
machine settings (credentials redacted) to a git repository after each shot.
|
|
30
|
+
- Generates a static **shot journal** from that archive, ready for GitHub
|
|
31
|
+
Pages. Because the journal lives outside the machine, it survives firmware
|
|
32
|
+
updates and downgrades, a dying SD card, or a water-damaged machine.
|
|
33
|
+
- Ships a standalone `.slog` decoder (`matebot decode shot.slog --csv`).
|
|
34
|
+
- After a sour, bitter or low-rated shot it suggests the next dial-in step
|
|
35
|
+
(grind → ratio → temperature, one variable at a time), following
|
|
36
|
+
[modsmthng's Automatic Pro cheat sheet](https://modsmthng.github.io/Automatic-Pro/)
|
|
37
|
+
— disable with `MATEBOT_HINTS=0`.
|
|
38
|
+
|
|
39
|
+
## The shot journal
|
|
40
|
+
|
|
41
|
+
[Live example](https://alexnly.github.io/GAGGIMATE-0614/) — every shot with
|
|
42
|
+
the familiar combined pressure/flow/temperature chart, phase markers, ratings
|
|
43
|
+
and notes.
|
|
44
|
+
|
|
45
|
+
<p align="center">
|
|
46
|
+
<img src="docs/screenshots/journal-list.png" width="49%" alt="Shot journal list with ratings, ratios and peak pressure">
|
|
47
|
+
<img src="docs/screenshots/journal-detail.png" width="49%" alt="Per-shot detail with combined pressure/flow/temperature chart">
|
|
48
|
+
</p>
|
|
49
|
+
|
|
50
|
+
## Install
|
|
51
|
+
|
|
52
|
+
### Docker
|
|
53
|
+
|
|
54
|
+
```bash
|
|
55
|
+
mkdir matebot && cd matebot
|
|
56
|
+
curl -O https://raw.githubusercontent.com/AlexNly/matebot/main/docker-compose.example.yml
|
|
57
|
+
cp docker-compose.example.yml docker-compose.yml
|
|
58
|
+
# edit: machine host, bot token, chat id
|
|
59
|
+
docker compose up -d
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
Images are multi-arch (`amd64` + `arm64`, so a Raspberry Pi works):
|
|
63
|
+
`ghcr.io/alexnly/matebot:latest`. There is no cloud service behind this —
|
|
64
|
+
the bot needs to run on something in your home network that is always on.
|
|
65
|
+
A Pi Zero 2 W and a USB charger is the whole data center.
|
|
66
|
+
|
|
67
|
+
### pip
|
|
68
|
+
|
|
69
|
+
```bash
|
|
70
|
+
pip install "matebot[telegram] @ git+https://github.com/AlexNly/matebot"
|
|
71
|
+
matebot run
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
### NixOS (flake)
|
|
75
|
+
|
|
76
|
+
```nix
|
|
77
|
+
inputs.matebot.url = "github:AlexNly/matebot";
|
|
78
|
+
|
|
79
|
+
services.matebot = {
|
|
80
|
+
enable = true;
|
|
81
|
+
machineHost = "192.168.1.50";
|
|
82
|
+
environmentFile = "/etc/secrets/matebot"; # TELEGRAM_BOT_TOKEN=... / TELEGRAM_CHAT_ID=...
|
|
83
|
+
dataRepo = "/var/lib/gaggimate-journal"; # optional
|
|
84
|
+
};
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
## Setup
|
|
88
|
+
|
|
89
|
+
1. Telegram: create a bot with [@BotFather](https://t.me/BotFather)
|
|
90
|
+
(`/newbot`), copy the token. Message your bot once, then read your chat id
|
|
91
|
+
from `https://api.telegram.org/bot<TOKEN>/getUpdates`.
|
|
92
|
+
Discord: create an application + bot, invite it to a server, enable the
|
|
93
|
+
*message content* intent, copy the channel id.
|
|
94
|
+
2. Point `MATEBOT_MACHINE_HOST` at your GaggiMate. A DHCP reservation is more
|
|
95
|
+
reliable than `gaggimate.local`.
|
|
96
|
+
3. Pull a shot.
|
|
97
|
+
|
|
98
|
+
## Chat commands
|
|
99
|
+
|
|
100
|
+
Besides the post-shot questionnaire, the bot answers commands (any messenger):
|
|
101
|
+
|
|
102
|
+
```
|
|
103
|
+
/wake turn the machine on — pings you when it's at temperature
|
|
104
|
+
/sleep back to standby
|
|
105
|
+
/status mode, boiler temperature, water level
|
|
106
|
+
/last the last logged shot (with journal link if configured)
|
|
107
|
+
/fix redo the questionnaire for the last shot
|
|
108
|
+
/help list commands
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
## Smart plug cold start (optional)
|
|
112
|
+
|
|
113
|
+
GaggiMate in standby still draws power, so many people cut it at a smart
|
|
114
|
+
plug — which normally kills `/wake`. Give MATEbot the plug's on/off commands
|
|
115
|
+
and `/wake` becomes a true cold start (plug on → wait for the machine to
|
|
116
|
+
boot → brew mode → ready ping), while `/sleep` powers everything down:
|
|
117
|
+
|
|
118
|
+
```bash
|
|
119
|
+
# Tasmota (Nous A1T, Eightree, ...)
|
|
120
|
+
MATEBOT_WAKE_HOOK='curl -sf "http://192.168.1.60/cm?cmnd=Power%20On"'
|
|
121
|
+
MATEBOT_SLEEP_HOOK='curl -sf "http://192.168.1.60/cm?cmnd=Power%20Off"'
|
|
122
|
+
|
|
123
|
+
# Shelly
|
|
124
|
+
MATEBOT_WAKE_HOOK='curl -sf "http://192.168.1.60/relay/0?turn=on"'
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
Any shell command works (Home Assistant webhook, `tinytuya`, zigbee2mqtt…).
|
|
128
|
+
|
|
129
|
+
## Configuration
|
|
130
|
+
|
|
131
|
+
Environment variables, or the same keys in `~/.config/matebot/config.toml`:
|
|
132
|
+
|
|
133
|
+
| Variable | Default | Meaning |
|
|
134
|
+
|---|---|---|
|
|
135
|
+
| `MATEBOT_MACHINE_HOST` | `gaggimate.local` | GaggiMate hostname/IP |
|
|
136
|
+
| `MATEBOT_MESSENGER` | `telegram` | `telegram` or `discord` |
|
|
137
|
+
| `TELEGRAM_BOT_TOKEN` / `TELEGRAM_CHAT_ID` | — | Telegram credentials |
|
|
138
|
+
| `DISCORD_BOT_TOKEN` / `DISCORD_CHANNEL_ID` | — | Discord credentials |
|
|
139
|
+
| `MATEBOT_DATA_REPO` | — | Path to a git clone; enables archive + journal |
|
|
140
|
+
| `MATEBOT_SITE_TITLE` | `Shot Journal` | Title of the generated journal |
|
|
141
|
+
| `MATEBOT_JOURNAL_URL` | — | Public journal URL, used for `/last` deep links |
|
|
142
|
+
| `MATEBOT_HINTS` | `1` | Dial-in hints after sour/bitter/low-rated shots |
|
|
143
|
+
| `MATEBOT_STATE_DIR` | `~/.local/state/matebot` | Bot state (defaults, resume) |
|
|
144
|
+
| `MATEBOT_MIN_SHOT_S` | `10` | Ignore shots shorter than this |
|
|
145
|
+
| `MATEBOT_IGNORE_PROFILES` | `(?i)backflush\|descale\|flush\|clean` | Profile regex to skip |
|
|
146
|
+
|
|
147
|
+
## CLI
|
|
148
|
+
|
|
149
|
+
```
|
|
150
|
+
matebot run # the bot (--replay frames.jsonl --dry-run to test)
|
|
151
|
+
matebot decode SHOT.slog # .slog -> JSON (--csv for CSV)
|
|
152
|
+
matebot sitegen shots/ -o docs/ --title "My Shot Journal"
|
|
153
|
+
matebot sync # one-off journal sync (shots, profiles, settings, site)
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
## Publishing your journal on GitHub Pages
|
|
157
|
+
|
|
158
|
+
1. Create a repo for your data, clone it where matebot runs, set
|
|
159
|
+
`MATEBOT_DATA_REPO` to the clone.
|
|
160
|
+
2. On GitHub: Settings → Pages → Deploy from branch → `main` / `/docs`.
|
|
161
|
+
3. Every shot now updates `https://<you>.github.io/<repo>/`.
|
|
162
|
+
|
|
163
|
+
## How it talks to the machine
|
|
164
|
+
|
|
165
|
+
Local network only — nothing leaves your LAN except the messenger API and
|
|
166
|
+
your own git remote. Requires GaggiMate firmware ≥ v1.7 (binary shot logs).
|
|
167
|
+
|
|
168
|
+
- `ws://<machine>/ws` — `evt:status` for shot detection,
|
|
169
|
+
`req:history:notes:save` for notes, `req:profiles:list` for backup
|
|
170
|
+
- `GET /api/history/index.bin`, `<id>.slog`, `<id>.json` — shot downloads
|
|
171
|
+
- `GET /api/settings` — settings backup; WiFi/AP/Home-Assistant credentials
|
|
172
|
+
are redacted before anything is written to disk
|
|
173
|
+
|
|
174
|
+
The `.slog` v5 binary format (512-byte header, 26-byte samples at 250 ms) is
|
|
175
|
+
documented in [`src/matebot/slog.py`](src/matebot/slog.py).
|
|
176
|
+
|
|
177
|
+
### WhatsApp?
|
|
178
|
+
|
|
179
|
+
There is no reasonable self-hosted WhatsApp bot API. Two workable paths:
|
|
180
|
+
bridge your Telegram/Matrix chat via [mautrix](https://docs.mau.fi/bridges/),
|
|
181
|
+
or Meta's WhatsApp Business Cloud API (requires a business account). Native
|
|
182
|
+
support: contributions welcome.
|
|
183
|
+
|
|
184
|
+
## Credits
|
|
185
|
+
|
|
186
|
+
- [GaggiMate](https://gaggimate.eu) by jniebuhr — the machine controller this
|
|
187
|
+
companion talks to.
|
|
188
|
+
- The journal's shot chart recreates the look of GaggiMate's own web UI
|
|
189
|
+
(independent reimplementation — GaggiMate's UI code is CC BY-NC-SA and none
|
|
190
|
+
of it is copied). Rendered with [Chart.js](https://www.chartjs.org/) and
|
|
191
|
+
chartjs-plugin-annotation (MIT, vendored).
|
|
192
|
+
- The dial-in hints follow the guide from
|
|
193
|
+
[Automatic Pro cheat sheet](https://modsmthng.github.io/Automatic-Pro/) by
|
|
194
|
+
**modsmthng**.
|
|
195
|
+
|
|
196
|
+
## License
|
|
197
|
+
|
|
198
|
+
MIT. Not affiliated with the GaggiMate project.
|