Howto: Road Reconnaissance Report for german roads

Important: it’s only for german roads!

Hey there,
i integrated road data from the DWD (German Weather Service) into my OH2 instance. It’s important for me, because i commute a lot. Therefore, data from opendata.dwd.de is retrieved every 60 minutes and parsed. The data format is a very custom one and called “SH10”, it cost me a lot of time to make the regular expression (in a rule) work.

You wanna see a SH10-example?

P912 111 10149 20149 3//// 5//// 70///
222 10206 2//// 30194 4//// 5//// 620/0=

What a beauty. The following screenshot is the more beautiful result:

I have implemented it based on a rule and the HTTP binding and REGEX transformation. Because of the complicated setup it would certainly make sense to develop a binding.

How to do it?

  1. Find the road sensors you are interested: https://www.dwd.de/DE/leistungen/opendata/help/stationen/sws_stations_pdf.pdf?blob=publicationFile&v=5
    Therefore, copy “Kennung” and “GDS-Verzeichnis”, for example “P808” and “FN” for the sensors in Memmingen Süd. Trivia: GDS is the name of the DWD data center, that aggregates the sensor data. In our case, it’s Freimann.

  2. Create items to retrieve the data.
    Important: Replace XX with your “GDS-Verzeichnis”

Group gWeather_DWD_SWS (gWeather)
String Weather_DWD_SWS_XX (gWeather_DWD_SWS) { http="<[https://opendata.dwd.de/weather/weather_reports/road_weather_stations/XX/YSH_XXLATEST:3600000:REGEX((.*))]"}

In our case, the GDS is “FN” for DWD Freimann:

Group gWeather_DWD_SWS (gWeather)
String Weather_DWD_SWS_FN (gWeather_DWD_SWS) { http="<[https://opendata.dwd.de/weather/weather_reports/road_weather_stations/FN/YSH_FNLATEST:3600000:REGEX((.*))]"}
  1. Add data items for every “Kennung” / road sensor. Items have to follow a schema: Weather_DWD_SWS_XX_KK_MeasureType. Remind: XX is your “GDS” as above, KK is your “Kennung”. The following MeasureTypes are allowed:
Number:Temperature Weather_DWD_SWS_FN_P808_TTT "Temperatur [%.1f %unit%]" <temperature> (gWeather_DWD_SWS)
Number:Temperature Weather_DWD_SWS_FN_P808_TTTd "Taupunkt [%.1f %unit%]" <temperature> (gWeather_DWD_SWS)
Number:Length Weather_DWD_SWS_FN_P808_VVVV "Sichtweite [%.0f m]" <zoom> (gWeather_DWD_SWS)
//Number:Angle            Weather_DWD_SWS_FN_P808_DD "Windrichtung [MAP(winddir.map):%s]" (gWeather_DWD_SWS)
//Number:Speed            Weather_DWD_SWS_FN_P808_FF "Windgeschwindigkeit [%.2f m/s]" (gWeather_DWD_SWS)
Number:Dimensionless    Weather_DWD_SWS_FN_P808_W "Niederschlagsart [MAP(weather_dwd_sws_W.map):%s]" <rain> (gWeather_DWD_SWS)
//Number:Dimensionless    Weather_DWD_SWS_FN_P808_RR (gWeather_DWD_SWS)
//Number:Dimensionless    Weather_DWD_SWS_FN_P808_RRK (gWeather_DWD_SWS)
Number:Temperature Weather_DWD_SWS_FN_P808_TTTb "Belagstemperatur [%.1f %unit%]" <temperature> (gWeather_DWD_SWS)
Number:Temperature Weather_DWD_SWS_FN_P808_TTTu "Untergrundtemperatur [%.1f %unit%]" <temperature> (gWeather_DWD_SWS)
Number:Length Weather_DWD_SWS_FN_P808_hh "Wasserfilmdicke [%.0f cm]" (gWeather_DWD_SWS)
Number:Dimensionless Weather_DWD_SWS_FN_P808_hhK "Wasserfilmdickeklasse [%.0f]" (gWeather_DWD_SWS)
Number:Dimensionless Weather_DWD_SWS_FN_P808_m "Hersteller [MAP(weather_dwd_sws_m.map):%s]" (gWeather_DWD_SWS)
Number:Dimensionless Weather_DWD_SWS_FN_P808_F "Funktionszustand [MAP(weather_dwd_sws_F.map):%s]" <qualityofservice> (gWeather_DWD_SWS)
Number:Dimensionless Weather_DWD_SWS_FN_P808_S "Straßenzustand [MAP(weather_dwd_sws_S.map):%s]" <qualityofservice> (gWeather_DWD_SWS)
  1. Create the rule to do the magic
