The sensors that I use are supplied by many merchants on AliExpress
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).
A Raspberry Pi Model A with a RS-485 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
from XNQ Electric Company Store
FS-M01 30m/s Weather Station Outdoor 3 Cup Anemometer Sensor fom AliExpress
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.
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.
The server call this every hour. The "weerfile.txt" shows the last results on my
$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(); ?>
Fairly uninspired phplot code
<?phpinclude '../../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(); ?>
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