Hardware

The sensors that I use are supplied by many merchants on AliExpress

weatherstation

As the sensors are all shipped with the same default Modbus address, one needs a configuration tool to change the address. (I left the baud rate).

Server

A Raspberry Pi Model A with a RS-485 to USB convertor

RS485 to USB convertor

The sensors use the Modbus protocol over the RS-485 bus

The server polls the sensors every 2 seconds, as some sensors need a small pause before being polled. The maximum windspeed is recorded and the other values averaged over 1 hour. After one hour, the results are sent with curl to the ISP

Temperature and humidity

from XNQ Electric Company Store

Windspeed

FS-M01 30m/s Weather Station Outdoor 3 Cup Anemometer Sensor fom AliExpress

Wind direction

RS-FXJT-N01 Wind Direction Sensor 0-360 degree from Shandong Renke Control Technology Co., Ltd., who was very supportive when I had trouble to configue the Modbus.

Results

The wind energy is expressed as a percentage of the rated power of a turbine with 3 m/s cut-in and a rated speed of 11 m/s.

Temperature and humidity

Winddirection and energy

Wind speed (maximum and average) and energy

Software

Entering the data in the ISP's DB

The server call this every hour. The "weerfile.txt" shows the last results on my home page

<?php
$windspeed=$_POST["speed"];
$maxspeed=$_POST["max"];
$energy=$_POST["watt"];
$winddir=$_POST["dir"];
$humidity=$_POST["hum"];
$temperature=$_POST["temp"];
if (($winddir >360) or ($winddir <0)) die('wind direction out of range '. $winddir);
if (($windspeed >999) or ($windspeed <0)) die('wind speed out of range ' . $windspeed);
if (($temperature >999) or ($temperature < -200)) die('temperature out of range ' . $temperature);
if (($humidity >999) or ($humidity <1)) die('humidity out of range '. $humidity);
$myfile = fopen("../../tmp/weerfile.txt", "w+"); // To disply the current values on the home page
$ws=strval($temperature/10.0) . ',' . strval($humidity/10.0) . ',' . strval($windspeed/10.0) . ',' . strval($maxspeed/10.0) . ',' . strval($winddir) . ',' . strval($energy);
fwrite($myfile,$ws);
fclose($myfile);
$Now = new DateTime('now', new DateTimeZone('Europe/Dublin'));
$mysqli = new mysqli('localhost','userid','password','DBname'); 
if ($mysqli->connect_errno)
{
  echo ("Failed to connect to MySQL: " . $mysqli -> connect_error);
  exit();
}
$insertstring= 'INSERT INTO Measurements (Date,Windspeed,Maxspeed,Energy,Winddirection,Temperature,Humidity) VALUES (\''. $Now->format('Y-m-d H:i:s').'\',\''.$windspeed/10.0.'\',\''.$maxspeed/10.0.'\',\''.$energy.'\',\''.$winddir.'\',\''.$temperature/10.0.'\',\''.$humidity/10.0.'\')';
if (!$mysqli->query($insertstring)) {
    echo( $mysqli->error . '  '. $insertstring . '\n');
}
$mysqli->close();
?>  

Plotting the results

Fairly uninspired phplot code

<?php
include '../../php/phplot/phplot.php';
$now = new DateTime('now', new DateTimeZone('Europe/Dublin'));
$id=$_GET['int'];
$pt=$_GET['type'];
switch($id)
{
    case 'd':
       $interval = DateInterval::createFromDateString('1 day');
       break;    
    case 'w':
       $interval = DateInterval::createFromDateString('1 week');
       break;    
    case 'm':
       $interval = DateInterval::createFromDateString('1 month');
       break;    
    case 'y':
       $interval = DateInterval::createFromDateString('1 year');
       break;    
   default:
       $interval = DateInterval::createFromDateString('1 day');
}

$data = array();
$startdate = $now->sub($interval);
$mysqli = new mysqli('localhost','userid','password','DBname'); 
if ($mysqli->connect_errno)
{
  echo ("Failed to connect to MySQL: " . $mysqli -> connect_error);
  exit();
}
$querystring= 'SELECT * FROM Measurements WHERE Date > \' ' . $startdate->format('Y-m-d') ."'" ;
//$querystring= 'SELECT * FROM Measurements' ;
$measurements = $mysqli->query($querystring) or die( $mysqli->error . '  '. $querystring );
$pts = $measurements ->num_rows;
$i =0;
if ($pts > 0) {
  while ($measurementrow = $measurements ->fetch_assoc() ) {
    switch ($pt) {
        case 't':
	    	$data[] = array('',$i,$measurementrow["Temperature"],$measurementrow["Humidity"]);
		    break;
        case 'w': 
    	    $data[] = array('',$i,$measurementrow["Windspeed"],$measurementrow["Maxspeed"],20*$measurementrow["Energy"]);
    	    break;
        case 'd': 
            $s = sin($measurementrow["Winddirection"]*6.28/360);
            $c = cos($measurementrow["Winddirection"]*6.28/360);
    	    $data[] = array('',10*$measurementrow["Energy"]*$c,10*$measurementrow["Energy"]*$s);
        	break;
        default:
 	    	$data[] = array('',$i,$measurementrow["Temperature"],$measurementrow["Humidity"],$measurementrow["Windspeed"]);
    }
    $i++;
  }
}
$mysqli->close();
$plot = new PHPlot(800,600);
$plot->SetImageBorderType('plain');
$plot->SetDataType('data-data');
$xdiv = 20;
//configure graph
    switch ($pt) {
        case 't':
	        $plot->SetTitle('Temperature and Humidity');
	        $plot->SetLegend(array('C', '%'));
	        $plot->SetPlotType('lines');
            $plot->SetXTickincrement($pts / $xdiv);
            $plot->SetXLabelType('data');
            $plot->SetPrecisionX(3);
            $plot->SetDrawXGrid(True);
            $plot->SetYTickincrement(10);
            $plot->SetYLabelType('data');
            $plot->SetPrecisionY(1);
            $plot->SetDrawYGrid(True);
            $plot->SetPlotAreaWorld(0, -10, $pts, 100);
		    break;
        case 'w': 
    	    $plot->SetTitle('Avg Windspeed, Max Windspeed, Energy');
    	    $plot->SetLegend(array('m/s', 'm/s', '%'));
    	    $plot->SetPlotType('lines');
    	    $plot->SetXTickincrement($pts / $xdiv);
            $plot->SetXLabelType('data');
            $plot->SetPrecisionX(3);
    	    $plot->SetDrawXGrid(True);
            $plot->SetYTickincrement(10);
            $plot->SetYLabelType('data');
            $plot->SetPrecisionY(1);   	    
            $plot->SetDrawYGrid(True);
            $plot->SetPlotAreaWorld(0, 0, $pts, 20);
    	    break;
        case 'd': 
    	    $plot->SetTitle('Direction Energy*10');
    	    $plot->SetPlotType('points');
    	    $plot->SetDrawXGrid(False);
    	    $plot->SetDrawYGrid(False);
    	    $plot->SetPlotAreaWorld(-1, -1, 1, 1);
        	break;
        default:
 	        $plot->SetTitle('Temperature, Humidity, Windspeed');
    	    $plot->SetLegend(array('C', '%', 'm/s'));
 	        $plot->SetPlotType('lines');
            $plot->SetPlotAreaWorld(NULL, -10, NULL, 100);
    }

