Supercharge your morning: AI Daily Planner with Gemini & Homey

Hi everyone,

Most of our smart homes are great at being reactive—turning on a light when motion is detected or showing us a dashboard full of isolated sensor values. But what happens when you feed your specific Homey data into an AI? Your smart home becomes truly proactive.

By combining our calendar events, live weather data, and device states (like EV battery levels or power consumption) and feeding them to an LLM, we can bridge the gap between raw data and actual daily context. Instead of looking at five different apps every morning, the AI connects the dots and gives you a single, highly actionable briefing.

My Setup: I wrote a HomeyScript that gathers today’s calendar events and strictly filtered sensor data, then sends a customized prompt to the Gemini 2.5 Flash API (which has a generous free tier). I don’t run this on a fixed timer. Instead, I use a physical smart button (or a virtual switch in the Homey app) to trigger the script when I’m actually ready for it. The script processes the data and saves the AI’s response to a Homey text variable. From there, a simple Homey Flow takes over: It sends the text directly to my WhatsApp and triggers my Amazon Echo speaker to read the briefing out loud while I grab my morning coffee.

The Result: Instead of a robotic “Battery 48%, Rain 5mm, Meeting at 9 AM,” the output actually makes sense of the data. It sounds more like this: “Your main focus today is your Q3 Planning Meeting at 9 AM. Since it’s raining outside, I recommend your indoor workout instead of the bike. Your EV battery is at 48%, so don’t forget to plug it in tonight after work. Also, your 3D printer is currently off, so it’s safe to start a new long print.”

The Potential: This template is just the beginning. You can easily adapt the logic guidelines in the script to fit your own life. You could have it remind kids of specific chores based on the day of the week, suggest different activities based on the weather, or optimize your heavy appliance usage based on solar power and your work schedule. The possibilities are endless when the AI actually understands your daily context.

Here is the fully anonymized script. You just need to insert your Gemini API key, your private iCal link, and adapt the device keywords to match your Homey setup.

// Configuration
const GEMINI_API_KEY = '[YOUR_GEMINI_API_KEY_HERE]';
const MODEL_NAME = 'gemini-2.5-flash'; 
const ICAL_URL = '[YOUR_PRIVATE_ICAL_URL_HERE]';

// We only search for partial names (ignores incorrect spaces)
// Users: Adapt these to match your actual Homey device names!
const TRACKED_KEYWORDS = ['car', '3D-printer', 'Outdoor Temperature', 'Rain Gauge', 'Indoor', 'Weather Station'];

const MIN_COOLDOWN_MS = 2 * 60 * 1000; 
const MAX_REQUESTS_PER_DAY = 1450;     

async function checkFreeTierLimits() {
    const today = new Date().toISOString().split('T')[0];
    let usage = global.get('geminiFreeTierUsage') || { date: today, count: 0, lastRun: 0 };
    if (usage.date !== today) usage = { date: today, count: 0, lastRun: 0 };

    const msPassed = Date.now() - usage.lastRun;
    if (msPassed < MIN_COOLDOWN_MS) {
        throw new Error(`Burst protection active. (Wait ${Math.ceil((MIN_COOLDOWN_MS - msPassed) / 1000)}s)`);
    }
    if (usage.count >= MAX_REQUESTS_PER_DAY) {
        throw new Error(`Daily limit reached.`);
    }
    return usage;
}

// Improved iCal parser for multi-day events
async function fetchCalendarEvents(url) {
    if (!url || url === '') return "No iCal URL configured.";
    try {
        const res = await fetch(url);
        const text = await res.text();
        
        // Current date in YYYYMMDD (local timezone)
        const today = new Date();
        const yyyy = today.getFullYear();
        const mm = String(today.getMonth() + 1).padStart(2, '0');
        const dd = String(today.getDate()).padStart(2, '0');
        const todayYYYYMMDD = `${yyyy}${mm}${dd}`;
        
        const events = [];
        const regex = /BEGIN:VEVENT([\s\S]*?)END:VEVENT/g;
        let match;
        
        while ((match = regex.exec(text)) !== null) {
            const block = match[1];
            const summary = block.match(/SUMMARY:(.*)/)?.[1]?.trim() || 'No Title';
            const dtstart = block.match(/DTSTART(?:;.*)?:(.*)/)?.[1]?.trim();
            const dtend = block.match(/DTEND(?:;.*)?:(.*)/)?.[1]?.trim() || dtstart;
            
            if (dtstart && dtend) {
                const startDay = dtstart.slice(0, 8);
                const endDay = dtend.slice(0, 8);
                
                // Is TODAY within the event period?
                if (todayYYYYMMDD >= startDay && todayYYYYMMDD <= endDay) {
                    events.push(`- ${summary}`);
                }
            }
        }
        return events.length > 0 ? events.join("\n") : "No events found for today.";
    } catch (e) {
        return `Error downloading calendar: ${e.message}`;
    }
}

