Nightmare with photon weather shield

Hi all,

I am working on this code lot of hours.

The electron reads some sensors, but the problem is in the wind readings. This measurement returns all zero. I would like to read the wind and direction during 30 seconds, but I did not know how to do it

I’m desperate, tired and I’m looking for somebody could help me to find why did not work.

Thank’s for your support.

Thank’s

SYSTEM_MODE(SEMI_AUTOMATIC)
SYSTEM_THREAD(ENABLED)

#include <SparkFun_Photon_Weather_Shield_Library.h>
#include <math.h>

// Each time we loop through the main loop, we check to see if it's time to capture the sensor readings
unsigned int sensorCapturePeriod = 100;
unsigned int timeNextSensorReading;

// Each time we loop through the main loop, we check to see if it's time to publish the data we've collected
unsigned int publishPeriod = 90000;
unsigned int timeNextPublish;
const    int   sleepPeriode = 4 * 60;

void setup() {
  initializeRainGauge();
  initializeAnemometer();
  initializeWindVane();
}

void loop() {
  Particle.connect();
  Particle.publish("weatherstation", "start", 60, PRIVATE);
  /////////power on led switch
  digitalWrite(D7, HIGH);
  //Timestamp
  float vtime = Time.now();
  String Svtime = String(vtime);

  ///////////////////////// Sparkfun Weather Station /////////////////////////

  float rainmm = getAndResetRainInches()*25.4;
  float gustKPH;
  float windKPH = (getAndResetAnemometerkph(&gustKPH))*1.609;
  float knots = windKPH * 0.5399;
  float windDegrees = getAndResetWindVaneDegrees();

  //Sparkfun Weather station

  String SPKwind = String(windKPH);
  String SPKdegrees = String(windDegrees);
  String SPKrain = String(rainmm);
  String SPKgust = String(gustKPH);
  String SPKknots = String(knots);

  //Particle.connect();
  //if (waitFor(Particle.connected, 300000 ))
  //    { // proceede only if a connection could be established within 60 seconds 

  //////////////////////////////////Building JSON3///////////////////////////////////

  String dest2 = "{";
  if (Svtime.length() != 0) { dest2 = dest2 + "\"1\":\"" + "1" + "\","; }
  if (Svtime.length() != 0) { dest2 = dest2 + "\"31\":\"" + SPKrain + "\","; }
  if (Svtime.length() != 0) { dest2 = dest2 + "\"32\":\"" + SPKgust + "\","; }
  if (Svtime.length() != 0) { dest2 = dest2 + "\"33\":\"" + SPKwind + "\","; }
  if (Svtime.length() != 0) { dest2 = dest2 + "\"34\":\"" + SPKknots + "\","; }
  if (Svtime.length() != 0) { dest2 = dest2 + "\"35\":\"" + SPKdegrees + "\""; }
  dest2 = dest2 + "}";

  delay(5000);
  Particle.publish("weatherstation", dest2, 60, PRIVATE);
  delay(10);
  //}

  System.sleep(SLEEP_MODE_DEEP, sleepPeriode, SLEEP_NETWORK_STANDBY); //Sleep a while to save battery    
}

Weather sensor;

//===========================================================================
// Rain Guage
//===========================================================================
int RainPin = D2;
volatile unsigned int rainEventCount;
unsigned int lastRainEvent;
float RainScaleInches = 0.011; // Each pulse is .011 inches of rain

void initializeRainGauge() {
  pinMode(RainPin, INPUT_PULLUP);
  rainEventCount = 0;
  lastRainEvent = 0;
  attachInterrupt(RainPin, handleRainEvent, FALLING);
  return;
}

void handleRainEvent() {
  // Count rain gauge bucket tips as they occur
  // Activated by the magnet and reed switch in the rain gauge, attached to input D2
  unsigned int vtimeRainEvent = millis(); // grab current time

                                          // ignore switch-bounce glitches less than 10mS after initial edge
  if (vtimeRainEvent - lastRainEvent < 10) {
    return;
  }

  rainEventCount++; //Increase this minute's amount of rain
  lastRainEvent = vtimeRainEvent; // set up for next event
}

float getAndResetRainInches()
{
  float result = RainScaleInches * float(rainEventCount);
  rainEventCount = 0;
  return result;
}

//===========================================================================
// Wind Speed (Anemometer)
//===========================================================================

// The Anemometer generates a frequency relative to the windspeed.  1Hz: 2.4 kph
// We measure the average period (elaspsed time between pulses), and calculate the average windspeed since the last recording.

int AnemometerPin = D3;
float AnemometerScalekph = 2.4; // Windspeed if we got a pulse every second (i.e. 1Hz)
volatile unsigned int AnemoneterPeriodTotal = 0;
volatile unsigned int AnemoneterPeriodReadingCount = 0;
volatile unsigned int GustPeriod = UINT_MAX;
unsigned int lastAnemoneterEvent = 0;

