Skip to content

feat: optional one-time Telegram notification on first boot - #177

Open
joaosouz4dev wants to merge 1 commit into
memovai:mainfrom
joaosouz4dev:feat/first-boot-telegram-notify
Open

feat: optional one-time Telegram notification on first boot#177
joaosouz4dev wants to merge 1 commit into
memovai:mainfrom
joaosouz4dev:feat/first-boot-telegram-notify

Conversation

@joaosouz4dev

@joaosouz4dev joaosouz4dev commented May 21, 2026

Copy link
Copy Markdown

Summary

Adds a build-time toggle and an idempotent, NVS-guarded notifier that sends a single "MimiClaw booted" message to a configured Telegram admin chat the first time the device comes online after flashing.

Why this is useful:

  • Confirm remotely that the device came back up after a power cycle, OTA, or being relocated to a USB charger / power bank, without having to monitor serial.
  • A self-contained no-op when the feature is disabled or the admin chat is not configured — zero impact on existing users.

How it works

  • MIMI_TG_SEND_FIRST_BOOT in main/mimi_config.h is the master toggle (defaults to 1).
  • MIMI_SECRET_TG_ADMIN_CHAT_ID in mimi_secrets.h holds the destination chat ID (defaults to empty — feature inert).
  • MIMI_NVS_KEY_FIRST_BOOT_DONE lives in the existing MIMI_NVS_TG namespace and is set to 1 after a successful send, making subsequent boots silent.
  • If sending fails (no network, invalid token, etc.) the NVS flag is left unset, so the next boot retries.
  • The call site in app_main is intentionally not wrapped in ESP_ERROR_CHECK and the function always returns ESP_OK, so no failure mode here can block boot.

What's in the diff

  • main/mimi_config.h — new MIMI_TG_SEND_FIRST_BOOT flag, new NVS key, new optional secret fallback.
  • main/mimi_secrets.h.example — new MIMI_SECRET_TG_ADMIN_CHAT_ID slot with inline guidance pointing to @userinfobot.
  • main/channels/telegram/telegram_bot.{h,c}telegram_send_first_boot_notice() (the guarded sender) and telegram_reset_first_boot_flag() (test helper).
  • main/mimi.c — single call after telegram_bot_start() in the boot sequence.
  • main/cli/serial_cli.creset_first_boot CLI command to re-arm the notifier without erasing NVS.
  • README.md — documents the new secret, the toggle, and the CLI command.

No existing call sites or behaviours change. The notifier uses the public telegram_send_message() API and the standard MIMI_NVS_TG namespace pattern already used for the polling offset.

Test plan

  • Build: idf.py fullclean && idf.py build
  • Erase NVS and flash: idf.py -p <PORT> erase-flash flash monitor
  • With MIMI_SECRET_TG_ADMIN_CHAT_ID set and MIMI_TG_SEND_FIRST_BOOT=1: confirm a single Telegram message arrives once WiFi is up
  • mimi> restart — no additional message (idempotent)
  • mimi> reset_first_boot then mimi> restart — message is sent again
  • Set MIMI_TG_SEND_FIRST_BOOT 0, rebuild and erase-flash — no message ever sent
  • Robustness: leave MIMI_SECRET_TG_ADMIN_CHAT_ID empty with the flag enabled — boot continues normally with a single warn log, no crash

Summary by CodeRabbit

  • New Features

    • Optional first-boot Telegram notification sends device boot details (IP address and timestamp) to admin chat on startup; tracked persistently with ability to disable or resend
    • New CLI command to re-arm first-boot notification without full system reset
  • Documentation

    • Updated README documenting first-boot notification configuration options and behavior

Review Change Stack

Add a build-time toggle and an idempotent NVS-guarded notifier that sends
a single "MimiClaw booted" message to a configured Telegram admin chat
on the first boot after flashing. Useful for confirming remotely that a
device came online after a power-cycle, OTA, or relocating it onto a
USB charger / power bank.

Config:
  - mimi_config.h:    MIMI_TG_SEND_FIRST_BOOT (1 to enable, 0 to disable)
  - mimi_secrets.h:   MIMI_SECRET_TG_ADMIN_CHAT_ID (target chat ID)
  - NVS flag:         MIMI_NVS_KEY_FIRST_BOOT_DONE in MIMI_NVS_TG namespace

Behaviour:
  - No-op when the toggle is 0 or the chat ID is empty.
  - Sends once per device lifetime (persisted in NVS). On send failure
    the flag is left unset, so the next boot retries.
  - Never blocks app_main: the call is intentionally not wrapped in
    ESP_ERROR_CHECK and always returns ESP_OK to callers.

A reset_first_boot CLI command clears the NVS flag so the next boot
re-sends the notice without needing a full NVS erase.

README updated with the new secret and CLI command.
@coderabbitai

coderabbitai Bot commented May 21, 2026

Copy link
Copy Markdown
📝 Walkthrough

Walkthrough

