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
<?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();
?>
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