diff --git a/README.md b/README.md index 46046d3..d816020 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,9 @@ # wind_rain_sensor -Arduino library to read out a Weather Sensor Assembly p/n 80422 \ No newline at end of file +Arduino library to read out a Weather Sensor Assembly p/n 80422, as sold [here](https://www.watterott.com/de/Wetter-Messeinheit) + +Will add documentation & make it a proper Arduino library once I find the time. +The lib is really small though, just skim over it. + +# license +GPL-3.0 diff --git a/datasheet.pdf b/datasheet.pdf new file mode 100644 index 0000000..096e158 Binary files /dev/null and b/datasheet.pdf differ diff --git a/wind.h b/wind.h new file mode 100644 index 0000000..598d310 --- /dev/null +++ b/wind.h @@ -0,0 +1,178 @@ +/** + * provides functions to read out the sensors of a SEN 08942 "Wettermesseinheit": + * - Wind Direction: analog sensor with 16 cardinal direction steps + * - Wind Speed: Interrupt counter + * - Rain: Interrupt counter + * + * Check your controller's spec which pins have ADC or interrupt capabilities! + * On a senseBox MCU this should work well: + * WeatherUnit weather(4, 6, 5); + */ + +#pragma once + +#include + +class WeatherUnit { + public: + WeatherUnit (uint8_t pinDirection, uint8_t pinSpeed, uint8_t pinRain, uint16_t directionOffset); + void begin (); + + float getWindDir (unsigned char numMeasures, unsigned int interval); + float getWindDir (); + float getWindSpeed (); + float getRain (); + + // interrupt handlers + void countRain (); + void countSpeed (); + + private: + const unsigned int pinDirection, pinSpeed, pinRain, directionOffset; + const unsigned int directionVolt[16] = {320, 410, 450, 620, 900, 1190, 1400, 1980, 2250, 2930, 3080, 3430, 3840, 4040, 4620, 4780}; + const unsigned int directionDegree[16] = {113, 68, 90, 158, 135, 203, 180, 23, 45, 248, 225, 338, 0, 292, 315, 270 }; + + unsigned unsigned long lastResetRain = 0, lastResetSpeed = 0; + volatile unsigned long rainCounter = 0, speedCounter = 0; + volatile bool lastRainReading = 0, currentRainReading = 0, lastSpeedReading = 0, currentSpeedReading = 0; + + bool debounceInput (unsigned int pin, bool last); +}; + + + + + + + +// keep a pointer to the last created instance, in order to attach global interrupt handlers +WeatherUnit* WEATHER_INSTANCE = NULL; + +void WEATHER_speedInterruptHandler () { + WEATHER_INSTANCE->countSpeed(); +} + +void WEATHER_rainInterruptHandler () { + WEATHER_INSTANCE->countRain(); +} + + + + + + + + +WeatherUnit::WeatherUnit (uint8_t pinDirection, uint8_t pinSpeed, uint8_t pinRain, uint16_t directionOffset = 0) + : pinDirection(pinDirection), pinSpeed(pinSpeed), pinRain(pinRain), directionOffset(directionOffset) { + WEATHER_INSTANCE = this; +} + +void WeatherUnit::begin () { + lastResetRain = millis(); + lastResetSpeed = millis(); + + pinMode(pinDirection, INPUT); + pinMode(pinSpeed, INPUT_PULLUP); + pinMode(pinRain, INPUT_PULLUP); + + attachInterrupt(digitalPinToInterrupt(pinSpeed), WEATHER_speedInterruptHandler, RISING); + attachInterrupt(digitalPinToInterrupt(pinRain), WEATHER_rainInterruptHandler, LOW); +} + +float WeatherUnit::getWindDir (unsigned char numMeasures, unsigned int interval) { + // angular average + float sumX = 0, sumY = 0, val; + for (unsigned char i = 0; i < numMeasures; i++) { + val = radians(getWindDir()); + sumX += sin(val); + sumY += cos(val); + if (i != numMeasures - 1) delay(interval); + } + return 180 + atan(sumX / sumY) * 180 / 3.1415; +} + +float WeatherUnit::getWindDir () { + unsigned int mvolt = analogRead(pinDirection); + mvolt = map(mvolt, 0, 1023, 0, 5000); // map to range 0-5000 to make use of voltage values as defined in data sheet + + // search for correct index + unsigned char i; + for (i = 0; i < 16; i++) { + if (mvolt <= directionVolt[i]) break; + } + i--; // the correct reference is actually lower than the reference value found. + + return (float) ((directionDegree[i] + directionOffset) % 360); +} + +float WeatherUnit::getWindSpeed () { + unsigned long now = millis(); + double secondsPassed = (now - lastResetSpeed) / 1000; // @FIXME: might yield bullshit when millis() overflows! + // one impulse per second equals 2.4 km/h -> 0.6666 m/s + float metersPerSec = (2 * speedCounter) / (3 * max(secondsPassed, 1.0)); + speedCounter = 0; + lastResetSpeed = now; + return metersPerSec; +} + +float WeatherUnit::getRain () { + float val = rainCounter * 0.2794; // mm aka L/m² + rainCounter = 0; + return val; +} + +bool WeatherUnit::debounceInput (unsigned int pin, bool last) { + bool current = digitalRead(pin); + if (last != current) { + // adjust this value. + // shorter -> risk of detecting single switch multiple times, + // longer -> risk of missing switches. + delayMicroseconds(1e3); + current = digitalRead(pin); + } + return current; +} + +void WeatherUnit::countRain () { + currentRainReading = debounceInput(pinRain, lastRainReading); + if (lastRainReading == LOW && currentRainReading == HIGH) + rainCounter++; + lastRainReading = currentRainReading; +} + +void WeatherUnit::countSpeed () { + currentSpeedReading = debounceInput(pinSpeed, lastSpeedReading); + if (lastSpeedReading == LOW && currentSpeedReading == HIGH) + speedCounter++; + lastSpeedReading = currentSpeedReading; +} + + + + +float mpsToKmph (float val) { return val * 3.6; } +float mpsToKnots (float val) { return val * 1.943843307; } +float mpsToBeaufort (float val) { + if (val < 0.5) return 0; + else if (val < 1.5) return 1; + else if (val < 3.3) return 2; + else if (val < 5.5) return 3; + else if (val < 7.9) return 4; + else if (val < 10.7) return 5; + else if (val < 13.8) return 6; + else if (val < 17.1) return 7; + else if (val < 20.7) return 8; + else if (val < 24.4) return 9; + else if (val < 28.4) return 10; + else if (val < 32.6) return 11; + else return 12; +} + +const String CARDINAL_DIRS_EN[16] = {"N", "NNE", "NE", "ENE", "E", "ESE", "SE", "SSE", "S", "SSW", "SW", "WSW", "W", "WNW", "NW", "NNW"}; +const String CARDINAL_DIRS_DE[16] = {"N", "N-NO", "N-O", "O-NO", "O", "O-SO", "S-O", "S-SO", "S", "S-SW", "S-W", "W-SW", "W", "W-NW", "N-W", "N-NW"}; +String degreeToCardinal (float val, const String cardinalNames[] = CARDINAL_DIRS_EN) { + int i = (int)((val + 11.25) / 22.5); + return cardinalNames[i % 16]; +} +