Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions defaultmodules/weather/current.njk
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,12 @@
{% endif %}
</span>
{% endif %}
{% if config.showSunshineHours and current.sunshineHours is defined and current.sunshineHours is not none %}
<span class="sunshine-hours">
<span class="wi dimmed wi-day-sunny"></span>
{{ current.sunshineHours | unit("sunshine") }}
</span>
{% endif %}
{% if config.showUVIndex %}
<div class="align-right bright uv-index">
<div class="wi dimmed wi-hot"></div>
Expand Down
8 changes: 8 additions & 0 deletions defaultmodules/weather/forecast.njk
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,14 @@
{% if config.showPrecipitationProbability %}
<div class="align-right bright precipitation-prob" role="cell">{{ f.precipitationProbability | unit('precip', '%') }}</div>
{% endif %}
{% if config.showSunshineHours %}
<div class="align-right dimmed sunshine-hours" role="cell">
{% if f.sunshineHours is defined and f.sunshineHours is not none %}
{{ f.sunshineHours | unit("sunshine") }}
<span class="wi dimmed weathericon wi-day-sunny"></span>
{% endif %}
</div>
{% endif %}
</div>
{% set currentStep = currentStep + 1 %}
{% endfor %}
Expand Down
9 changes: 7 additions & 2 deletions defaultmodules/weather/providers/openmeteo.js
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,8 @@ class OpenMeteoProvider extends WeatherProvider {
"winddirection_10m_dominant",
"shortwave_radiation_sum",
"uv_index_max",
"et0_fao_evapotranspiration"
"et0_fao_evapotranspiration",
"sunshine_duration"
];

constructor (config) {
Expand Down Expand Up @@ -402,6 +403,9 @@ class OpenMeteoProvider extends WeatherProvider {
if (today.temperature_2m_max !== undefined) {
current.maxTemperature = today.temperature_2m_max;
}
if (today.sunshine_duration != null) {
current.sunshineHours = today.sunshine_duration / 3600;
}
}

return current;
Expand All @@ -422,7 +426,8 @@ class OpenMeteoProvider extends WeatherProvider {
snow: weather.snowfall_sum != null ? parseFloat(weather.snowfall_sum * 10) : null,
precipitationAmount: weather.precipitation_sum != null ? parseFloat(weather.precipitation_sum) : null,
precipitationProbability: weather.precipitation_hours != null ? parseFloat(weather.precipitation_hours * 100 / 24) : null,
uvIndex: weather.uv_index_max != null ? parseFloat(weather.uv_index_max) : null
uvIndex: weather.uv_index_max != null ? parseFloat(weather.uv_index_max) : null,
sunshineHours: weather.sunshine_duration != null ? weather.sunshine_duration / 3600 : null
}));
}

Expand Down
9 changes: 7 additions & 2 deletions defaultmodules/weather/weather.css
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
.weather .weather-hourly {
display: grid;
width: max-content;
grid-template-columns: repeat(7, max-content);
grid-template-columns: repeat(8, max-content);
}

.weather .weather-forecast-row,
Expand Down Expand Up @@ -43,7 +43,8 @@
.weather .precipitation-amount,
.weather .precipitation-prob,
.weather .humidity-hourly,
.weather .uv-index {
.weather .uv-index,
.weather .sunshine-hours {
padding: 0;
}

Expand Down Expand Up @@ -79,6 +80,10 @@
order: var(--weather-forecast-precipitation-prob-order, 7);
}

.weather .weather-forecast .sunshine-hours {
order: var(--weather-forecast-sunshine-hours-order, 8);
}

