#!/usr/bin/env python3 """ PetKit food watchdog — проверяет уровень корма в фидере. Поведение: - Читает state.food текущего фидера. - Хранит последний известный уровень в state-файле. - Печатает в stdout уведомление ТОЛЬКО при переходе в "мало"(1) или "пусто"(0), либо когда корм снова долили (возврат к полному) — чтобы сбросить тревогу. - При первом запуске просто запоминает состояние, ничего не печатает. Предназначен для крона в watchdog-режиме: - непустой stdout -> уведомление доставляется пользователю - пустой stdout -> тихий прогон, никому ничего не шлём """ import asyncio, os, sys from aiohttp import ClientSession from petkitaio import PetKitClient HERE = os.path.dirname(os.path.abspath(__file__)) STATE_FILE = os.path.join(HERE, ".food_state") # Уровни корма у D4H: 2 = достаточно, 1 = мало, 0 = пусто # (порог можно скорректировать после наблюдения за реальными значениями) ALERT_AT = 1 # алертим когда food <= 1 def load_env(): p = os.path.join(HERE, ".env") if os.path.exists(p): for line in open(p): line = line.strip() if line and not line.startswith("#") and "=" in line: k, _, v = line.partition("=") os.environ.setdefault(k.strip(), v.strip().strip('"').strip("'")) def read_state(): try: with open(STATE_FILE) as f: return int(f.read().strip()) except Exception: return None def write_state(v): with open(STATE_FILE, "w") as f: f.write(str(v)) async def main(): load_env() email = os.environ.get("PETKIT_EMAIL") password = os.environ.get("PETKIT_PASSWORD") region = os.environ.get("PETKIT_REGION", "Russian Federation") if not email or not password: sys.exit("ОШИБКА: нет PETKIT_EMAIL/PETKIT_PASSWORD в .env") s = ClientSession() c = PetKitClient(email, password, s, region) try: await c.login() devs = await c.get_petkit_data() feeders = devs.feeders or {} if not feeders: sys.exit(0) # фидер не виден — молчим fid = list(feeders.keys())[0] f = feeders[fid] food = f.data.get("state", {}).get("food") name = f.data.get("name", fid) if food is None: sys.exit(0) prev = read_state() write_state(food) # Тревога "мало/пусто" — только при переходе именно в это состояние if prev is not None and prev > ALERT_AT and food <= ALERT_AT: state_txt = "ПОЧТИ ПУСТО" if food == 0 else "НА ИСХОДЕ" print( f"\U0001f9d4 Фидер «{name}»: КОРМ {state_txt} (уровень {food}). " f"Проверь и долей корм." ) # Сброс тревоги — корм снова долили elif prev is not None and prev <= ALERT_AT and food > ALERT_AT: print(f"🟢 Фидер «{name}»: корм долили (уровень {food}). Тревога сброшена.") # Остальное — тихо finally: await s.close() if __name__ == "__main__": asyncio.run(main())