How to Integrate LoRa sensor to Weather Underground

This is short tutorial how to send data from sensor connected to The Things Network via LoRaWAN to Weather Underground. I will use Scidrom air quality sensor whic I developed for students in local school. Just a quick reminder the “AQ” sensor detects PM10, PM2,5 particles, air pressure, temperature and humidity and solar radiation in visible in UV spectrum. More details are available in older post.

Data source (via LoRa/TTN

The sensor is sending data via LoRa network to TTN. Payload is sent on different channels. Each channel is reserved for different parameter set. The payload decoder is self explanatory:

  var decoded = {};
		
   if (port === 0x21) // PM Sensor
   {
     decoded.PM2_5 = bytes[2]<<8 | bytes[1];
     decoded.PM10 = bytes[4]<<8 | bytes[3];
   }

if (port === 0x24) // SHT31
   {
     decoded.SHT_Temp = (bytes[4]<<24 | bytes[3]<<16 | bytes[2]<<8 | bytes[1]) / 1000;
     decoded.SHT_RH = (bytes[8]<<24 | bytes[7]<<16 | bytes[6]<<8 | bytes[5]) / 1000;
   }

	
if (port === 0x25) // BME280
   {
		 decoded.BME280_Temp = bytesToFloat(bytes[4]<<24 | bytes[3]<<16 | bytes[2]<<8 | bytes[1]);	 
     decoded.BME280_RH = bytesToFloat(bytes[8]<<24 | bytes[7]<<16 | bytes[6]<<8 | bytes[5]);
     decoded.BME280_PRESS = bytesToFloat(bytes[12]<<24 | bytes[11]<<16 | bytes[10]<<8 | bytes[9]);
   }

if (port === 0x26) // Battery
  {
    decoded.BATvoltage = bytesToFloat(bytes[4]<<24 | bytes[3]<<16 | bytes[2]<<8 | bytes[1]);
    decoded.BATcurrent = bytesToFloat(bytes[8]<<24 | bytes[7]<<16 | bytes[6]<<8 | bytes[5]);
    decoded.BATrsoc =    bytesToFloat(bytes[12]<<24 | bytes[11]<<16 | bytes[10]<<8 | bytes[9]);
    decoded.BATtemperature = bytesToFloat(bytes[16]<<24 | bytes[15]<<16 | bytes[14]<<8 | bytes[13]);
    decoded.BATcharging = bytes[17];
  }
  
if (port === 0x27) // VEML6030
   {
     decoded.als = bytes[2]<<8 | bytes[1];
     decoded.white = bytes[4]<<8 | bytes[3];
   }

if (port === 0x28) // VEML6075
  {
    decoded.UVA = bytesToFloat(bytes[4]<<24 | bytes[3]<<16 | bytes[2]<<8 | bytes[1]);
    decoded.UVB = bytesToFloat(bytes[8]<<24 | bytes[7]<<16 | bytes[6]<<8 | bytes[5]);
    decoded.UVINDEX = bytesToFloat(bytes[12]<<24 | bytes[11]<<16 | bytes[10]<<8 | bytes[9]);
  }  
  
  decoded.channel = bytes[0];
  return decoded;

the decoded payload is then sent via HTTP integration to application web server running php. It acts as simple forwarder to the WU.

Adding HTTP integration

In the TTN console I added HTTP integration to my web server:

  1. Go to your TTN console and select your application
  2. Select Integrations, Add, HTTP integration and fill in the form:
  • Select default access key
  • type URL of your web server (e.g. https://my.web.server/send2wu.php
  • Method is "POST"
  • Type some value of the Authorization header, e.g. "mysecretkey123"
  • Click Save and you are done here

Register your weather station on WU

After registering on WU, go to the "My Devices" tab and select "Add new device" then "Personal Weather Station" and fill the form:

  • Enter Device location (either with map or address)
  • Give some device name
  • Device hardware: select "other"
  • Leave other blank,
  • accept privacy and click "Next"

The WU will give you station ID and station key. Remember both and repeat above for all other sensors in the wild you might placed.

PHP script forwarding the data to WU

Create the file on your web server with the same name as you defined in the properties of the http integration (send2wu.php in example above).

The data (JSON) sent from TTN is first get from the POST and next decoded int the array:

$postdata = file_get_contents('php://input');
$decoded = json_decode($postdata, true);

The content of the array $decoded depends on the parameter sent from the TTN. With the decoder (from p. "Data source" above) for port 37 (0x25) the array looks something like this:

Array ( [app_id] => nmpmaq [dev_id] => nmpmaq-000ffff0f10fe484 [hardware_serial] => 000FFFF0F10FE484 [port] => 37 [counter] => 47536 [payload_raw] => JWavnEGwfLtCaKrARw== [payload_fields] => Array ( [BME280_PRESS] => 98644.8125 [BME280_RH] => 93.743530273438 [BME280_Temp] => 19.585643768311 [channel] => 37 ) [metadata] => Array ( [time] => 2019-07-29T21:24:59.908383053Z [frequency] => 868.3 [modulation] => LORA [data_rate] => SF7BW125 [coding_rate] => 4/5 [gateways] => Array ( [0] => Array ( [gtw_id] => s54mtb-nm2 [gtw_trusted] => 1 [timestamp] => 2688289267 [time] => 2019-07-29T21:24:59Z [channel] => 1 [rssi] => -54 [snr] => 11 [rf_chain] => 1 [latitude] => 45.801853 [longitude] => 15.178975 [altitude] => 225 ) ) ) [downlink_url] => https://integrations.thethingsnetwork.org/ttn-eu/api/v2/down/nmpmaq/weather_under?key=ttn-account-v2-someverysecretkeynottopepublished )

Now it's time to get some data from the array and construct the URL for the Weather Underground...

First get some general info about the device:

$app_id = $decoded["app_id"]; 
$dev_id = $decoded["dev_id"]; 

$port = $decoded["port"];
$counter = $decoded["counter"];

... and actual fields with data:

$payload_fields = $decoded["payload_fields"];

Currently I have only few sensors in the field and for such small quantity, I don't want to complicate too much with some device registration and all necessary security issues etc... Nevertheless this tutorial is just to show how to upload the sensor data from TTN to WU. Therefor I simply put all currently registered devices into one "case":

switch ($dev_id)
    {
        case "nmpmaq-000ffff0f10fe481" : $pass="pass123"; $id="IWS123"; break;  
        case "nmpmaq-000ffff0f10fe482" : $pass="pass124"; $id="IWS124"; break;  
        case "nmpmaq-000ffff0f10fe483" : $pass="pass125"; $id="IWS125"; break;  
        case "nmpmaq-000ffff0f10fe484" : $pass="pass126"; $id="IWS126"; break;  
        case "nmpmaq-000ffff0f10fe485" : $pass="pass127"; $id="IWS127"; break;  
        case "nmpmaq-000ffff0f10fe486" : $pass="pass128"; $id="IWS128"; break;  
    }

... where $pass is the "Key" are WU station Key and ID (respectively). See above the paragraph describing WU registration process.

Now it's time to finally construct the proper URL for WU to be happy:

    if ($pass!="")  
    {
        $url = "http://weatherstation.wunderground.com/weatherstation/updateweatherstation.php?ID=".$id."&PASSWORD=".$pass."&dateutc=now";
        switch ($port)
        {
           case 0x21 : // PM particles
               $pm10 = round($payload_fields["PM10"]);
               $pm2_5 = round($payload_fields["PM2_5"]);
               $url .= "&AqPM2.5=".$pm2_5;
               $url .= "&AqPM10=".$pm10;
            break;
               
           case 0x24 : // SHT31
               $temp = $payload_fields["SHT_Temp"]; $tf = round($temp * 1.80 + 32 , 1) ; // payload in deg. C, convert to deg.F
               $rh = round($payload_fields["SHT_RH"]);
               $url .= "&tempf=".$tf;
               $url .= "&humidity=".$rh;
            break;
            
           case 0x25 : // BME280
               $temp = $payload_fields["BME280_Temp"]; $tf1 = round($temp * 1.80 + 32 , 1) ; // convert to deg.F
               $p = $payload_fields["BME280_PRESS"];  round ( $pinch = ($p / 100) * 0.02953 , 5); // Convert from Pascals to inH2O
               $url .= "&temp2f=".$tf1;
               $url .= "&baromin=".$pinch;
            break;
            
           case 0x27 : // ALS
               $als = $payload_fields["als"]; $wpsqm = round ( $als * 0.0079, 3); // approx. Lux to W/sqm for sunlight
               $url .= "&solarradiation=".$wpsqm;
            break;
            
           case 0x28 : // UV
               $uv = round($payload_fields["UVINDEX"],1); 
               $url .= "&UV=".$uv;
            break;
            
            
               
        }
        

And finally, send the URL to WU server:

    $url .= "&softwaretype=ScidromWX";     
$fg = file_get_contents($url);

The result in variable $fg should be "success" if everything went well.

Two devices are currently online:

Idrija

Novo mesto

Update:

It seems the WU requires all parameter each time URL is sent. I updated the script in such way the parameters are stored to file and updated when new readout arrives. All parameters (old values and updated values) are sent each time new (partial) update is received.

    
    $pm10 = 0.0;
    $pm2_5 = 0.0;
    $temp = 0.0;
    $temp1 = 0.0;
    $rh = 0;
    $p = 0;
    $als = 0;
    $uv = 0;
    
    $filename = $dev_id.".txt";
    
    if (!file_exists($filename)) {

      $fp = fopen($filename, 'w');
      
      fprintf($fp, "%f;%f;%f;%f;%f;%f;%f;%f",$pm10, $pm2_5, $temp, $temp1, $rh, $p, $als, $uv);
      
      fclose($fp);

    }
    
    
    
    if ($pass!="")  
    {
        // read old values from file for specific sensor
        $fp = fopen($filename, 'r');
        fscanf($fp, "%f;%f;%f;%f;%f;%f;%f;%f",$pm10, $pm2_5, $temp, $temp1, $rh, $p, $als, $uv);
        fclose($fp);
                
        // Add new values (if any)
        switch ($port)
        {
           case 0x21 : // PM
               $pm10 = $payload_fields["PM10"];
               $pm2_5 = $payload_fields["PM2_5"];
            break;
               
           case 0x24 : // SHT31
               $temp = $payload_fields["SHT_Temp"]; 
               $rh = $payload_fields["SHT_RH"];
            break;
            
           case 0x25 : // BME280
               $temp1 = $payload_fields["BME280_Temp"]; 
               $p = $payload_fields["BME280_PRESS"];  
            break;
            
           case 0x27 : // ALS
               $als = $payload_fields["als"]; 
            break;
            
           case 0x28 : // UV
               $uv = $payload_fields["UVINDEX"];
            break;
        }
    
            // calc WU values 
        
        $pm10o = round($pm10);
        $pm2_5o = round($pm2_5);
        $tfo = round($temp * 1.80 + 32 , 1) ; 
        $rho = round($rh);
        $tf1o = round($temp1 * 1.80 + 32 , 1) ; 
        $pincho = round (($p / 100) * 0.02953 , 5);
        $wpsqmo = round ( $als * 0.0079, 3); // approx. Lux to W/sqm
        $uvo = round($uv,1); 

        $url = "http://weatherstation.wunderground.com/weatherstation/updateweatherstation.php?ID=".$id."&PASSWORD=".$pass."&dateutc=now";
        $url .= "&AqPM2.5=".$pm2_5o;
        $url .= "&AqPM10=".$pm10o;
        $url .= "&tempf=".$tfo;
        $url .= "&humidity=".$rho;
        $url .= "&temp2f=".$tf1o;
        $url .= "&baromin=".$pincho;
        $url .= "&solarradiation=".$wpsqmo;
        $url .= "&UV=".$uvo;
        $url .= "&softwaretype=ScidromWX";
        
        $fg = file_get_contents($url);
        
        // Write values back for next use
        $fp = fopen($filename, 'w');
        fprintf($fp, "%f;%f;%f;%f;%f;%f;%f;%f",$pm10, $pm2_5, $temp, $temp1, $rh, $p, $als, $uv);
        fclose($fp);
    
    }

Leave a Reply