.weather .weather-hourly .time {
order: var(--weather-hourly-time-order, 1);
white-space: nowrap;
Expand Down
3 changes: 3 additions & 0 deletions defaultmodules/weather/weather.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ Module.register("weather", {
showPrecipitationProbability: false,
showUVIndex: false,
showSun: true,
showSunshineHours: false,
showWindDirection: true,
showWindDirectionAsArrow: false,
degreeLabel: false,
Expand Down Expand Up @@ -361,6 +362,8 @@ Module.register("weather", {
}
} else if (type === "humidity") {
formattedValue = `${value}%`;
} else if (type === "sunshine") {
formattedValue = value === null || isNaN(value) ? "" : `${Math.round(value)} h`;
} else if (type === "wind") {
formattedValue = WeatherUtils.convertWind(value, this.config.windUnits);
}
Expand Down
48 changes: 48 additions & 0 deletions tests/configs/modules/weather/sunshine_hours.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
const config = {
address: "0.0.0.0",
ipWhitelist: [],
timeFormat: 12,

modules: [
{
module: "weather",
position: "bottom_bar",
config: {
lat: 48.14,
lon: 11.58,
weatherProvider: "openweathermap",
apiKey: "test-api-key",
showSunshineHours: true
}
},
{
module: "weather",
position: "bottom_bar",
config: {
type: "forecast",
lat: 48.14,
lon: 11.58,
weatherProvider: "openweathermap",
apiKey: "test-api-key",
showSunshineHours: true
}
},
{
module: "weather",
position: "bottom_bar",
classes: "sunshine-disabled",
config: {
type: "forecast",
lat: 48.14,
lon: 11.58,
weatherProvider: "openweathermap",
apiKey: "test-api-key"
}
}
]
};

/*************** DO NOT EDIT THE LINE BELOW ***************/
if (typeof module !== "undefined") {
module.exports = config;
}
44 changes: 44 additions & 0 deletions tests/e2e/modules/weather_sunshine_spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
const { expect } = require("playwright/test");
const helpers = require("../helpers/global-setup");

describe("Weather module: sunshine hours", () => {
let page;

beforeAll(async () => {
await helpers.startApplication("tests/configs/modules/weather/sunshine_hours.js");
await helpers.getDocument();
page = helpers.getPage();
// eslint-disable-next-line playwright/no-wait-for-selector
await page.waitForSelector(".weather", { timeout: 5000 });

await page.evaluate(() => {
const now = Date.now();
const day = 24 * 60 * 60 * 1000;
for (const module of MM.getModules().filter((m) => m.name === "weather")) {
const data = module.config.type === "current"
? { date: now, temperature: 18, windSpeed: 3, sunshineHours: 4.6 }
: [0, 1].map((index) => ({ date: now + index * day, minTemperature: 8, maxTemperature: 18, weatherType: "day-sunny", sunshineHours: index ? null : 7.2 }));
module.socketNotificationReceived("WEATHER_DATA", { instanceId: module.instanceId, type: module.config.type, data });
}
});
});

afterAll(async () => {
await helpers.stopApplication();
});

it("should show today's sunshine hours in the current weather", async () => {
await expect(page.locator(".weather .normal.medium .sunshine-hours")).toHaveText("5 h");
});

it("should show rounded sunshine hours in the forecast", async () => {
const rows = page.locator(".weather:not(.sunshine-disabled) .weather-forecast-row");
await expect(rows.nth(0).locator(".sunshine-hours")).toHaveText("7 h");
await expect(rows.nth(1).locator(".sunshine-hours")).toHaveText("");
});

it("should not show sunshine hours unless enabled", async () => {
await expect(page.locator(".weather.sunshine-disabled .weather-forecast-row").first()).toBeVisible();
await expect(page.locator(".weather.sunshine-disabled .sunshine-hours")).toHaveCount(0);
});
});
28 changes: 28 additions & 0 deletions tests/unit/modules/default/weather/providers/openmeteo_spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -252,6 +252,34 @@ describe("OpenMeteoProvider", () => {
// precipitation_sum has value 0.0 in mock data
expect(result[0].precipitationAmount).toBe(0.0);
});

it("should convert sunshine duration to hours", async () => {
const provider = new OpenMeteoProvider({
lat: 48.14,
lon: 11.58,
type: "forecast"
});

const dataPromise = new Promise((resolve) => {
provider.setCallbacks(resolve, vi.fn());
});

const sunshineData = structuredClone(forecastData);
sunshineData.daily.sunshine_duration = sunshineData.daily.time.map((_, index) => index * 3600);
server.use(
http.get("https://api.open-meteo.com/v1/forecast*", () => {
return HttpResponse.json(sunshineData);
})
);

await provider.initialize();
provider.start();

const result = await dataPromise;

expect(result[0].sunshineHours).toBe(0);
expect(result[3].sunshineHours).toBe(3);
});
});

describe("Error Handling", () => {
Expand Down
Loading