This PR adds an optional first-boot Telegram notification feature to the device. When enabled via configuration, the device sends a one-time "booted" message containing its IP address and timestamp to a Telegram admin chat, with persistence tracked via NVS. A new CLI command allows re-arming the notification for testing.

Changes

First-boot Telegram notification feature

Layer / File(s) Summary
Configuration macros and secrets
main/mimi_config.h, main/mimi_secrets.h.example
Build-time macros define the admin chat ID secret, enable/disable toggle (MIMI_TG_SEND_FIRST_BOOT), and NVS key for persistence tracking.
Telegram bot first-boot implementation
main/channels/telegram/telegram_bot.h, main/channels/telegram/telegram_bot.c
Core implementation adds telegram_send_first_boot_notice() to send boot notifications (with IP and timestamp) on first eligible boot, checking NVS and persisting completion state. Also adds telegram_reset_first_boot_flag() to clear the NVS flag and re-arm notification. Includes required headers for Wi-Fi and time retrieval.
CLI command for resetting first-boot flag
main/cli/serial_cli.c
New reset_first_boot command clears the NVS persistence flag so the notification will be re-sent on the next boot, used for testing and maintenance.
Service startup integration
main/mimi.c
Calls the first-boot notification function immediately after Telegram bot initialization during device startup.
Documentation and user guidance
README.md
Documents the new feature configuration, behavior (one-time boot notification with IP and timestamp), NVS persistence, how to disable or reset it, and the new CLI command.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~12 minutes

Suggested labels

enhancement, ready to merge

Suggested reviewers

  • crispyberry

Poem

