»Sensor.Community is a contributors driven global sensor network that creates Open Environmental Data.« That’s what they say about themselves on their website. As far as I know they started with measuring fine dust in Stuttgart and then grew by time.
They are offering instructions to build airrohr, an ESP8266 based sensor platform to connect environmental sensors (BME280, DHT22, …) and fine dust sensors (SDS011, PPD42NS). Sadly this firmware pushes directly to their server, not to openHAB. As I didn’t get their software to boot anyways, I did it the other way round: Use Tasmota-sensors firmware, push data to my MQTT server and let openHAB push it to sensor.community - this is how I have done it.
Prerequisites
Software
- openHAB 2.5 with *.jar file from here put to your openHAB addons folder
- MQTT Server running and openHAB configured to use it
Hardware:
- Airrohr Hardware with Tasmota-sensors firmware flashed
Setup
Tasmota Hardware
Configure Tasmota as usual to connect to your Wifi and push data to your MQTT server
I/O configuration for Tasmota using BME280 and SDS011 sensor connected as in Airrohr instructions:
Template as JSON:
{"NAME":"Airrohr Air Quality","GPIO":[6,148,5,149,101,70,0,0,0,0,0,0,0],"FLAG":0,"BASE":18}
Go to Tasmota console, enter STATUS 4
and look for the value after FlashChipId
- this is an hexadecimal value. Change that to decimal, and you have your Sensor ID to register at https://devices.sensor.community/register
openHAB
replace YOURBROKER with the thing name of your broker
replace TASMOTAID with ID of your tasmota as seen in console
replace YOURSENSORID with the decimal ID of your sensor
tasmota.things
Thing mqtt:topic:tasmota:tasmota_TASMOTAID "Air Quality Balcony" (mqtt:broker:YOURBROKER) {
Channels:
Type switch : Reachable [stateTopic="tele/tasmota_TASMOTAID/LWT", transformationPattern="MAP:tasmota-reachable.map"]
Type string : RestartReason [stateTopic="tele/tasmota_TASMOTAID/INFO3", transformationPattern="JSONPATH:$.RestartReason"]
// old one, have to query it
Type string : Version2 [stateTopic="stat/tasmota_TASMOTAID/STATUS2", transformationPattern="JSONPATH:$.StatusFWR.Version"]
// new one - comes for free at startup
Type string : Version [stateTopic="tele/tasmota_TASMOTAID/INFO1", transformationPattern="JSONPATH:$.Version"]
Type number : RSSI [stateTopic="tele/tasmota_TASMOTAID/STATE", transformationPattern="JSONPATH:$.Wifi.RSSI"]
Type string : WifiDowntime [stateTopic="tele/tasmota_TASMOTAID/STATE", transformationPattern="JSONPATH:$.Wifi.Downtime"]
Type number : LoadAvg [stateTopic="tele/tasmota_TASMOTAID/STATE", transformationPattern="JSONPATH:$.LoadAvg"]
Type number : Uptime [stateTopic="tele/tasmota_TASMOTAID/STATE", transformationPattern="JSONPATH:$.UptimeSec"]
Type string : Result [stateTopic="stat/tasmota_TASMOTAID/RESULT"]
Type number : BME280_Temperature [stateTopic="tele/tasmota_TASMOTAID/SENSOR", transformationPattern="JSONPATH:$.BME280.Temperature"]
Type number : BME280_HumidityRel [stateTopic="tele/tasmota_TASMOTAID/SENSOR", transformationPattern="JSONPATH:$.BME280.Humidity"]
Type number : BME280_DewPoint [stateTopic="tele/tasmota_TASMOTAID/SENSOR", transformationPattern="JSONPATH:$.BME280.DewPoint"]
Type number : BME280_Pressure [stateTopic="tele/tasmota_TASMOTAID/SENSOR", transformationPattern="JSONPATH:$.BME280.Pressure"]
Type number : SDS011_PM25 [stateTopic="tele/tasmota_TASMOTAID/SENSOR", transformationPattern="JSONPATH:$.SDS0X1['PM2.5']"]
Type number : SDS011_PM10 [stateTopic="tele/tasmota_TASMOTAID/SENSOR", transformationPattern="JSONPATH:$.SDS0X1['PM10']"]
}
AirQuality.items
Switch Tasmota_TASMOTAID_Reachable "Reachable" { channel="mqtt:topic:tasmota:tasmota_TASMOTAID:Reachable" }
String Tasmota_TASMOTAID_RestartReason "Restart Reason [%s]" { channel="mqtt:topic:tasmota:tasmota_TASMOTAID:RestartReason" }
String Tasmota_TASMOTAID_Version "Tasmota Version [%s]" { channel="mqtt:topic:tasmota:tasmota_TASMOTAID:Version", channel="mqtt:topic:tasmota:tasmota_TASMOTAID:Version2" }
Number:Dimensionless Tasmota_TASMOTAID_RSSI "RSSI [%d %%]" { channel="mqtt:topic:tasmota:tasmota_TASMOTAID:RSSI" }
String Tasmota_TASMOTAID_WifiDowntime "Wifi Downtime [%s]" { channel="mqtt:topic:tasmota:tasmota_TASMOTAID:WifiDowntime" }
Number:Dimensionless Tasmota_TASMOTAID_LoadAvg "Load [%d %%]" { channel="mqtt:topic:tasmota:tasmota_TASMOTAID:LoadAvg" }
Number:Time Tasmota_TASMOTAID_Uptime "Uptime [%.1f s]" { channel="mqtt:topic:tasmota:tasmota_TASMOTAID:Uptime" }
String Tasmota_TASMOTAID_Result "Result [%s]" { channel="mqtt:topic:tasmota:tasmota_TASMOTAID:Result" }
Number:Temperature BME280_Temperature "Temperature [%.1f °C]" <temperature> { channel="mqtt:topic:tasmota:tasmota_TASMOTAID:BME280_Temperature" }
Number:Dimensionless BME280_HumidityRel "Humidity [%.1f %%]" <humidity> { channel="mqtt:topic:tasmota:tasmota_TASMOTAID:BME280_HumidityRel" }
Number:Temperature BME280_DewPoint "DewPoint [%.1f °C]" <rain> { channel="mqtt:topic:tasmota:tasmota_TASMOTAID:BME280_DewPoint" }
Number:Pressure BME280_Pressure "Air Pressure [%.1f hPa]" <pressure> { channel="mqtt:topic:tasmota:tasmota_TASMOTAID:BME280_Pressure" }
Number:Density SDS011_PM25 "PM 2.5 µm [%.1f µg/m³]" <smoke> { channel="mqtt:topic:tasmota:tasmota_TASMOTAID:SDS011_PM25" }
Number:Density SDS011_PM10 "PM 10 µm [%.1f µg/m³]" <smoke> { channel="mqtt:topic:tasmota:tasmota_TASMOTAID:SDS011_PM10" }
AirQualityToSensorCommunity.rules
var Timer changeTimerBME = null
var Timer changeTimerSDS = null
val String sensorID = "esp8266-YOURSENSORID"
rule "Send BME280 data to sensor.community"
when
Item BME280_Temperature changed or
Item BME280_HumidityRel changed or
Item BME280_Pressure changed
then
if (changeTimerBME === null) {
changeTimerBME = createTimer(now.plusSeconds(1), [ |
var String url = "https://api.sensor.community/v1/push-sensor-data/"
val String contentType = "application/json"
val String content = '{
"sensordatavalues":[
{"value_type":"temperature","value":"' + (BME280_Temperature.state as Number).floatValue + '"},
{"value_type":"humidity","value":"' + (BME280_HumidityRel.state as Number).floatValue + '"},
{"value_type":"pressure","value":"' + (BME280_Pressure.state as Number).floatValue * 100 + '"}' + // API expects pressure in Pa, not in hPa
' ]
}'
val headers = newHashMap("X-Pin" -> "11", "X-Sensor" -> sensorID)
val timeout = 60000
// var String result =
sendHttpPostRequest(url, contentType, content, headers, timeout)
//logInfo("SensorCommunity", "sent »" + content + "«, result »" + result + "«")
changeTimerBME = null // clear for another time
] )
} // else timer already running and we ignore trigger
end
rule "Send SDS011 data to sensor.community"
when
Item SDS011_PM25 changed or
Item SDS011_PM10 changed
then
if (changeTimerSDS === null) {
changeTimerSDS = createTimer(now.plusSeconds(1), [ |
var String url = "https://api.sensor.community/v1/push-sensor-data/"
val String contentType = "application/json"
val String content = '{
"sensordatavalues":[
{"value_type":"P1","value":"' + (SDS011_PM10.state as Number).floatValue + '"},
{"value_type":"P2","value":"' + (SDS011_PM25.state as Number).floatValue + '"}
]
}'
val headers = newHashMap("X-Pin" -> "1", "X-Sensor" -> sensorID)
val timeout = 60000
//var String result =
sendHttpPostRequest(url, contentType, content, headers, timeout)
//logInfo("SensorCommunity", "sent »" + content + "«, result »" + result + "«")
changeTimerSDS = null // clear for another time
] )
} // else timer already running and we ignore trigger
end
(I wrote two rules to later on be able to change refresh time of the sensors individually)
To adapt this to other sensor types, you will have to change the “X-Pin” header to reflect the correct pin - you see this get this during sensor registration.