This is generated with a Homeyscript that passes markdown data to a Text widget.
I did not write the script myself, it is entirely coded by Gemini. LLM Ai chat (Like Gemini, ChatGPT, Claude) models are incredibly skilled to write Homey scripts, and if you feed it also with the help file it can perfectly generate the markdown for such a widget. In this case I created two Homeyscripts one that generates the 7 day forecast, and one that generates the coming 6 hours.
This was the AI prompt I did for the 7 day forecast:
I am using the Homey app dashboard studio and want to create a 7 day weather widget for “Heidenheim an der Brenz”. Could you help me with the Homeyscript code to generate a horizontal markdown table for the text widget? Inside each cell, I need the day in German, a weather icon like a cloud or sun, the max temperature as just a number with no label, and the min temperature as just a number with no label. This is the help file with the specialised markdown that can be used: [Help file snippet]
In the online help file you can copy all the markdown with the copy button (if you hover above the titles):
I copied this and pasted it below above prompt.
Gemini generated working homeyscript first try, but after some follow up requests (Like adjustable icon size and able to set a location name instead of longitude and latitude) this was the final code for the 7 day forecast:
// --- Configuration ---
const LOCATION = "Heidenheim an der Brenz";
const ICON_SIZE = 50; // Size in pixels
const TIMEZONE = "Europe/Berlin";
try {
const geoUrl = `https://geocoding-api.open-meteo.com/v1/search?name=${encodeURIComponent(LOCATION)}&count=1&format=json`;
const geoResponse = await fetch(geoUrl);
if (!geoResponse.ok) throw new Error(`Geocoding fetch failed: ${geoResponse.status}`);
const geoData = await geoResponse.json();
if (!geoData.results || geoData.results.length === 0) throw new Error(`Location not found: ${LOCATION}`);
const lat = geoData.results[0].latitude;
const lon = geoData.results[0].longitude;
const url = `https://api.open-meteo.com/v1/forecast?latitude=${lat}&longitude=${lon}&daily=weather_code,temperature_2m_max,temperature_2m_min&timezone=${encodeURIComponent(TIMEZONE)}`;
const response = await fetch(url);
if (!response.ok) throw new Error(`Weather fetch failed: ${response.status}`);
const data = await response.json();
const daily = data.daily;
const daysGerman = ["Sonntag", "Montag", "Dienstag", "Mittwoch", "Donnerstag", "Freitag", "Samstag"];
function getWeatherIcon(code) {
let iconName = "cloud";
if (code === 0) iconName = "sun";
if ([1, 2, 3].includes(code)) iconName = "cloud-sun";
if ([45, 48].includes(code)) iconName = "cloud-fog";
if ([51, 53, 55, 56, 57].includes(code)) iconName = "cloud-snow";
if ([61, 63, 65, 66, 67, 80, 81, 82].includes(code)) iconName = "cloud-rain";
if ([71, 73, 75, 77, 85, 86].includes(code)) iconName = "snowflake";
if ([95, 96, 99].includes(code)) iconName = "cloud-lightning";
return `:ph-${iconName}|${ICON_SIZE}:`;
}
let rowDays = "|";
let rowSeps = "|";
let rowIcons = "|";
let rowMax = "|";
let rowMin = "|";
for (let i = 0; i < 7; i++) {
const date = new Date(daily.time[i]);
const dayName = daysGerman[date.getDay()];
const icon = getWeatherIcon(daily.weather_code[i]);
const maxTemp = Math.round(daily.temperature_2m_max[i]) + "°C";
const minTemp = Math.round(daily.temperature_2m_min[i]) + "°C";
rowDays += ` ${dayName} |`;
rowSeps += ` :---: |`;
rowIcons += ` ${icon} |`;
rowMax += ` ${maxTemp} |`;
rowMin += ` ${minTemp} |`;
}
return `${rowDays}\n${rowSeps}\n${rowIcons}\n${rowMax}\n${rowMin}\n`;
} catch (error) {
return `Error: ${error.message}`;
}
And with a similar prompt This was the final Homeyscript code for the 6 hour forecast:
// --- Configuration ---
const LOCATION = "Heidenheim an der Brenz";
const ICON_SIZE = 50; // Size in pixels
const TIMEZONE = "Europe/Berlin";
try {
const geoUrl = `https://geocoding-api.open-meteo.com/v1/search?name=${encodeURIComponent(LOCATION)}&count=1&format=json`;
const geoResponse = await fetch(geoUrl);
if (!geoResponse.ok) throw new Error(`Geocoding fetch failed: ${geoResponse.status}`);
const geoData = await geoResponse.json();
if (!geoData.results || geoData.results.length === 0) throw new Error(`Location not found: ${LOCATION}`);
const lat = geoData.results[0].latitude;
const lon = geoData.results[0].longitude;
const url = `https://api.open-meteo.com/v1/forecast?latitude=${lat}&longitude=${lon}¤t=temperature_2m,relative_humidity_2m,wind_speed_10m,surface_pressure&hourly=temperature_2m,weather_code&timezone=${encodeURIComponent(TIMEZONE)}`;
const response = await fetch(url);
if (!response.ok) throw new Error(`Weather fetch failed: ${response.status}`);
const data = await response.json();
const current = data.current;
const hourly = data.hourly;
function getWeatherIcon(code) {
let iconName = "ph-cloud";
if (code === 0) iconName = "sun";
if ([1, 2, 3].includes(code)) iconName = "cloud-sun";
if ([45, 48].includes(code)) iconName = "cloud-fog";
if ([51, 53, 55, 56, 57].includes(code)) iconName = "cloud-snow";
if ([61, 63, 65, 66, 67, 80, 81, 82].includes(code)) iconName = "cloud-rain";
if ([71, 73, 75, 77, 85, 86].includes(code)) iconName = "snowflake";
if ([95, 96, 99].includes(code)) iconName = "cloud-lightning";
return `:ph-${iconName}|${ICON_SIZE}:`;
}
const now = new Date();
let startIndex = 0;
for (let i = 0; i < hourly.time.length; i++) {
const d = new Date(hourly.time[i]);
if (d.getTime() > now.getTime() - 60 * 60 * 1000) {
startIndex = i;
break;
}
}
const currentTemp = Math.round(current.temperature_2m) + "°C";
const windSpeed = current.wind_speed_10m.toFixed(1);
const humidity = current.relative_humidity_2m;
const pressureMmHg = Math.round(current.surface_pressure * 0.750062);
let markdown = `:ph-thermometer: ${currentTemp} :ph-wind: ${windSpeed} m/s :ph-drop: ${humidity}% :ph-gauge: ${pressureMmHg} mmHg\n---\n`;
let rowHours = "|";
let rowSeps = "|";
let rowIcons = "|";
let rowTemps = "|";
for (let i = 0; i < 6; i++) {
const idx = startIndex + i;
if (idx >= hourly.time.length) break;
const d = new Date(hourly.time[idx]);
const hours = String(d.getHours()).padStart(2, '0');
const minutes = String(d.getMinutes()).padStart(2, '0');
const timeStr = `${hours}:${minutes}`;
const icon = getWeatherIcon(hourly.weather_code[idx]);
const temp = Math.round(hourly.temperature_2m[idx]) + "°C";
rowHours += ` ${timeStr} |`;
rowSeps += ` :---: |`;
rowIcons += ` ${icon} |`;
rowTemps += ` ${temp} |`;
}
return `${markdown}${rowHours}\n${rowSeps}\n${rowIcons}\n${rowTemps}\n`;
} catch (error) {
return `Error: ${error.message}`;
}
Then create the following Homey Flow and add the code to the two Homeyscripts (that returns text tags):
Right click the day and time node and press “execute from here” to let it update the topics.
Save and Open the Dashboard studio editor. (Make sure you are on the latest v1.10.3 version, you can see this in the top of the editor)
You can make the “Text Content” setting dynamic and add the topic in there, but since these are two topics, you can also add inline topics inside the text content:
# :ph-thermometer-simple: 7 Tage Wetterdaten für Heidenheim an der Brenz
---
{{weather7day}}
{{weather5hr}}
I also changed these settings:
And… “Zack die Bohne!” A beautiful weather forecast that completely fits the theme of the dashboard. And when you switch themes it completely blends with the theme:
Nice weather over there!