🐰 A bunny's device now chirps on first wake,
A Telegram whisper with IP to make,
NVS remembers, no spam shall break,
Reset command rearms it for testing's sake! 🚀

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'feat: optional one-time Telegram notification on first boot' directly and precisely describes the main change—adding an optional, idempotent first-boot Telegram notification feature. It is concise, specific, and aligns with the changeset.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@main/channels/telegram/telegram_bot.c`:
- Around line 611-616: The first-boot functions mishandle NVS persistence: in
telegram_send_first_boot_notice() and telegram_reset_first_boot_flag() check and
handle return values from nvs_set_u8 and nvs_commit instead of ignoring
them—verify nvs_set_u8(...) result, call nvs_commit(...) and check its esp_err_t
return, log failures with ESP_LOGI/ESP_LOGE including the error, and propagate
the persistence error as the function's return value (do not override err =
ESP_OK). Make these changes around the symbols telegram_send_first_boot_notice,
telegram_reset_first_boot_flag, nvs_set_u8, nvs_commit, telegram_send_message
and the key MIMI_NVS_KEY_FIRST_BOOT_DONE so that send/ reset only report success
when the NVS commit actually succeeds.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 847b1608-fa2d-478c-bfbd-e80d4d857bb8

📥 Commits

Reviewing files that changed from the base of the PR and between bb10ea0 and a7c1ee9.

📒 Files selected for processing (7)
  • README.md
  • main/channels/telegram/telegram_bot.c
  • main/channels/telegram/telegram_bot.h
  • main/cli/serial_cli.c
  • main/mimi.c
  • main/mimi_config.h
  • main/mimi_secrets.h.example

Comment on lines +611 to +616
esp_err_t send_err = telegram_send_message(chat_id, msg);
if (send_err == ESP_OK) {
if (nvs_set_u8(nvs, MIMI_NVS_KEY_FIRST_BOOT_DONE, 1) == ESP_OK) {
nvs_commit(nvs);
}
ESP_LOGI(TAG, "First-boot notice sent to %s", chat_id);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

FILE="main/channels/telegram/telegram_bot.c"

# Show relevant regions around the referenced lines
sed -n '560,700p' "$FILE" | nl -ba | sed -n '1,200p'

# Extract the full functions if possible (find by signature)
rg -n "telegram_send_first_boot_notice|telegram_reset_first_boot_flag" "$FILE" -n

# Print from each function start to just after the function end (best-effort via awk on braces)
python3 - <<'PY'
import re,sys
path="main/channels/telegram/telegram_bot.c"
with open(path,'r',encoding='utf-8',errors='ignore') as f:
    s=f.read()
def extract(fn):
    # naive extraction: find signature line then take until matching braces
    m=re.search(r'\b%s\s*\(' % re.escape(fn), s)
    if not m:
        print(f"NOT FOUND: {fn}")
        return
    start=s.rfind('\n',0,m.start())+1
    # find first '{' after signature
    i=s.find('{', m.start())
    if i==-1:
        print(f"NO BRACE FOR: {fn}")
        return
    depth=0
    for j in range(i, len(s)):
        if s[j]=='{': depth+=1
        elif s[j]=='}':
            depth-=1
            if depth==0:
                end=j+1
                print(f"\n===== {fn} =====\n")
                print(s[start:end])
                return
    print(f"UNTERMINATED: {fn}")
extract("telegram_send_first_boot_notice")
extract("telegram_reset_first_boot_flag")
PY

Repository: memovai/mimiclaw

Length of output: 103


🏁 Script executed:

#!/bin/bash
set -euo pipefail

FILE="main/channels/telegram/telegram_bot.c"
echo "== Snippet 580-680 =="
sed -n '580,680p' "$FILE" | cat -n

echo
echo "== Snippet around 600-660 =="
sed -n '600,660p' "$FILE" | cat -n

echo
echo "== Functions (best-effort) via python brace matching =="
python3 - <<'PY'
import re
path="main/channels/telegram/telegram_bot.c"
with open(path,'r',encoding='utf-8',errors='ignore') as f:
    s=f.read()

def line_of_index(idx):
    return s[:idx].count('\n') + 1

def extract(fn):
    # match function definition line with return type(s) + name + '('
    m=re.search(r'\b' + re.escape(fn) + r'\s*\(', s)
    if not m:
        print(f"NOT FOUND: {fn}")
        return
    # backtrack to start of line
    start = s.rfind('\n', 0, m.start())
    start = 0 if start==-1 else start+1
    # find first '{' after match
    i=s.find('{', m.start())
    if i==-1:
        print(f"NO BRACE FOR: {fn}")
        return
    depth=0
    for j in range(i, len(s)):
        if s[j]=='{': depth+=1
        elif s[j]=='}':
            depth-=1
            if depth==0:
                end=j+1
                print(f"\n===== {fn} (lines {line_of_index(start)}-{line_of_index(end-1)}) =====\n")
                print(s[start:end])
                return
    print(f"UNTERMINATED: {fn}")

extract("telegram_send_first_boot_notice")
extract("telegram_reset_first_boot_flag")
PY

echo
echo "== Direct search for nvs_commit/nvs_set_u8/nvs_erase_key in this file =="
rg -n "nvs_commit\\(|nvs_set_u8\\(|nvs_erase_key\\(" "$FILE"

Repository: memovai/mimiclaw

Length of output: 6671


Handle NVS set/commit failures for first-boot flag before reporting success.

  • telegram_send_first_boot_notice() (around lines 611-616) commits NVS without checking nvs_commit()’s return value, logs "First-boot notice sent...", and returns ESP_OK even if persistence fails.
  • telegram_reset_first_boot_flag() (around lines 633-640) commits NVS without checking nvs_commit()’s return value and then forces err = ESP_OK, so it can log/return success when the reset wasn’t actually persisted.
Suggested fix
@@
     esp_err_t send_err = telegram_send_message(chat_id, msg);
     if (send_err == ESP_OK) {
-        if (nvs_set_u8(nvs, MIMI_NVS_KEY_FIRST_BOOT_DONE, 1) == ESP_OK) {
-            nvs_commit(nvs);
-        }
-        ESP_LOGI(TAG, "First-boot notice sent to %s", chat_id);
+        esp_err_t persist_err = nvs_set_u8(nvs, MIMI_NVS_KEY_FIRST_BOOT_DONE, 1);
+        if (persist_err == ESP_OK) {
+            persist_err = nvs_commit(nvs);
+        }
+        if (persist_err == ESP_OK) {
+            ESP_LOGI(TAG, "First-boot notice sent to %s", chat_id);
+        } else {
+            ESP_LOGW(TAG, "First-boot notice sent but flag persist failed: %s",
+                     esp_err_to_name(persist_err));
+        }
@@
     err = nvs_erase_key(nvs, MIMI_NVS_KEY_FIRST_BOOT_DONE);
     if (err == ESP_OK || err == ESP_ERR_NVS_NOT_FOUND) {
-        nvs_commit(nvs);
-        err = ESP_OK;
+        esp_err_t commit_err = nvs_commit(nvs);
+        err = (commit_err == ESP_OK) ? ESP_OK : commit_err;
     }
     nvs_close(nvs);
-    ESP_LOGI(TAG, "First-boot flag cleared; next boot will re-send notice");
+    if (err == ESP_OK) {
+        ESP_LOGI(TAG, "First-boot flag cleared; next boot will re-send notice");
+    } else {
+        ESP_LOGW(TAG, "Failed to persist first-boot flag reset: %s", esp_err_to_name(err));
+    }
     return err;
 }

Also propagate the persistence error as the return value from telegram_send_first_boot_notice() (it currently always returns ESP_OK).

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@main/channels/telegram/telegram_bot.c` around lines 611 - 616, The first-boot
functions mishandle NVS persistence: in telegram_send_first_boot_notice() and
telegram_reset_first_boot_flag() check and handle return values from nvs_set_u8
and nvs_commit instead of ignoring them—verify nvs_set_u8(...) result, call
nvs_commit(...) and check its esp_err_t return, log failures with
ESP_LOGI/ESP_LOGE including the error, and propagate the persistence error as
the function's return value (do not override err = ESP_OK). Make these changes
around the symbols telegram_send_first_boot_notice,
telegram_reset_first_boot_flag, nvs_set_u8, nvs_commit, telegram_send_message
and the key MIMI_NVS_KEY_FIRST_BOOT_DONE so that send/ reset only report success
when the NVS commit actually succeeds.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant