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?
-
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. -
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((.*))]"}
- 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)
- 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
- 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
Cheers,
Klaus
EDIT: replaced rule with a better one.