/** * 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]; }