An Arduino-based smart plant guardian that detects cats, monitors for falls, and reports via Telegram — built for the Arduino MKR IoT Carrier.
Plant Guard protects your plants by detecting cats nearby using a PIR sensor. When a cat is spotted, it sounds a randomised buzzer alarm and flashes LEDs. The onboard IMU accelerometer also detects falls or violent hits, sending Telegram notifications to the owner for both events. Additionally, a set of bot commands allows remote control of the system.
| Component | Details |
|---|---|
| Board | Arduino MKR WiFi 1010 |
| Shield | Arduino MKR IoT Carrier |
| Sensor | PIR motion sensor (connected to pin A6) |
| Connectivity | Wi-Fi (via WiFiNINA) |
Install the following libraries via the Arduino Library Manager or PlatformIO:
| Library | Purpose |
|---|---|
Arduino_MKRIoTCarrier |
Carrier hardware abstraction (display, LEDs, buzzer, IMU, env sensor) |
WiFiNINA |
Wi-Fi connectivity |
UniversalTelegramBot |
Telegram Bot API client |
ArduinoJson |
Required by UniversalTelegramBot, converts JSON API responses into data structures |
ezTime |
NTP time sync with automatic timezone and DST support |
PlantGuard_MKR/
├── include/
│ ├── CatAlarm.h # Alarm logic: buzzer, LEDs, fall detection, display
│ ├── telegramBot.h # Telegram bot: command handling, message formatting
│ ├── testHardware.h # Hardware self-test routines
│ └── secrets.h # ⚠️ Sensitive credentials
├── src/
│ ├── main.cpp # Entry point: setup, loop, PIR reading, system state
│ ├── CatAlarm.cpp
│ ├── telegramBot.cpp
│ └── testHardware.cpp
├── .gitignore
├── LICENSE # MIT license
├── patch_libs.py # Patches incompatible library headers at build time
├── platformio.ini # PlatformIO build configuration
└── README.md
Two of the dependencies ship with bugs or incompatibilities with the SAMD21 hardware. patch_libs.py is a PlatformIO pre-build script that patches the library source files automatically before compilation, so no manual edits are needed.
Patch 1 — ezTime: disable EEPROM cache
ezTime by default tries to cache timezone data in EEPROM. The MKR WiFi 1010 has no EEPROM, which causes the board to stall on boot. The script comments out #define EZTIME_CACHE_EEPROM in ezTime.h to disable this behaviour.
Patch 2 — Arduino_MKRIoTCarrier: fix NONE enum collision
The carrier library declares a NONE = -1 enum in the global namespace, which collides with identifiers from other libraries and causes ODR (One Definition Rule) compilation errors. The script renames it to SHANE_NONE via regex to resolve the conflict.
Both patches are idempotent — running them multiple times has no effect. They are triggered automatically by PlatformIO at the start of every build via env.AddPreAction("compile", ...), so they also handle cases where libraries are re-downloaded mid-project.
Create a secrets.h file in the include/ folder with the following content:
#pragma once
#define SSID_WIFI "your_wifi_ssid"
#define PASSWORD_WIFI "your_wifi_password"
#define BOT_TOKEN "your_telegram_bot_token"
#define USER_ID "your_telegram_chat_id"
⚠️ This file must stay on device, and it shouldn't be add to git or pushed on a public repo. In order to do so,src/secrets.his already listed in.gitignore.
To obtain a bot token, talk to @BotFather on Telegram.
To find your chat ID, talk to @userinfobot.
| Command | Description |
|---|---|
/help |
Show all available commands |
/active_guard |
Activate the plant guard system |
/stop_guard |
Deactivate the plant guard system |
/test |
Run all hardware self-tests |
/plant_health |
Get current temperature and humidity readings with timestamp |
/time_zone <offset> |
Set timezone as UTC offset (e.g. /time_zone 2 for UTC+2) |
The bot includes a security layer: only messages from the configured USER_ID are processed; all others are silently rejected.
IMU squared magnitude is checked every loop (A = x²+y²+z², resting ≈ 1.0 g²):
| Condition | Threshold | Meaning |
|---|---|---|
| Freefall | A < 0.15 g² |
Plant airborne |
| Impact | A > 4.0 g² |
Plant hit or landed |
On detection → two Telegram alerts, alarm triggers, isArduinoActive = false. Reactivation requires manual /active_guard.
Non-blocking — all timing via millis(), no delay(). Beep frequency, duration and gap are randomized each cycle.
LED state:
- 🟢 Green (static) — idle, monitoring active
- 🟡 Yellow (blinking 300 ms) — alarm active
- 🟢 Green (static) — alarm done, back to monitoring
catDetectedis passed by reference —trigger(false)resets it directly, no extra logic inmain.cpp.
Arduino has no real-time clock — every time it boots, its internal clock resets to zero. To solve this, the system syncs with an NTP server on boot: a public internet server that provides the exact current time (accurate to milliseconds, sourced from atomic clocks).
The library used is ezTime:
waitForSync(); // blocks until UTC time is obtained from pool.ntp.orgOnce synced, the board knows the exact UTC time. The timezone is then set manually by the user via Telegram: on every boot the bot sends a message showing the current UTC time and asking the user to set their local offset:
🕒 Current Time (UTC): 14:32
Please set your timezone using: /time_zone <hours>, e.g. /time_zone 2
The /time_zone command accepts any UTC offset between -12 and +14 and applies it immediately using a POSIX string:
// /time_zone 2 → UTC-2 (ezTime uses reversed sign convention)
_tz->setPosix("UTC-2");events() is called every loop iteration to keep the internal clock aligned with the NTP server, correcting any drift over time.
All timestamps in Telegram messages and /plant_health reports reflect the user-configured local time.
The /test command runs run_all_hardware_tests(), which sequentially tests:
| Test | Type | What it checks |
|---|---|---|
| Display | Non-atomic | Fills screen blue, then clears |
| Temperature/Humidity | Atomic | Reads ENV sensor, validates range (-20 °C to 80 °C) |
| Gyroscope | Atomic | Verifies IMU is available and readable |
| LEDs | Non-atomic | Briefly lights all 5 LEDs white |
| Buzzer | Non-atomic | Emits two short beeps |
Total test delay is ~650 ms, safely within the 1 s bot polling interval, so no messages are missed during testing.
- Wire the PIR sensor signal pin to
A6on the MKR board - Install all required libraries (including
ezTime) - Create
secrets.hwith your credentials - Run
patch_libs.pyif required (fixes incompatible library headers) - Compile and upload via PlatformIO
- Open the Serial Monitor at
9600 baudto follow boot and system logs - Reply to the bot's startup message with
/time_zone <offset>to set your local time - Message your bot on Telegram with
/active_guardto start monitoring