void initializeAnemometer() {
  pinMode(AnemometerPin, INPUT_PULLUP);
  AnemoneterPeriodTotal = 0;
  AnemoneterPeriodReadingCount = 0;
  GustPeriod = UINT_MAX;  //  The shortest period (and therefore fastest gust) observed
  lastAnemoneterEvent = 0;
  attachInterrupt(AnemometerPin, handleAnemometerEvent, FALLING);
  return;
}

void handleAnemometerEvent() {
  // Activated by the magnet in the anemometer (2 ticks per rotation), attached to input D3
  unsigned int vtimeAnemometerEvent = millis(); // grab current time

                                                //If there's never been an event before (first time through), then just capture it
  if (lastAnemoneterEvent != 0) {
    // Calculate time since last event
    unsigned int period = vtimeAnemometerEvent - lastAnemoneterEvent;
    // ignore switch-bounce glitches less than 10mS after initial edge (which implies a max windspeed of 149kph)
    if (period < 10) {
      return;
    }
    if (period < GustPeriod) {
      // If the period is the shortest (and therefore fastest windspeed) seen, capture it
      GustPeriod = period;
    }
    AnemoneterPeriodTotal += period;
    AnemoneterPeriodReadingCount++;
  }

  lastAnemoneterEvent = vtimeAnemometerEvent; // set up for next event
}

float getAndResetAnemometerkph(float * gustKPH)
{
  if (AnemoneterPeriodReadingCount == 0)
  {
    *gustKPH = 0.0;
    return 0;
  }
  // Nonintuitive math:  We've collected the sum of the observed periods between pulses, and the number of observations.
  // Now, we calculate the average period (sum / number of readings), take the inverse and muliple by 1000 to give frequency, and then mulitply by our scale to get kph.
  // The math below is transformed to maximize accuracy by doing all muliplications BEFORE dividing.
  float result = AnemometerScalekph * 1000.0 * float(AnemoneterPeriodReadingCount) / float(AnemoneterPeriodTotal);
  AnemoneterPeriodTotal = 0;
  AnemoneterPeriodReadingCount = 0;
  *gustKPH = AnemometerScalekph * 1000.0 / float(GustPeriod);
  GustPeriod = UINT_MAX;
  return result;
}

//===========================================================
// Wind Vane
//===========================================================
void initializeWindVane() {
  return;
}

// For the wind vane, we need to average the unit vector components (the sine and cosine of the angle)
int WindVanePin = A0;
float windVaneCosTotal = 0.0;
float windVaneSinTotal = 0.0;
unsigned int windVaneReadingCount = 0;

void captureWindVane() {
  // Read the wind vane, and update the running average of the two components of the vector
  unsigned int windVaneRaw = analogRead(WindVanePin);

  float windVaneRadians = lookupRadiansFromRaw(windVaneRaw);
  if (windVaneRadians > 0 && windVaneRadians < 6.14159)
  {
    windVaneCosTotal += cos(windVaneRadians);
    windVaneSinTotal += sin(windVaneRadians);
    windVaneReadingCount++;
  }
  return;
}

float getAndResetWindVaneDegrees()
{
  if (windVaneReadingCount == 0) {
    return 0;
  }
  float avgCos = windVaneCosTotal / float(windVaneReadingCount);
  float avgSin = windVaneSinTotal / float(windVaneReadingCount);
  float result = atan(avgSin / avgCos) * 180.0 / 3.14159;
  windVaneCosTotal = 0.0;
  windVaneSinTotal = 0.0;
  windVaneReadingCount = 0;
  // atan can only tell where the angle is within 180 degrees.  Need to look at cos to tell which half of circle we're in
  if (avgCos < 0) result += 180.0;
  // atan will return negative angles in the NW quadrant -- push those into positive space.
  if (result < 0) result += 360.0;

  return result;
}