// Strict data filter for sensors
async function getCleanSensorData() {
    const devices = await Homey.devices.getDevices();
    let sensorOutput = [];

    // Allowed base metrics (everything else like Odometer is blocked)
    const allowedCaps = ['measure_temperature', 'measure_power', 'measure_battery', 'battery', 'measure_rain', 'meter_rain'];

    for (const d of Object.values(devices)) {
        const isTracked = TRACKED_KEYWORDS.some(kw => d.name.includes(kw));
        
        if (isTracked) {
            let caps = [];
            for (const cap in d.capabilitiesObj) {
                if (!allowedCaps.includes(cap)) continue;

                // Do not send battery levels from small living room sensors to the AI!
                if ((cap === 'measure_battery' || cap === 'battery') && !d.name.includes('car')) continue;

                const val = d.capabilitiesObj[cap]?.value;
                if (val !== null && val !== undefined) {
                    const name = cap.replace('measure_', '').replace('meter_', '');
                    caps.push(`${name}: ${val}`);
                }
            }
            if (caps.length > 0) {
                sensorOutput.push(`[${d.name.trim()}]: ${caps.join(', ')}`);
            }
        }
    }
    return sensorOutput.length > 0 ? sensorOutput.join('\n') : "No sensor data.";
}

async function run() {
    try {
        const currentUsage = await checkFreeTierLimits();
        const calendarData = await fetchCalendarEvents(ICAL_URL);
        const sensorData = await getCleanSensorData();
        
        const today = new Date();
        const dateOptions = { weekday: 'long', year: 'numeric', month: 'long', day: 'numeric' };
        // Output date in English format
        const currentDateStr = today.toLocaleDateString('en-US', dateOptions);

        // Debug logs for the Homey console
        console.log("=== FILTERED DATA FOR AI ===");
        console.log("CALENDAR TODAY:\n" + calendarData);
        console.log("SENSORS:\n" + sensorData);
        console.log("============================");

        const promptText = `
        Create a highly analytical, directly actionable daily briefing.
        Date: ${currentDateStr}.
        Target audience: [YOUR_PROFESSION] in [YOUR_CITY].
        
        CALENDAR (Active today):
        ${calendarData}
        
        SENSORS:
        ${sensorData}
        
        TASK: 
        Derive direct insights and actionable recommendations from the data. Do not just list values!
        
        LOGIC GUIDELINES:
        1. Daily focus: Summarize the calendar events in ONE sentence. What is on the agenda today?
        2. Mobility: Car. If "Workshop" is in the calendar, DO NOT mention the charge level (irrelevant). Otherwise: Only prompt to charge if the battery is below 50%.
        3. Maker: If the 3D-printer consumes < 5W, it is off. Only mention it if it is active (> 5W) OR if you suggest a 3D printing project that fits the calendar/weather.
        4. Sports/Garden: Correlate the outdoor temperature and rain! 
           - Rain > 0mm: Recommend [YOUR_INDOOR_SPORT]. 
           - Dry & above 10°C: Recommend [YOUR_OUTDOOR_SPORT]. 
        
        FORMAT (Mandatory):
        - No Markdown special characters (no asterisks).
        - Use hyphens (-) for bullet points.
        - Tone: Cool, direct, analytical. No rambling.
        
        STRUCTURE:
        - Daily focus
        - Mobility
        - Maker & Tech
        - Sports & Balance
        `;

        const url = `[https://generativelanguage.googleapis.com/v1beta/models/$](https://generativelanguage.googleapis.com/v1beta/models/$){MODEL_NAME}:generateContent?key=${GEMINI_API_KEY}`;
        
        const response = await fetch(url, {
            method: 'POST',
            headers: { 'Content-Type': 'application/json' },
            body: JSON.stringify({ contents: [{ parts: [{ text: promptText }] }] })
        });

        if (!response.ok) throw new Error(`HTTP Error: ${response.status}`);

        const data = await response.json();
        if (data.error) throw new Error(`API Error: ${data.error.message}`);
        
        const advice = data.candidates[0].content.parts[0].text.trim();

        // Make sure you have created a text variable named "DailyPlanner" in your Homey Logic!
        await tag("DailyPlanner", advice);
        
        currentUsage.count += 1;
        currentUsage.lastRun = Date.now();
        global.set('geminiFreeTierUsage', currentUsage);
        
        return advice;

    } catch (e) {
        console.error(e.message);
        const errorMsg = `Analysis error: ${e.message}`;
        await tag("DailyPlanner", errorMsg);
        return errorMsg;
    }
}

return await run();

Let me know what you guys think or how you adapt this for your own daily routines!