import java.util.regex.Pattern
import java.util.regex.Matcher

rule "DWD Wetterupdate SWS"
when
    Item Weather_DWD_SWS_FN received update //changed
then
    
    // get valid stations from here:
    // https://www.dwd.de/DE/leistungen/opendata/help/stationen/sws_stations_pdf.pdf?__blob=publicationFile&v=5__
    
    /*  Format Description
        https://www.dwd.de/DE/leistungen/opendata/help/schluessel_datenformate/bufr/sws_sh10_code_pdf.pdf?__blob=publicationFile&v=2
        
        Example:

        P912 111 10149 20149 3//// 5//// 70///
        222 10206 2//// 30194 4//// 5//// 620/0=

    */

    val String regexHead = "SH\\/\\/10 DWFN (\\d{10})\\s"
    val String regexData = "(?<Station>\\w\\d{3}) .*?" + 
                            "111 1(?>(?<vTTT>\\d)|\\/)(?>(?<TTT>\\d{3})|\\/\\/\\/) " +
                            "2(?>(?<vTTTd>\\d)|\\/)(?>(?<TTTd>\\d{3})|\\/\\/\\/) " +
                            "3(?>(?<VVVV>\\d{4})|\\/\\/\\/\\/) " +
                            "5(?>(?<DD>\\d{2})|\\/\\/)(?>(?<FF>\\d{2})|\\/\\/)(?>.){0,4} " + 
                            "7(?>(?<W>\\d)|\\/)(?>(?<RR>\\d{2})|\\/\\/)(?>(?<RRK>\\d)|\\/)\\s" + 
                            "\\W*222 1(?>(?<vTTTb>\\d)|\\/)(?>(?<TTTb>\\d{3})|\\/\\/\\/) " +
                            "2(?>(?<vTTTb2>\\d)|\\/)(?>(?<TTTb2>\\d{3})|\\/\\/\\/) " +
                            "3(?>(?<vTTTu>\\d)|\\/)(?>(?<TTTu>\\d{3})|\\/\\/\\/) " +
                            "4(?>(?<vTTTu2>\\d)|\\/)(?>(?<TTTu2>\\d{3})|\\/\\/\\/) " +
                            "5(?>(?<hh>\\d{2})\\/(?<hhK>\\d{1})|\\/\\/\\/\\/) " +
                            "6(?>(?<m>\\d)|\\/)(?>(?<F>\\d{1})|\\/)\\/(?>(?<S>\\d)|\\/)="
    val String swsResult = Weather_DWD_SWS_FN.state.toString

    var Pattern patternHead = Pattern::compile(regexHead, Pattern::MULTILINE)
	var Matcher mHead = patternHead.matcher(swsResult)
    if(mHead.find()) {
		//logInfo("[DWD_SWS]", "Found SH10 Header with Date:" + mHead.group(1))
        swsResult.replace(mHead.group(0),"")    // remove header
        
        var Pattern patternData = Pattern::compile(regexData, Pattern::MULTILINE)
        var Matcher mData = patternData.matcher(swsResult)
        while(mData.find()) {
            val stationName = mData.group("Station")
            //logInfo("[DWD_SWS]", "Found SH10 Data for Station:" + stationName)
            //logInfo("[DWD_SWS]", mData.group(0)) //print complete group for debugging
                    
            var items = gWeather_DWD_SWS.members.filter[i | i.name.contains(stationName)]
            var i = 0
            while ((i=i+1) < items.length) {
                var item = items.get(i)
                var dwdType = item.name.split("_").get(5)
                try {
                    var itemValue = Float::parseFloat(mData.group(dwdType))
                    if(dwdType.contains("TTT")) {
                        // Komma
                        itemValue = Float::parseFloat(mData.group(dwdType).substring(0,2) + "." + mData.group(dwdType).substring(2))
                        // Vorzeichen
                        var pre = Float::parseFloat(mData.group("v" + dwdType))
                        if(pre == 1)
                            itemValue = -itemValue
                    }
                    //logInfo("DWD", "Update Item " + item.name + " " + itemValue)
                    item.sendCommand(itemValue)
                } catch(NullPointerException e) {
                    //logInfo(" ", e.toString)
                }
                
            }
                    
        }
        
   
	}
