/*********************************************************************************
*
* weather_station is a weatherstation build around the SparkFun weather meter
* It can measure wind speed , wind gust , wind direction , rain fall , temperature ,
* humidity and air pressure and has an RS - 485 ModBus interface for your convenience .
*
* LED on Arduino gives status :
*
* ON : Booting
* BLINK : I2C ERROR
* FLASH : Heartbeat
*
* Copyright ( C ) 2023 M . T . Konstapel https : //meezenest.nl/mees
*
* This file is part of weather_station
*
* weather_station is free software : you can redistribute it and / or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation , either version 3 of the License , or
* ( at your option ) any later version .
*
* weather_station is distributed in the hope that it will be useful ,
* but WITHOUT ANY WARRANTY ; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE . See the
* GNU General Public License for more details .
*
* You should have received a copy of the GNU General Public License
* along with weather_station . If not , see < https : //www.gnu.org/licenses/>.
*
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
# include <ModbusSerial.h>
# include "SparkFun_Weather_Meter_Kit_Arduino_Library.h"
//I2C
# include <Wire.h>
# include "i2c.h"
//Temperature and humidity sensor
# include "i2c_SI7021.h"
SI7021 si7021 ;
// Pressure sensor
# include "i2c_BMP280.h"
BMP280 bmp280 ;
/**************************/
/* Configurable variables */
/**************************/
// Sparkfun weather station
int windDirectionPin = A0 ;
int windSpeedPin = 2 ;
int rainfallPin = 3 ;
// RS485 driver
# define RS485_RE 11 // Tight to RS485_DE and must be configured as an input to prevent a short circuit
# define RS485_DE 12
// Used Pins
const int TxenPin = RS485_DE ; // -1 disables the feature, change that if you are using an RS485 driver, this pin would be connected to the DE and /RE pins of the driver.
// ModBus address
const byte SlaveId = 14 ;
/* Modbus Registers Offsets (0-9999)
*
* 30000 : Weater station ID ( 0x5758 )
* 30001 : Wind direction ( degrees )
* 30002 : Wind speed ( average over 10 minutes in km / h )
* 30003 : Wind gust ( peak wind speed in the last 10 minutes in km / h )
* 30004 : Temperature ( degrees Celcius )
* 30005 : Rain last hour ( l / m2 )
* 30006 : Rain last 24 hours ( l / m2 )
* 30007 : Rain since midnight ( l / m2 )
* 30008 : Humidity ( percent )
* 30009 : Barometric pressure ( hPa )
*
*/
const int SensorIDIreg = 0 ;
const int SensorWindDirectionIreg = 1 ;
const int SensorWindSpeedIreg = 2 ;
const int SensorWindGustIreg = 3 ;
const int SensorTemperatureIreg = 4 ;
const int SensorRainIreg = 5 ;
const int SensorRainLast24Ireg = 6 ;
const int SensorRainSinceMidnightIreg = 7 ;
const int SensorHumidityIreg = 8 ;
const int SensorPressureIreg = 9 ;
// RS-485 serial port
# define MySerial Serial // define serial port used, Serial most of the time, or Serial1, Serial2 ... if available
const unsigned long Baudrate = 9600 ;
/******************************/
/* END Configurable variables */
/******************************/
// Create an instance of the weather meter kit
SFEWeatherMeterKit weatherMeterKit ( windDirectionPin , windSpeedPin , rainfallPin ) ;
// ModbusSerial object
ModbusSerial mb ( MySerial , SlaveId , TxenPin ) ;
unsigned long ts ;
unsigned long HourTimer ;
int WindGustData1 [ 30 ] ;
unsigned char WindGustData1Counter = 0 ;
int WindGustData2 [ 10 ] ;
unsigned char WindGustData2Counter = 0 ;
int WindAverageData1 [ 30 ] ;
unsigned char WindAverageData1Counter = 0 ;
int WindAverageData2 [ 10 ] ;
unsigned char WindAverageData2Counter = 0 ;
int RainPerHour [ 24 ] ;
unsigned char RainPerHourCounter = 0 ;
struct MeasuredData {
int WindDirection ;
int WindSpeed ;
int WindGust ;
int Rain ;
int RainLast24 ;
int SensorRainSinceMidnight ;
int Pressure ;
float Temperature ;
float Humidity ;
bool HeaterStatus = 0 ;
} MeasuredData ;
// Read Si7021 sensor and process data
void ReadSi7021 ( void )
{
si7021 . triggerMeasurement ( ) ;
si7021 . getHumidity ( MeasuredData . Humidity ) ;
si7021 . getTemperature ( MeasuredData . Temperature ) ;
// Scale for more decimal positions when converted to integer value for ModBus
MeasuredData . Humidity * = 100 ;
MeasuredData . Temperature * = 100 ;
//If humidity is larger than 80% switch on heater to get more acurate measurement
//Switch off when lower than 78% (hysteresis)
if ( MeasuredData . Humidity > 80 & & ! MeasuredData . HeaterStatus ) {
Serial . print ( F ( " Heater on. " ) ) ;
MeasuredData . HeaterStatus = 1 ;
si7021 . setHeater ( MeasuredData . HeaterStatus ) ;
}
if ( MeasuredData . Humidity < 78 & & MeasuredData . HeaterStatus ) {
Serial . print ( F ( " Heater off. " ) ) ;
MeasuredData . HeaterStatus = 0 ;
si7021 . setHeater ( MeasuredData . HeaterStatus ) ;
}
}
// Read BMP280
void ReadBMP280 ( void )
{
MeasuredData . Pressure = 0 ;
/*
bmp280 . awaitMeasurement ( ) ;
float temperature ;
bmp280 . getTemperature ( temperature ) ;
float pascal ;
bmp280 . getPressure ( pascal ) ;
MeasuredData . Pressure = pascal ;
bmp280 . triggerMeasurement ( ) ;
*/
}
int MaxOfArray ( int array [ ] , unsigned int length )
{
int maximum_value = 0 ;
while ( length - - )
{
if ( array [ length ] > maximum_value )
maximum_value = array [ length ] ;
}
return maximum_value ;
}
int AverageOfArray ( int array [ ] , unsigned int length )
{
int tmp_value = 0 ;
unsigned char tmp_length = length ;
int average_value = 0 ;
while ( length - - )
{
tmp_value + = array [ length ] ;
}
average_value = tmp_value / tmp_length ;
return average_value ;
}
// Call this function every 2 seconds
void ReadSparkfunWeatherStation ( void )
{
unsigned char cnt = 0 ;
float tmpRegister ;
MeasuredData . WindDirection = weatherMeterKit . getWindDirection ( ) ;
tmpRegister = 100 * ( weatherMeterKit . getWindSpeed ( ) ) / 3.6 ; // Use float for conversion to m/s times 100, than put it in integer register for ModBus
MeasuredData . WindSpeed = tmpRegister ;
tmpRegister = 100 * weatherMeterKit . getTotalRainfall ( ) ; // Use float for conversion to l/m2 times 100, than put it in integer register for ModBus
MeasuredData . Rain = tmpRegister ;
// FIFO for calculating wind gust of last 10 minutes
// to preserve valuable RAM we caanot store all measurements of the last 10 minutes.
// So we use a hack: store the last 30 values in a FIFO and every minute we store the maximum value from this FIFO in another FIFO.
// This second FIFO is 10 deep: it stores the maximum values of the last 10 minutes.
// The maximum value from this FIFO is the maximum wind gust of the last 10 minutes.
if ( WindGustData1Counter < 29 )
{
WindGustData1Counter + + ;
}
else
{
if ( WindGustData2Counter < 9 )
{
WindGustData2Counter + + ;
}
else
{
WindGustData2Counter = 0 ;
}
WindGustData2 [ WindGustData2Counter ] = MaxOfArray ( WindGustData1 , 30 ) ;
WindGustData1Counter = 0 ;
}
WindGustData1 [ WindGustData1Counter ] = MeasuredData . WindSpeed ;
MeasuredData . WindGust = MaxOfArray ( WindGustData2 , 10 ) ;
// Smart FIFO, same as for Wind Gust, but now for average wind speed over 10 minutes
if ( WindAverageData1Counter < 29 )
{
WindAverageData1Counter + + ;
}
else
{
if ( WindAverageData2Counter < 9 )
{
WindAverageData2Counter + + ;
}
else
{
WindAverageData2Counter = 0 ;
}
WindAverageData2 [ WindAverageData2Counter ] = AverageOfArray ( WindAverageData1 , 30 ) ;
WindAverageData1Counter = 0 ;
WindAverageData1 [ WindAverageData1Counter ] = MeasuredData . WindSpeed ;
}
WindAverageData1 [ WindAverageData1Counter ] = MeasuredData . WindSpeed ;
MeasuredData . WindSpeed = AverageOfArray ( WindAverageData2 , 10 ) ;
// Record rainfall in one hour, save last 24 readings in FIFO
if ( ( millis ( ) - HourTimer ) > = 3.6e+6 ) {
HourTimer = millis ( ) ;
if ( RainPerHourCounter < 23 )
{
RainPerHourCounter + + ;
} else {
RainPerHourCounter = 0 ;
}
RainPerHour [ RainPerHourCounter ] = MeasuredData . Rain ;
weatherMeterKit . resetTotalRainfall ( ) ;
// Calculate rain fall in the last 24 hours
MeasuredData . RainLast24 = 0 ;
for ( cnt = 0 ; cnt < 24 ; cnt + + ) {
MeasuredData . RainLast24 + = RainPerHour [ cnt ] ;
}
}
MeasuredData . Rain = RainPerHour [ RainPerHourCounter ] ;
}
void setup ( ) {
MySerial . begin ( Baudrate ) ; // works on all boards but the configuration is 8N1 which is incompatible with the MODBUS standard
// prefer the line below instead if possible
// MySerial.begin (Baudrate, MB_PARITY_EVEN);
// initialize digital pin LED_BUILTIN as an output and turn it on.
pinMode ( LED_BUILTIN , OUTPUT ) ;
digitalWrite ( LED_BUILTIN , HIGH ) ;
//Setup control lines for RS485 driver
pinMode ( RS485_RE , INPUT ) ; // In hardware connected to RS485_DE. Should be input to prevent a short circuit!
pinMode ( RS485_DE , OUTPUT ) ;
digitalWrite ( RS485_DE , LOW ) ;
mb . config ( Baudrate ) ;
mb . setAdditionalServerData ( " TEMP_SENSOR " ) ; // for Report Server ID function (0x11)
// Add SensorIreg registers - Use addIreg() for analog Inputs
mb . addIreg ( SensorIDIreg ) ;
mb . addIreg ( SensorWindDirectionIreg ) ;
mb . addIreg ( SensorWindSpeedIreg ) ;
mb . addIreg ( SensorWindGustIreg ) ;
mb . addIreg ( SensorTemperatureIreg ) ;
mb . addIreg ( SensorRainIreg ) ;
mb . addIreg ( SensorRainLast24Ireg ) ;
mb . addIreg ( SensorRainSinceMidnightIreg ) ;
mb . addIreg ( SensorHumidityIreg ) ;
mb . addIreg ( SensorPressureIreg ) ;
// Set Weather station ID
mb . Ireg ( SensorIDIreg , 0x5758 ) ;
// Set unused register to zero
mb . Ireg ( SensorRainSinceMidnightIreg , 0 ) ;
Serial . println ( F ( " Weather station " ) ) ;
//Initialize Si7021 sensor
Serial . print ( F ( " Humidity sensor SI7021 " ) ) ;
if ( si7021 . initialize ( ) )
Serial . println ( F ( " found " ) ) ;
else
{
Serial . println ( F ( " missing " ) ) ;
while ( 1 ) {
digitalWrite ( LED_BUILTIN , HIGH ) ; // turn the LED on (HIGH is the voltage level)
delay ( 500 ) ; // wait for half a second
digitalWrite ( LED_BUILTIN , LOW ) ; // turn the LED off by making the voltage LOW
delay ( 500 ) ;
}
}
/* // Initialize BMP280 pressure sensor
Serial . print ( F ( " Pressure sensor BMP280 " ) ) ;
if ( bmp280 . initialize ( ) )
Serial . println ( F ( " found " ) ) ;
else
{
Serial . println ( F ( " missing " ) ) ;
while ( 1 ) {
digitalWrite ( LED_BUILTIN , HIGH ) ; // turn the LED on (HIGH is the voltage level)
delay ( 500 ) ; // wait for half a second
digitalWrite ( LED_BUILTIN , LOW ) ; // turn the LED off by making the voltage LOW
delay ( 500 ) ;
}
}
// onetime-measure:
bmp280 . setEnabled ( 0 ) ;
bmp280 . triggerMeasurement ( ) ;
*/
// Expected ADC values have been defined for various platforms in the
// library, however your platform may not be included. This code will check
// if that's the case
# ifdef SFE_WMK_PLAFTORM_UNKNOWN
// The platform you're using hasn't been added to the library, so the
// expected ADC values have been calculated assuming a 10k pullup resistor
// and a perfectly linear 16-bit ADC. Your ADC likely has a different
// resolution, so you'll need to specify it here:
weatherMeterKit . setADCResolutionBits ( 10 ) ;
# endif
// Begin weather meter kit
weatherMeterKit . begin ( ) ;
ts = millis ( ) ;
RainPerHourCounter = ts ;
}
void loop ( ) {
// Call once inside loop() - all magic here
mb . task ( ) ;
// Read each two seconds
if ( ( millis ( ) - ts ) > = 2000 ) {
ts = millis ( ) ;
digitalWrite ( LED_BUILTIN , HIGH ) ; // LED as heartbeat
// Read temperature and humidity
ReadSi7021 ( ) ;
// Read pressure and temperature
ReadBMP280 ( ) ;
// Read Wind and rain
ReadSparkfunWeatherStation ( ) ;
// Setting Sparkfun weather station registers
mb . Ireg ( SensorWindDirectionIreg , MeasuredData . WindDirection ) ;
mb . Ireg ( SensorWindSpeedIreg , MeasuredData . WindSpeed ) ;
mb . Ireg ( SensorWindGustIreg , MeasuredData . WindGust ) ;
mb . Ireg ( SensorRainIreg , MeasuredData . Rain ) ;
mb . Ireg ( SensorRainLast24Ireg , MeasuredData . RainLast24 ) ;
mb . Ireg ( SensorTemperatureIreg , MeasuredData . Temperature ) ;
mb . Ireg ( SensorHumidityIreg , MeasuredData . Humidity ) ;
mb . Ireg ( SensorPressureIreg , MeasuredData . Pressure ) ;
digitalWrite ( LED_BUILTIN , LOW ) ; // LED as heartbeat
}
}