Introduction
This tutorial shows how to create an OpenHAB rule that monitors German Weather Service (DWD) weather warnings for multiple locations simultaneously. The rule regularly checks if there are current weather warnings for defined coordinates and logs them in detail.
Prerequisites
JS Scripting Add-on installed
Internet access for DWD API
Rule in Detail
Basic Configuration
The rule uses an array of locations with names and coordinates:
var locations = [
{ name: "Location 1", lat: 48.8302, lon: 9.1212 },
{ name: "Location 2", lat: 48.7758, lon: 9.1829 },
{ name: "Location 3", lat: 47.4911, lon: 11.0958 }
];
Rule Definition
rules.JSRule({
name: "Log Multiple Locations Weather Warnings",
description: "Check and log weather warnings for multiple locations",
triggers: [
triggers.GenericCronTrigger("0 */1 * * * ?"), // Runs every minute
triggers.SystemStartlevelTrigger(100) // Runs on system start
],
// ... additional configuration
});
Core Functionality
HTTP Request for DWD Data:
const url = "https://maps.dwd.de/geoserver/dwd/ows?service=WFS&version=2.0.0&request=GetFeature&typeName=dwd%3AWarnungen_Gemeinden_vereinigt&outputFormat=application%2Fjson";
var response = actions.HTTP.sendHttpGetRequest(url);
Point-in-Polygon Check:
The isPointInPolygon() function checks if a location is within a warning area:
function isPointInPolygon(x, y, polygon) {
var inside = false;
for (var i = 0, j = polygon.length - 1; i < polygon.length; j = i++) {
var xi = polygon[i][0], yi = polygon[i][1];
var xj = polygon[j][0], yj = polygon[j][1];
var intersect = ((yi > y) !== (yj > y))
&& (x < (xj - xi) * (y - yi) / (yj - yi) + xi);
if (intersect) inside = !inside;
}
return inside;
}
Implementation Steps
Step 1: Configure Locations
Adapt the locations array to your needs:
var locations = [
{ name: "Home", lat: YOUR_LATITUDE, lon: YOUR_LONGITUDE },
{ name: "Work", lat: YOUR_LATITUDE, lon: YOUR_LONGITUDE }
];
Step 2: Create the Rule
Add the complete code to your JS-Scripting rule. The rule will automatically:
-
Run on system startup
-
Execute again every minute
-
Check all defined locations
Step 3: Dynamic Location Management
You can add locations at runtime:
// Add new locations
addLocation("Vacation Spot", 47.604, 9.84);
Log Output
The rule generates detailed logs:
-
Info: Number of checked locations and features
-
Warnings: Found weather warnings with details
-
Errors: Query problems
-
Example output:
β οΈ Found 2 warning(s) for Location 1
π Location 1 - Warning 1:
Severity: Moderate
Event: STURMBΓEN
Description: Storm gusts with speeds around 70 km/h
Urgency: Immediate
Certainty: Likely
Customization Options
- Adjust Check Interval
Change the cron trigger for different intervals:
"0 */5 * * * ?" - Every 5 minutes
"0 0 */1 * * ?" - Hourly
- Additional Warning Information
Extend the warning object with additional properties:
var warning = {
name: location.name,
severity: properties.SEVERITY,
// ... existing properties
area: properties.AREA, // New property
sender: properties.SENDER
};
- Update OpenHAB Items
Add item updates to the rule:
// Example for item update
events.sendCommand("WeatherWarning_Active", locationWarnings.length > 0 ? "ON" : "OFF");
Error Handling
The rule includes comprehensive error handling:
-
Timeouts for HTTP queries
-
JSON parsing errors
-
Missing geometry data
-
Coordinate checking errors
-
Benefits of This Solution
-
Multiple Locations: Monitor several positions simultaneously
-
Real-time Checking: Minute-precise updates
-
Robust Geometry Check: Precise polygon verification
-
Detailed Logging: Comprehensive warning information
-
Easy Extensibility: Dynamic addition of more locations
-
Typical Use Cases
-
Monitoring home and work locations
-
Tracking family members at different locations
-
Commercial applications with multiple sites
-
Vacation home or weekend house monitoring
-
Troubleshooting
-
Problem: No warnings found
-
Solution: Check coordinates and ensure they are in Germany
Problem: HTTP errors
Solution: Check internet connection and DWD API accessibility
Problem: Rule doesnβt run
Solution: Verify JS-Scripting installation and cron syntax
This solution provides a robust foundation for monitoring weather warnings at multiple locations and can be easily adapted to individual needs.
Complete Code
// Array of coordinates to check
var locations = [
{ name: "Location 1", lat: 48.8302, lon: 9.1212 },
{ name: "Location 2", lat: 48.7758, lon: 9.1829 },
{ name: "Location 3", lat: 47.4911, lon: 11.0958 },
{ name: "Test Location", lat: 47.604, lon: 9.84 }
];
rules.JSRule({
name: "Log Multiple Locations Weather Warnings",
description: "Check and log weather warnings for multiple locations",
triggers: [
triggers.GenericCronTrigger("0 */1 * * * ?"),
triggers.SystemStartlevelTrigger(100)
],
execute: (data) => {
logger.info("π Fetching DWD weather warnings for " + locations.length + " locations...");
const url = "https://maps.dwd.de/geoserver/dwd/ows?service=WFS&version=2.0.0&request=GetFeature&typeName=dwd%3AWarnungen_Gemeinden_vereinigt&outputFormat=application%2Fjson";
try {
// Use simple HTTP request without options
var response = actions.HTTP.sendHttpGetRequest(url);
if (response === null) {
logger.error("β Failed to fetch warnings: Response is null");
return;
}
var data = JSON.parse(response);
if (!data || !data.features) {
logger.warn("No features found in DWD response");
return;
}
logger.info("Processing " + data.features.length + " warning features for " + locations.length + " locations");
// Check each location
for (var locIndex = 0; locIndex < locations.length; locIndex++) {
var location = locations[locIndex];
var locationWarnings = [];
// Check each feature for this location's coordinates
for (var i = 0; i < data.features.length; i++) {
var feature = data.features[i];
try {
if (feature.geometry && feature.geometry.type === "MultiPolygon") {
var isInside = false;
var multiPolygon = feature.geometry.coordinates;
// Check each polygon in the MultiPolygon
for (var p = 0; p < multiPolygon.length; p++) {
var polygon = multiPolygon[p];
var exteriorRing = polygon[0]; // First ring is exterior
if (isPointInPolygon(location.lon, location.lat, exteriorRing)) {
isInside = true;
break;
}
}
if (isInside) {
var properties = feature.properties || {};
// Use UPPERCASE property names from DWD response
var warning = {
name: location.name,
severity: properties.SEVERITY || 'Unknown',
description: properties.HEADLINE || properties.DESCRIPTION || 'No description',
urgency: properties.URGENCY || 'Unknown',
certainty: properties.CERTAINTY || 'Unknown',
event: properties.EVENT || 'Unknown event',
effective: properties.EFFECTIVE,
expires: properties.EXPIRES,
instruction: properties.INSTRUCTION || '',
onset: properties.ONSET
};
locationWarnings.push(warning);
logger.info("β
Found matching warning for " + location.name + ": " + warning.severity + " - " + warning.event);
}
}
} catch (error) {
logger.warn("Error processing feature " + i + " for " + location.name + ": " + error.message);
}
}
// Log results for this location
if (locationWarnings.length > 0) {
logger.warn("β οΈ Found " + locationWarnings.length + " warning(s) for " + location.name);
for (var w = 0; w < locationWarnings.length; w++) {
var warn = locationWarnings[w];
logger.warn("π " + warn.name + " - Warning " + (w + 1) + ":");
logger.warn(" Severity: " + warn.severity);
logger.warn(" Event: " + warn.event);
logger.warn(" Description: " + warn.description);
logger.warn(" Urgency: " + warn.urgency);
logger.warn(" Certainty: " + warn.certainty);
if (warn.effective) logger.warn(" Effective: " + warn.effective);
if (warn.onset) logger.warn(" Onset: " + warn.onset);
if (warn.expires) logger.warn(" Expires: " + warn.expires);
if (warn.instruction) logger.warn(" Instruction: " + warn.instruction);
logger.warn(" βββββββββββββββββββββββββββ");
}
} else {
logger.info("β No warnings found for " + location.name + " at coordinates " + location.lat + ", " + location.lon);
}
}
} catch (error) {
logger.error("π¨ Error checking weather warnings: " + error.message);
}
},
tags: ["Weather", "Warnings", "Multiple"],
id: "log-multiple-locations-weather-warnings"
});
// Correct point-in-polygon function
function isPointInPolygon(x, y, polygon) {
var inside = false;
for (var i = 0, j = polygon.length - 1; i < polygon.length; j = i++) {
var xi = polygon[i][0], yi = polygon[i][1];
var xj = polygon[j][0], yj = polygon[j][1];
var intersect = ((yi > y) !== (yj > y))
&& (x < (xj - xi) * (y - yi) / (yj - yi) + xi);
if (intersect) inside = !inside;
}
return inside;
}
// Function to add new locations dynamically
function addLocation(name, lat, lon) {
locations.push({ name: name, lat: lat, lon: lon });
logger.info("π Added new location: " + name + " (" + lat + ", " + lon + ")");
}
logger.info("π Multiple locations weather warning rule loaded - monitoring " + locations.length + " locations");