What I Built
A close friend of mine recently expanded their fishkeeping hobby from a single indoor tank in the living room to a larger outdoor tub on the patio for seasonal breeding.
They quickly hit a wall managing two completely different aquatic ecosystems:
Indoor Tank: Needed strict daily feeding routines and consistent lighting schedules.
Outdoor Tub: Required tracking environmental swings, floating debris checks, and care advice tailored specifically to outdoor setups (like managing sunlight algae vs. indoor LED cycles).
Generic reminder apps felt clunky, and searching online often produced contradictory advice that mixed up indoor and outdoor care. Knowing they wanted a fast, conversational way to track tasks without sending personal schedule data to third-party cloud servers, I built Orcafish—a 100% local, privacy-first AI companion tailored strictly to their dual setups.
Key Features
100% Local AI Privacy: Context-aware fish care advice powered by Ollama (gemma:2b). All LLM inference happens locally on 127.0.0.1:11434—zero user data leaves the machine.
Persistent Daily Reminders: Custom alerts rehydrate directly from SQLite into Telegram's JobQueue on boot, ensuring schedules survive restarts.
Multi-User Isolation: Database schema isolates user profiles, location settings, and task schedules strictly by Telegram chat_id.
Code Snippets
1. Asynchronous Local AI Integration
Calls local Ollama without blocking Telegram's main event loop:
async def ask_gemma(prompt: str, setup_location: str) -> str:
"""Sends context-aware queries to local Gemma model via Ollama."""
loc_display = setup_location.replace("_", " ").title() if setup_location else "General Setup"
system_context = f"You are Orcafish. User setup type: '{loc_display}'."
payload = {
"model": "gemma:2b",
"prompt": f"{system_context}\n\nUser: {prompt}\nOrcafish:",
"stream": False,
}
async with aiohttp.ClientSession() as session:
async with session.post("http://127.0.0.1:11434/api/generate", json=payload, timeout=30) as resp:
if resp.status == 200:
data = await resp.json()
return data.get("response", "Could not process request.")
return f"⚠️ Ollama error status: {resp.status}"
2. Startup Schedule Rehydration
Rebuilds active user alarms in memory upon bot startup:
def load_all_reminders_on_startup(job_queue):
"""Rehydrates active schedules from SQLite into Telegram JobQueue on boot."""
reminders = get_all_reminders_db()
for chat_id, time_str, task_title in reminders:
schedule_reminder_in_queue(job_queue, chat_id, time_str, task_title)
print(f"✅ Reloaded {len(reminders)} active reminders from SQLite database.")
Tech Stack
| Component | Technology |
|---|---|
| Language | Python 3.10+ |
| Bot Framework | python-telegram-bot[job-queue] |
| Local AI Engine | Ollama (gemma:2b) |
| Async Network I/O | aiohttp |
| Database | SQLite (orcafish.db) |
| Timezone |
pytz (Asia/Kolkata) |
Demo
- /start: Interactive setup selector to toggle between Indoor Tank and Outdoor Tub.
-
/remind HH:MM [Task Name]: Schedules persistent daily care notifications (e.g.,
/remind 19:50 Water Level Check). - AI Care Chat: Asks tailored fish-care questions processed instantly by local Gemma.
Code Repository
👉 GitHub Repository: https://github.com/Iraiva63forem
Challenges & What's Next
Challenges I Faced
-
Balancing Local LLM Latency: Optimizing prompt structure for
gemma:2bin Ollama to ensure quick responses on consumer hardware without exceeding light memory footprints. -
Persistent Job State Sync: Ensuring SQLite schedules smoothly rehydrate into Telegram’s async
JobQueueon startup without creating duplicate alarms or losing active user intervals.
What's Next for Orcafish
- Multi-Tank & Tub Profiles: Expand database schemas to support multiple named profiles so users can toggle between specific indoor aquariums and outdoor tubs seamlessly.
- IoT Sensor Integration: Connect ESP32 temperature and pH sensors to feed live water metrics into Orcafish for automated threshold alerts in Telegram.
Top comments (0)