float lookupRadiansFromRaw(unsigned int analogRaw)
{
  // The mechanism for reading the weathervane isn't arbitrary, but effectively, we just need to look up which of the 16 positions we're in.
  if (analogRaw >= 2200 && analogRaw < 2400) return (3.14);//South
  if (analogRaw >= 2100 && analogRaw < 2200) return (3.53);//SSW
  if (analogRaw >= 3200 && analogRaw < 3299) return (3.93);//SW
  if (analogRaw >= 3100 && analogRaw < 3200) return (4.32);//WSW
  if (analogRaw >= 3890 && analogRaw < 3999) return (4.71);//West
  if (analogRaw >= 3700 && analogRaw < 3780) return (5.11);//WNW
  if (analogRaw >= 3780 && analogRaw < 3890) return (5.50);//NW
  if (analogRaw >= 3400 && analogRaw < 3500) return (5.89);//NNW
  if (analogRaw >= 3570 && analogRaw < 3700) return (0.00);//North
  if (analogRaw >= 2600 && analogRaw < 2700) return (0.39);//NNE
  if (analogRaw >= 2750 && analogRaw < 2850) return (0.79);//NE
  if (analogRaw >= 1510 && analogRaw < 1580) return (1.18);//ENE
  if (analogRaw >= 1580 && analogRaw < 1650) return (1.57);//East
  if (analogRaw >= 1470 && analogRaw < 1510) return (1.96);//ESE
  if (analogRaw >= 1900 && analogRaw < 2000) return (2.36);//SE
  if (analogRaw >= 1700 && analogRaw < 1750) return (2.74);//SSE
  if (analogRaw > 4000) return(-1); // Open circuit?  Probably means the sensor is not connected
  Particle.publish("error", String::format("Got %d from Windvane.", analogRaw), 60, PRIVATE);
  return -1;
}

Have you tried a bare minimum sketch for that particular sensor? Try removing everything that’s not essential to see if it works in the first place. That’s also a lot easier to read/debug.

2 Likes

A few things stick out but I’m not sure if any of them will help you. Worth a try:

  • Times are always uint32_t. I think unsigned int on STM32 is uint32_t, but it doesn’t hurt to be sure.

  • Order of operations

float result = AnemometerScalekph * 1000.0 * float(AnemoneterPeriodReadingCount) / float(AnemoneterPeriodTotal);

This bites me every once in a while because I’m too lazy to get super-detailed in the order of operations for typecasting. I usually do this to keep me out of trouble:

float result = AnemometerScalekph * 1000.0 * ((float)(AnemoneterPeriodReadingCount)) / ((float)(AnemoneterPeriodTotal));

  • Set up an LED to toggle to make sure your ISR is getting called.

Hi @Moors7

I just reduce to just and posted the mininimum to show what’s doing with wind sensors

The time reading is les than 3 seconds

Sorry @Moors7 and thank’s for your help

This line of code is found inside the ISR void handleAnemometerEvent() but you need to be aware that millis() will not be serviced inside an ISR, so it can't be used to measure the time spent inside.
On the other hand, since your vtimeAnemometerEvent is only local, the value will never be seen outside of the ISR (but your lastAnemometerEvent is used for that, I see).

But, as @Moors7 said, having a shorter code to look at would make it easier to focus on the code that actually may cause the issue, instead of having to plough through code you know works but we don't know which is which :wink:
Also having the pin designations stated in one place would make it easier to judge whether or not you may run into shared interrupt issues or other HW limitations.

Yes, uint32_t is exactly the same as unsigned int on these controllers.

But

Time.now() actually returns an int (UNIX epoch time UTC - Time.local() would return UNIX epoch time corrected for time zone, if set).

Where do you do the wind reading over 30 seconds?
I can't see that happen. AFAICT you are rushing from initializeAnemometer() through to reading as fast as Particle.connect() takes to do its job.

I also don't see the use of delay(5000) before Particle.publish() and delay(10) is a bit short if that's meant to allow for the publish to actually go through (for system versions that don't take care of succesful delivery before goint to sleep).


I also took the liberty to reformat your code and reduce some of the superfluous blank lines and excessive indentations, that just strech out the code, to get more code lines into view at once.

@ecabanas is aggregating a bunch of measurments and calculating the average periodically:

float getAndResetAnemometerkph(float * gustKPH)
{
  if (AnemoneterPeriodReadingCount == 0)
  {
    *gustKPH = 0.0;
    return 0;
  }
  // Nonintuitive math:  We've collected the sum of the observed periods between pulses, and the number of observations.
  // Now, we calculate the average period (sum / number of readings), take the inverse and muliple by 1000 to give frequency, and then mulitply by our scale to get kph.
  // The math below is transformed to maximize accuracy by doing all muliplications BEFORE dividing.
  float result = AnemometerScalekph * 1000.0 * float(AnemoneterPeriodReadingCount) / float(AnemoneterPeriodTotal);
  AnemoneterPeriodTotal = 0;
  AnemoneterPeriodReadingCount = 0;
  *gustKPH = AnemometerScalekph * 1000.0 / float(GustPeriod);
  GustPeriod = UINT_MAX;
  return result;
}

Where does that happen with loop() always going to deep sleep without having retained variables?

Uh, yeah that's not smart. Don't do that @ecabanas

If you're asleep, you can't capture data!

Hi,

At the end I solved the solution avoiding using timeNextSensorReading I used a simple way. I used a variable called VTime that is the millis, and an If in the loop checking if VTime >VTImeoof....

Thank's for your help

Would you be willing to post your final code? I’m having the same problem as you.

Thanks!!!