$plot->SetDataValues($data);
$plot->SetXDataLabelPos('none');
$plot->SetLineWidths(1);
$plot->DrawGraph();    
?>   

Server software

Modbus polling program, using libmodbus

The program writes the arguments for a POST command to stdout. Note that all measurements are *10.

#include <stdio.h>
#include <modbus.h>
#include <errno.h>
#include <unistd.h>

int main(void) {
const int WINDSPEED_ID = 3;
const int WINDDIR_ID = 2;
const int TEMP_ID = 4;
modbus_t *ctx;
uint16_t tab_reg1[2];
uint16_t tab_reg2[2];
uint16_t tab_reg3[3];
uint32_t dirsum=0;
uint32_t speedsum=0;
uint32_t dirsum1=0;
uint32_t speedsum1=0;
int temp1;
int hum1;
float energysum=0;
int i,rc, dircount=0, speedcount=0, speedmax=0, integrate=1798;
ctx = modbus_new_rtu("/dev/ttyUSB0", 4800, 'N', 8, 1);
if (ctx == NULL) {
    fprintf(stderr, "Unable to create the libmodbus context\n");
    return -1;
}
if (modbus_connect(ctx) == -1) {
    fprintf(stderr, "Connection failed: %s\n", modbus_strerror(errno));
    modbus_free(ctx);
    return -1;
}
for (i=0;ispeedmax) speedmax=s;
  if (s>30) { // cut-in speed 3 m/s 
     if (s<110) //plateau 11 m/s
         energysum+=(s-30.0)*(s-30.0)*(s-30.0)/(integrate*512000.0); // ((speed-3)/8)^3 in kJ
     else
          energysum+=1.0/integrate;
  }
  speedsum1+=s;
  speedsum+=s;
  speedcount++;
}
else {
  fprintf(stderr, "Windspeed error %s\n", modbus_strerror(errno));
}
modbus_set_slave(ctx, WINDDIR_ID);
sleep(1);
rc = modbus_read_registers(ctx, 1, 1, tab_reg2);
if (rc== 1) {
   dirsum+=tab_reg2[0];
   dirsum1+=tab_reg2[0];
   dircount++;
   }
else {
   fprintf(stderr, "Wind direction error %s\n", modbus_strerror(errno));
}
if ((integrate %10)==0) {
modbus_set_slave(ctx,TEMP_ID);
sleep(1);
rc=modbus_read_registers(ctx,0,2, tab_reg3);
if (rc==2) {
    fprintf(stderr,"s=%d d=%d t=%2.1f h=%2.1f\n", speedsum1/10, dirsum1/10, tab_reg3[0]/10.0, tab_reg3[1]/10.0);
    speedsum1=0;
    dirsum1=0;
}
}
}
// loop end
if (speedcount>0)
    printf(" \"dummy=0&speed=%d&max=%d&watt=%1.6f&",speedsum/speedcount,speedmax,energysum);
else {
     printf(" \"dummy=6&speed=66&max=666watt=6.66666&");
     fprintf(stderr, "Windspeed missing\n");
}
if (dircount>0)
     printf("dir=%d&",dirsum/dircount);
else {
     printf("dir=66&");
     fprintf(stderr, "Wind direction missing\n");
}
modbus_set_slave(ctx, TEMP_ID);
sleep(1);
rc=modbus_read_registers(ctx,0,2, tab_reg3);
if (rc==2) {
   printf("hum=%d&temp=%d\"\r\n",tab_reg3[0], tab_reg3[1]);
}
else {
    printf("hum=666&temp=666\"\r\n");
     fprintf(stderr, "Temperature error %s\n", modbus_strerror(errno));
}
  modbus_close(ctx);
  modbus_free(ctx);
  return 0;
}
// cc `pkg-config --cflags --libs libmodbus` files

Poll and upload script

#!/bin/bash while true do /home/pi/modbus/meteo|curl -X POST -d @- http://server.insertscript done