You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
625 lines
21 KiB
625 lines
21 KiB
/*********************************************************************************
|
|
*
|
|
* 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, 2024 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/>.
|
|
*
|
|
* 2023-01-21: - Buffer overflow when calculating average wind speed in AverageOfArray()
|
|
* Fix: use 32 bit register for average_value.
|
|
* - Changed some variables to the propper standard (uint8_t, uint16_t, etc.)
|
|
* - SparkFun wind interrupt now calculates over 3 seconds in stead of 1 second (KNMI standard)
|
|
*
|
|
**********************************************************************************/
|
|
#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;
|
|
float PRESSURE_OFFSET = 210; // Calibration of BMP280: offset in Pascal
|
|
|
|
/**************************/
|
|
/* 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) [NOT IMPLEMENTED, always 0]
|
|
* 30008: Humidity (percent)
|
|
* 30009: Barometric pressure (hPa)
|
|
* 30010: Luminosity (W/m2)
|
|
* 30011: Snow fall [NOT IMPLEMENTED, always 0]
|
|
* 30012: Raw rainfall counter (mm)
|
|
* 30013: Temperature pressure sensor (degrees Celsius)
|
|
* 30014: Status bits 0=heater, 1-15: reserved
|
|
*
|
|
*/
|
|
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;
|
|
const int SensorLuminosityIreg = 10;
|
|
const int SensorSnowFallIreg = 11;
|
|
const int SensorRainfallRawIreg = 12;
|
|
const int SensorTemperatureBackupIreg = 13;
|
|
const int SensorStatusBitsIreg = 14;
|
|
|
|
/* Modbus Registers Offsets (0-9999)
|
|
* Coils
|
|
* 0 = Heater algorithm (0 = disable, 1 = enable)
|
|
*/
|
|
const int HeaterCoil = 0;
|
|
|
|
// 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;
|
|
uint16_t WindGustData1[30];
|
|
uint8_t WindGustData1Counter=0;
|
|
uint16_t WindGustData2[10];
|
|
uint8_t WindGustData2Counter=0;
|
|
uint16_t WindAverageData1[30];
|
|
uint8_t WindAverageData1Counter=0;
|
|
uint16_t WindAverageData2[10];
|
|
uint8_t WindAverageData2Counter=0;
|
|
uint16_t RainPerHour[24];
|
|
uint8_t RainPerHourCounter=0;
|
|
|
|
struct MeasuredData {
|
|
uint16_t WindDirection;
|
|
uint16_t WindSpeed;
|
|
uint16_t WindGust;
|
|
uint16_t Rain;
|
|
uint16_t RainLast24;
|
|
uint16_t SensorRainSinceMidnight;
|
|
uint16_t Pressure;
|
|
uint16_t Luminosity;
|
|
uint16_t StatusBits = 0;
|
|
uint16_t RainfallCounter = 0;
|
|
|
|
float Temperature;
|
|
float Humidity;
|
|
float TemperatureBackup;
|
|
|
|
bool HeaterStatus = 0;
|
|
} MeasuredData;
|
|
|
|
// State machine implementing smart heater to prevent saturation of the sensor
|
|
char HeaterSi7021 (float humidity)
|
|
{
|
|
static int state=0;
|
|
static unsigned long StatemachineTimer=0;
|
|
bool TempValid=1;
|
|
bool Heater=0;
|
|
|
|
// If Smart heater algorithm is disabled, reset the statemachine forever.
|
|
// TempValid bit is also forced to 1, but it could be that we just came out of a heater period.
|
|
// We assume that the client on the other side of the ModBus is smart enough to understand.
|
|
if ( (MeasuredData.StatusBits & 0x04) == 0)
|
|
state = 0;
|
|
|
|
switch (state)
|
|
{
|
|
// Default state: humidity is below 95%
|
|
case 0:
|
|
Heater = 0;
|
|
if (humidity >= 95) {
|
|
StatemachineTimer = millis();
|
|
state = 1;
|
|
}
|
|
break;
|
|
// Humidity went above 95%. See if humidity stays above 95% for more than an hour, if so turn on heater
|
|
case 1:
|
|
if (humidity >= 95) {
|
|
if ( (millis() - StatemachineTimer) >= 3.6e+6 ) {
|
|
//if ( (millis() - StatemachineTimer) >= 300000 ) { // short delay for testing
|
|
Heater = 1;
|
|
TempValid = 0;
|
|
StatemachineTimer = millis();
|
|
state = 2;
|
|
} else {
|
|
Heater = 0;
|
|
}
|
|
} else {
|
|
Heater = 0;
|
|
state = 0;
|
|
}
|
|
break;
|
|
|
|
// Heater is now on, let the sensor cook for 5 minutes
|
|
case 2:
|
|
if ( (millis() - StatemachineTimer) >= 300000 ) {
|
|
StatemachineTimer = millis();
|
|
Heater = 0;
|
|
state = 3;
|
|
} else {
|
|
Heater = 1;
|
|
}
|
|
TempValid = 0;
|
|
break;
|
|
// Heater is now off, let the sensor cool for 15 minutes
|
|
case 3:
|
|
if ( (millis() - StatemachineTimer) >= 900000 ) {
|
|
TempValid = 1; // Sensor cooled, so we can take a valid temperature reading
|
|
if (humidity >= 95) {
|
|
// Humidity still above 95%, repeat heating/cooling
|
|
Heater = 1;
|
|
StatemachineTimer = millis();
|
|
state = 2;
|
|
} else {
|
|
// Humidity below 95%, reset statemachine
|
|
Heater = 0;
|
|
state = 0;
|
|
}
|
|
} else {
|
|
Heater = 0;
|
|
TempValid = 0;
|
|
}
|
|
break;
|
|
|
|
default:
|
|
Heater = 0;
|
|
state = 0;
|
|
break;
|
|
}
|
|
|
|
return TempValid<<1 | Heater;
|
|
|
|
}
|
|
// Read Si7021 sensor and process data
|
|
void ReadSi7021 (void)
|
|
{
|
|
char result=0x2;
|
|
float humidity;
|
|
|
|
si7021.triggerMeasurement();
|
|
si7021.getHumidity(humidity);
|
|
|
|
if (humidity>100 || humidity<0)
|
|
humidity = 100;
|
|
|
|
//If humidity is larger than 95% switch on heater to get more acurate measurement and prevent memory offset
|
|
result = HeaterSi7021(humidity);
|
|
MeasuredData.StatusBits &= 0xFFFC; // Reset heater status bits to zero
|
|
MeasuredData.StatusBits |= result; // And set the proper bits to one if there are any. The result is we copied the status bits to the register
|
|
|
|
// Temperture and humidity readings are valid (as the sensor is not heated)
|
|
if (result & 0x2) {
|
|
si7021.getTemperature(MeasuredData.Temperature);
|
|
// Scale for more decimal positions when converted to integer value for ModBus
|
|
MeasuredData.Temperature *= 100;
|
|
//Serial.print(F("Valid temp"));
|
|
|
|
si7021.getHumidity(MeasuredData.Humidity);
|
|
if (MeasuredData.Humidity>100 || MeasuredData.Humidity<0)
|
|
MeasuredData.Humidity = 100;
|
|
// Scale for more decimal positions when converted to integer value for ModBus
|
|
MeasuredData.Humidity *= 100;
|
|
}
|
|
// Statemachine thinks it is time to switch on the heater
|
|
if (result & 0x1) {
|
|
//Serial.print(F("Heater on."));
|
|
MeasuredData.HeaterStatus = 1;
|
|
|
|
} else {
|
|
//Serial.print(F("Heater off."));
|
|
MeasuredData.HeaterStatus = 0;
|
|
}
|
|
si7021.setHeater(MeasuredData.HeaterStatus);
|
|
|
|
}
|
|
|
|
// Read BMP280
|
|
void ReadBMP280 (void)
|
|
{
|
|
bmp280.awaitMeasurement();
|
|
|
|
float pascal;
|
|
bmp280.getPressure(pascal);
|
|
pascal = (pascal - PRESSURE_OFFSET) / 10; // Convert to hPa
|
|
MeasuredData.Pressure = pascal;
|
|
|
|
bmp280.getTemperature(MeasuredData.TemperatureBackup);
|
|
// Scale for more decimal positions when converted to integer value for ModBus
|
|
MeasuredData.TemperatureBackup *= 100;
|
|
|
|
bmp280.triggerMeasurement();
|
|
|
|
}
|
|
|
|
int MaxOfArray (int array[], uint16_t length)
|
|
{
|
|
int maximum_value = 0;
|
|
|
|
while (length--)
|
|
{
|
|
if (array[length] > maximum_value)
|
|
maximum_value = array[length];
|
|
}
|
|
return maximum_value;
|
|
}
|
|
|
|
uint16_t AverageOfArray (uint16_t array[], uint16_t length)
|
|
{
|
|
uint32_t tmp_value = 0;
|
|
uint8_t tmp_length = length;
|
|
uint16_t 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;
|
|
|
|
tmpRegister = 10*weatherMeterKit.getWindDirection(); // Use float for conversion to degrees times 10, than put it in integer register for ModBus
|
|
MeasuredData.WindDirection = tmpRegister;
|
|
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 cannot 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;
|
|
// Every time before we reset the TotalRainCounter we add the amount to the RawRainCounter.
|
|
// This 16 bit register will eventually overflow, but 655.35mm of rain fall is a lot!
|
|
MeasuredData.RainfallCounter += MeasuredData.Rain; // We don't care about the rounding error due to the convertion from float to int
|
|
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);
|
|
mb.addIreg (SensorLuminosityIreg);
|
|
mb.addIreg (SensorSnowFallIreg);
|
|
mb.addIreg (SensorRainfallRawIreg);
|
|
mb.addIreg (SensorTemperatureBackupIreg);
|
|
mb.addIreg (SensorStatusBitsIreg);
|
|
|
|
// Add HeaterCoil register
|
|
mb.addCoil (HeaterCoil);
|
|
|
|
// Set Weather station ID
|
|
mb.Ireg (SensorIDIreg, 0x5758);
|
|
// Set unused register to zero
|
|
mb.Ireg (SensorRainSinceMidnightIreg, 0);
|
|
mb.Ireg (SensorSnowFallIreg, 0);
|
|
|
|
Serial.println(F("Weather station v0.2.2"));
|
|
Serial.println(F("(C)2024 M.T. Konstapel"));
|
|
Serial.println(F("This project is free and open source"));
|
|
Serial.println(F("More details: https://meezenest.nl/mees/"));
|
|
|
|
//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);
|
|
}
|
|
}
|
|
|
|
// The standard library of the Si7021 sets the heater element to the default 3.1mA, but we want the full power
|
|
// We could alter the library, but than we break compatibility. So for this one time we do a raw-write to the
|
|
// heater register.
|
|
const uint8_t SI7021_I2C_ADDRESS =(0x40);
|
|
const uint8_t SI7021_CMD_WRITE_HEATER_CONTROL_REG =(0x51);
|
|
const uint8_t SI7021_HEATER_FULL_BLAST =(0x0F); // Set heater to 94mA
|
|
i2c.writeByte(SI7021_I2C_ADDRESS, SI7021_CMD_WRITE_HEATER_CONTROL_REG, SI7021_HEATER_FULL_BLAST);
|
|
|
|
// 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
|
|
|
|
// Here we create a struct to hold all the calibration parameters
|
|
SFEWeatherMeterKitCalibrationParams calibrationParams = weatherMeterKit.getCalibrationParams();
|
|
|
|
// The wind vane has 8 switches, but 2 could close at the same time, which
|
|
// results in 16 possible positions. Each position has a resistor connected
|
|
// to GND, so this library assumes a voltage divider is created by adding
|
|
// another resistor to VCC. Some of the wind vane resistor values are
|
|
// fairly close to each other, meaning an accurate ADC is required. However
|
|
// some ADCs have a non-linear behavior that causes this measurement to be
|
|
// inaccurate. To account for this, the vane resistor values can be manually
|
|
// changed here to compensate for the non-linear behavior of the ADC
|
|
calibrationParams.vaneADCValues[WMK_ANGLE_0_0] = 943;
|
|
calibrationParams.vaneADCValues[WMK_ANGLE_22_5] = 828;
|
|
calibrationParams.vaneADCValues[WMK_ANGLE_45_0] = 885;
|
|
calibrationParams.vaneADCValues[WMK_ANGLE_67_5] = 702;
|
|
calibrationParams.vaneADCValues[WMK_ANGLE_90_0] = 785;
|
|
calibrationParams.vaneADCValues[WMK_ANGLE_112_5] = 404;
|
|
calibrationParams.vaneADCValues[WMK_ANGLE_135_0] = 460;
|
|
calibrationParams.vaneADCValues[WMK_ANGLE_157_5] = 82;
|
|
calibrationParams.vaneADCValues[WMK_ANGLE_180_0] = 91;
|
|
calibrationParams.vaneADCValues[WMK_ANGLE_202_5] = 64;
|
|
calibrationParams.vaneADCValues[WMK_ANGLE_225_0] = 185;
|
|
calibrationParams.vaneADCValues[WMK_ANGLE_247_5] = 125;
|
|
calibrationParams.vaneADCValues[WMK_ANGLE_270_0] = 285;
|
|
calibrationParams.vaneADCValues[WMK_ANGLE_292_5] = 242;
|
|
calibrationParams.vaneADCValues[WMK_ANGLE_315_0] = 628;
|
|
calibrationParams.vaneADCValues[WMK_ANGLE_337_5] = 598;
|
|
|
|
// The rainfall detector contains a small cup that collects rain water. When
|
|
// the cup fills, the water is dumped and the total rainfall is incremented
|
|
// by some value. This value defaults to 0.2794mm of rain per count, as
|
|
// specified by the datasheet
|
|
calibrationParams.mmPerRainfallCount = 0.2794;
|
|
|
|
// The rainfall detector switch can sometimes bounce, causing multiple extra
|
|
// triggers. This input is debounced by ignoring extra triggers within a
|
|
// time window, which defaults to 100ms
|
|
calibrationParams.minMillisPerRainfall = 100;
|
|
|
|
// The anemometer contains a switch that opens and closes as it spins. The
|
|
// rate at which the switch closes depends on the wind speed. The datasheet
|
|
// states that a wind of 2.4kph causes the switch to close once per second
|
|
calibrationParams.kphPerCountPerSec = 2.4;
|
|
|
|
// Because the anemometer generates discrete pulses as it rotates, it's not
|
|
// possible to measure the wind speed exactly at any point in time. A filter
|
|
// is implemented in the library that averages the wind speed over a certain
|
|
// time period, which defaults to 1 second. Longer intervals result in more
|
|
// accurate measurements, but cause delay in the measurement
|
|
// Dutch metrology institute (KNMI) defines that the windspeed and gust should
|
|
// be calculated from 3 seconds measurements.
|
|
calibrationParams.windSpeedMeasurementPeriodMillis = 3000;
|
|
|
|
// Now we can set all the calibration parameters at once
|
|
weatherMeterKit.setCalibrationParams(calibrationParams);
|
|
|
|
// 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);
|
|
mb.Ireg (SensorTemperatureBackupIreg, MeasuredData.TemperatureBackup);
|
|
mb.Ireg (SensorLuminosityIreg, MeasuredData.Luminosity);
|
|
mb.Ireg (SensorRainfallRawIreg, MeasuredData.RainfallCounter);
|
|
mb.Ireg (SensorStatusBitsIreg, MeasuredData.StatusBits);
|
|
|
|
// Debug wind vane
|
|
//Serial.print(F("\n Measured ADC: "));
|
|
//Serial.print(analogRead(windDirectionPin));
|
|
|
|
// enable or disable smart heater
|
|
if (mb.Coil (HeaterCoil)) {
|
|
MeasuredData.StatusBits |= 0x04; // Set bit
|
|
} else {
|
|
MeasuredData.StatusBits &= 0x0B; // Reset bit
|
|
}
|
|
|
|
digitalWrite(LED_BUILTIN, LOW); // LED as heartbeat
|
|
|
|
|
|
}
|
|
} |