end
  1. Prettify with maps and a sitemap

weather_dwd_sws_F.map:

-=kein Wert
0=gesamte Messstelle in Ordnung
1=einige oder alle Luftsensoren defekt
2=einige oder alle Straßensensoren defekt 
9=Messstelle teilweise oder ganz defekt

weather_dwd_sws_m.map:

-=kein Wert
1=ANT/Bosch
2=Boschung
3=SSI/Scan (MicKS) 4 Vaisala
5=Vibrometer
6=Malling

weather_dwd_sws_S.map:

-=kein Wert
0=trocken 
1=feucht
2=nass 
3=Reif 
4=Schnee 
5=Eis

weather_dwd_sws_W.map:

-=kein Wert
0=kein Niederschlag
1=bei alten Anlagen: Niederschlag ja bei neuen Anlagen: Regen
2=Schneeregen
3=Schnee

Sitemap (excerpt):

Frame label="Memmingen Süd" {
    Default item=Weather_DWD_SWS_FN_P808_TTT
    Default item=Weather_DWD_SWS_FN_P808_TTTd 
    Default item=Weather_DWD_SWS_FN_P808_VVVV 
    //Default item=Weather_DWD_SWS_FN_P808_DD
    //Default item=Weather_DWD_SWS_FN_P808_FF
    Default item=Weather_DWD_SWS_FN_P808_W 
    //Default item=Weather_DWD_SWS_FN_P808_RR
    //Default item=Weather_DWD_SWS_FN_P808_RRK
    Default item=Weather_DWD_SWS_FN_P808_TTTb 
    Default item=Weather_DWD_SWS_FN_P808_TTTu 
    Default item=Weather_DWD_SWS_FN_P808_hh 
    Default item=Weather_DWD_SWS_FN_P808_hhK 
    Default item=Weather_DWD_SWS_FN_P808_m 
    Default item=Weather_DWD_SWS_FN_P808_F 
    Default item=Weather_DWD_SWS_FN_P808_S 
}

I commented out some MeasureTypes, as not every Kennung/road sensor delivers all data.

Hope, somebody is able to use it. I am currently working on a DWD Forecast Binding. Maybe someone can wrap everything above in another binding :wink:

Cheers,
Klaus

EDIT: replaced rule with a better one.

9 Likes

Is there a map too, where i can see all positions of stations? Many stations have names without a known location for me and i can´t enter all the coordinates into google maps, to see where they are located…

The sensors are installed and supervised by the road administration of the federal states. Unfortunately, every administration offers its own documentation. For Bavaria, you can take a look at the following map layers:

http://geoportal.bayern.de/bayernatlas/?layers=WMS||https://www.baysis.bayern.de/gis/services/wms/DE_BY_SBV_INSPIRE_Viewservice_SWIS_Sensoren/MapServer/WMSServer?

1 Like

Thanks, that helped! Now i´ve a station about 10km near my home on the road to my workplace.

Created a quick and dirty widget for visualization.

Color scheme: green = dry surface, blue = wet surface, red = slippery surface (ice, snow)

Just import from this repo and follow the instructions there: https://github.com/xlrx/habpanel-map-widget
Credits go to Yannick Schaus, who originally created the OpenStreetMap widget.

Important: in settings you are asked to provide an item. Create a Location item for yourself (use the correct naming scheme, see above). Example:

Location  Weather_DWD_SWS_FN_P808_Location     "Location [%2$s°N %3$s°E %1$sm]"

Have fun!