Jump to content

Banner.jpg.b83b14cd4142fe10848741bb2a14c66b.jpg

Weather Station Ideas


Gina

Recommended Posts

This is the latest sketch including debugging code I'll run it tomorrow - not doing any more tonight.

// Wind speed and direction 2020-09-05

/*********
  With thanks to Rui Santos
  Complete project details at https://randomnerdtutorials.com 
  and others
********
Modified and added to by Gina 2020-08-21 onward
********
*/
// Set up for timer interrupts
volatile int interrupts;
int totalInterrupts;

hw_timer_t * timer = NULL;
portMUX_TYPE timerMux = portMUX_INITIALIZER_UNLOCKED;
int timerCount = 0;

// periods or intervals in milliseconds
const int P3s = 3000;       // integer
unsigned long P3m = 180000; // variable used as constant
unsigned long last3s = 0;
unsigned long last3m = 0;

// ADC pins
const int An1 = 34;
const int An2 = 35;
const int An3 = 32;
const int An4 = 33;
// ADC readings
int dir1 = 0;
int dir2 = 0;
int dir3 = 0;
int dir4 = 0;

// Gray to binary table
int codeArray[16] = {0,1,3,2,7,6,4,5,15,14,12,13,8,9,11,10};
int dirn = 0;

// Direction count bins
int bin[20] = {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0};

// 10m speed averaging etc.
int meanArray[10] = {0,0,0,0,0,0,0,0,0,0};
int gustArray[10] = {0,0,0,0,0,0,0,0,0,0};
int ringIndex = 0;

// debugging the PulseCount
int pulseArray[10] = {0,0,0,0,0,0,0,0,0,0};
int writeIndex = 0, readIndex = 0;

// Speed variables
long sumSpeed = 0; // 0-
int meanSpeed = 0; // 0-100mph
int gustSpeed = 0; // 0-100mph

// Set GPIO for Hall Sensor
const int HallSensorPin = 4;
int PulseCount = 0, windGust = 0;
int windSpeed = 0;

#include <WiFi.h>
#include <PubSubClient.h>

// Replace the next variables with your SSID/Password combination
const char* ssid = "Ubiquity";
const char* password = "********";

const char* mqtt_server = "192.168.1.140";

WiFiClient windClient;
PubSubClient client(windClient);

void IRAM_ATTR onTime() {
  portENTER_CRITICAL_ISR(&timerMux);
  interrupts=1;
  windSpeed += PulseCount;  //  Accumulate mean speed
  if (PulseCount > windGust) {windGust = PulseCount;};  // Get max speed for gust
  pulseArray[writeIndex] = PulseCount;
  writeIndex = (writeIndex+1)%10;
  PulseCount = 0; // clear count 
  timerCount++;  // count number of timer interrupts for 1m speed sum                                      
  portEXIT_CRITICAL_ISR(&timerMux);
}

// Checks if Hall sensor was triggered - Interrupt Handler
void IRAM_ATTR HallTriggered() {
  ++PulseCount; // Increment count
}

int Beaufort(int mph){
  if (mph < 1) return 0;
  else if (mph <= 3) return 1;
  else if (mph <= 7) return 2;
  else if (mph <= 12) return 3;
  else if (mph <= 18) return 4;
  else if (mph <= 24) return 5;
  else if (mph <= 31) return 6;
  else if (mph <= 38) return 7;
  else if (mph <= 46) return 8;
  else if (mph <= 54) return 9;
  else if (mph <= 63) return 10;
  else if (mph <= 72) return 11;
  else return 12;
}

void setup() {
  Serial.begin(115200);
  setup_wifi();
  client.setServer(mqtt_server, 1883);
  client.setCallback(callback);
  
  // Hall Sensor mode INPUT_PULLUP
  pinMode(HallSensorPin, INPUT_PULLUP);
  // Set HallSensor pin as interrupt, assign interrupt function and set FALLING mode
  attachInterrupt(digitalPinToInterrupt(HallSensorPin), HallTriggered, FALLING);
  if (!client.connected()) {reconnect();}
//
  // Configure Prescaler to 80, as our timer runs @ 80Mhz
  // Giving an output of 80,000,000 / 80 = 1,000,000 ticks / second
  timer = timerBegin(0, 80, true);                
  timerAttachInterrupt(timer, &onTime, true);    
  // Fire Interrupt every 3m ticks, so 3s
  timerAlarmWrite(timer, 3000000, true);      
  timerAlarmEnable(timer);
}

void setup_wifi() {
  delay(10);
  // We start by connecting to a WiFi network
  Serial.println();
  Serial.print("Connecting to ");
  Serial.println(ssid);

  WiFi.begin(ssid, password);

  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.print(".");
  }

  Serial.println("");
  Serial.println("WiFi connected");
  Serial.println("IP address: ");
  Serial.println(WiFi.localIP());
}

void callback(char* topic, byte* message, unsigned int length) {
  Serial.print("Message arrived on topic: ");
  Serial.print(topic);
  Serial.print(". Message: ");
  String messageTemp;
  
  for (int i = 0; i < length; i++) {
    Serial.print((char)message[i]);
    messageTemp += (char)message[i];
  }
  Serial.println();
}

void reconnect() {
  // Loop until we're reconnected
  while (!client.connected()) {
    Serial.print("Attempting MQTT connection...");
    // Attempt to connect
    if (client.connect("windClient")) {
      Serial.println("connected");
      // Subscribe
    } else {
      Serial.print("failed, rc=");
      Serial.print(client.state());
      Serial.println(" try again in 5 seconds");
      // Wait 5 seconds before retrying
      delay(5000);
    }
  }
}

//Get instantaneous wind direction - variable dirn
int readDirection(){    
    // Read wind vane optical sensor values
    dir1 = analogRead(An1);
    dir2 = analogRead(An2);
    dir3 = analogRead(An3);
    dir4 = analogRead(An4);
    // Convert Gray bits to integer
    int Gray = 0;
    if (dir1 > 2000) {Gray = 8;};
    if (dir2 > 2000) {Gray += 4;};
    if (dir3 > 2000) {Gray += 2;};
    if (dir4 > 2000) {Gray += 1;};
    //  Convert Gray to binary
    dirn = 15 - codeArray[Gray]; // correct rotation direction
    dirn = (dirn - 1) %16; // correct encoder for North
    return dirn;
}
//
void sendSpeedMessages(){
  if (!client.connected()) {reconnect();}
  // messages to send :-
  // wind/speed/mph -- meanSpeed
  // wind speed/force -- Beaufort(meanSpeed)
  // wind/gust/mph -- gustSpeed
  // wind/gust/force -- Beaufort(gustSpeed)
  // 
  // Convert the Mean Speed to a char array
  char msString[8];
  dtostrf(meanSpeed, 1, 1, msString);
  Serial.print("  Mean Speed: ");
  Serial.print(msString);
  client.publish("wind/speed/mph", msString);
  
  // Convert the Mean-Speed-Force to a char array
  char bsString[8];
  dtostrf(Beaufort(meanSpeed), 1, 0, bsString);
//  Serial.print("Force: ");
//  Serial.println(bsString);
  client.publish("wind/speed/force", bsString);
  
  // Convert the Gust-Speed to a char array
  char gsString[8];
  dtostrf(gustSpeed, 1, 0, gsString);
  Serial.print("  Gust: ");
  Serial.println(gsString);
  client.publish("wind/gust/mph", gsString);

  // Convert the Gust-Speed-Force to a char array
  char bgString[8];
  dtostrf(Beaufort(gustSpeed), 1, 0, bgString);
//  Serial.print("Gust Force: ");
//  Serial.println(bgString);
  client.publish("wind/gust/force", bgString);
}
void sendDirectionMessage(int Dir){
  // Convert the Direction to a char array
  char dirString[8];
  dtostrf(Dir, 1, 0, dirString);
//  Serial.print("  Direction: ");
//  Serial.println(dirString);
  client.publish("wind/direction", dirString);
}
void sendDirectionMessageInst(int Dir){
  // Convert the Direction to a char array
  char dirString[8];
  dtostrf(Dir, 1, 0, dirString);
//  Serial.print("Transient Direction: ");
//  Serial.println(dirString);
  client.publish("wind/direction/inst", dirString);
}
//  Debugging only
void sendringIndex(){
  // Convert the number to a char array
  char numString[8];
  dtostrf(ringIndex, 1, 0, numString);
  Serial.print("ringIndex: ");
  Serial.println(numString);
  client.publish("wind/ringIndex", numString);
}
void sendNumberMessage(int N){
  // Convert the number to a char array
  char numString[8];
  dtostrf(N, 1, 0, numString);
  Serial.print("Number: ");
  Serial.println(numString);
  client.publish("wind/number", numString);
}
void sendNumberMessage2(int N){
  // Convert the number to a char array
  char numString[8];
  dtostrf(N, 1, 0, numString);
  Serial.print("Number: ");
  Serial.println(numString);
  client.publish("wind/number2", numString);
} 
void getDirection(){
  byte instDir = readDirection();
  ++bin[instDir]; // increment appropriate bin
  sendDirectionMessageInst(instDir); // send instantaneous direction 
}
void speed21sum(){
  sendringIndex();
  sendNumberMessage2(windGust);
//  sendNumberMessage2(windSpeed);
  // wind speed mean and gust ring arrays and report
  meanArray[ringIndex] = windSpeed; // put windSpeed into new array index
  gustArray[ringIndex] = windGust;  // put windGust into new array index
  sumSpeed = 0; gustSpeed = 0; windSpeed = 0; windGust = 0;
  for (int i = 0; i < 10; i++) { 
    sumSpeed += meanArray[i];  // sum the windSpeeds 
    if (gustArray[i] > gustSpeed){gustSpeed = gustArray[i];}} // find maximum gust speed
  sumSpeed *=3;  // in conjunction with 20 converts speed by 1.5
  meanSpeed = sumSpeed /20 /timerCount;  // 
  gustSpeed = gustSpeed *3 /2;  // Speed count over 3s rather than 4.5s
  sendSpeedMessages();
  ringIndex = (ringIndex+1)%10; // move ringIndex on to next location in the ring arrays
}
  // debugging PulseCount
void sendPulseArray(){
  sendNumberMessage(pulseArray[readIndex]); 
  readIndex = (readIndex+1)%10; // move readIndex on to next location in the pulse ring array
}

void ConsensusAveraging(){
  // wind direction calculations and report
  int sum[16];
  int S=0,I=0,W=0;
  bin[16] = bin[0];
  bin[17] = bin[1];
  bin[18] = bin[2];
  bin[19] = bin[3];
  for (int i = 0; i < 16; i++) {sum[i] = bin[i] + bin[i+1] + bin[i+2] + bin[i+3] + bin[i+4];
    // find the index with the highest sum and save sum and index
    if (sum[i] > S){S = sum[i]; I = i;};}
  W = (bin[I+1] + 2 * bin[I+2] + 3 * bin[I+3] + 4 * bin[I+4]) * 45 / S;
  sendDirectionMessage((I * 45 + W)%720 /2);
  //Empty the bins ready for a new direction calculation
  for (int i = 0; i < 16; i++) {bin[i] = 0;};
}

void loop() {
//  if (!client.connected()) {reconnect();}
  client.loop();
  if (interrupts != 0 ){
    // ISR has triggered - proceed and perform the jobs
    if (timerCount >= 21) {speed21sum(); timerCount=0;};
    // time the various periods
    long now = millis();
    if(now - last3s > P3s) {getDirection(); sendPulseArray(); last3s = now;}
    if(now - last3m > P3m) {ConsensusAveraging(); last3m = now;}
    interrupts = 0; // Reset the flag
  }
}

 

Link to comment
Share on other sites

I notice you have replaced *1.5 with *3/2  (and other similar changes in other places).
This is not the point I was trying to make.
Multiplying an integer by a float results in a float and requires conversion before being stored in an integer variable.
Best practise would be for the programmer to decide what type of conversion is required (round up, round nearest, round down, truncate, the list of options is endless).
If none is supplied (as in your code) then some languages will error at compile, others will error at runtime, others will not error but will produce unexpected results (including memory leaks or unexpected values) and others will use a default conversion type (often truncate in the case of float to integer) and run just fine (if the default was the desired action).
I suspect in this case the language will be converting it for you by truncating; and everything will run fine.
But, as you were getting unexpected results involving an int variable without an explicit conversion, I thought it best to rule out any possible errors by suggesting you either supply an explicit conversion between variable types or change variable types to avoid conversions.

Your change to *3/2 does not alter the fact that the result is a float (int divided by int is a float) so it still needs conversion to store the result in an int; also this change is slightly worse overall because it requires more computation.

As you seem certain the PulseCount variable is the culprit (not the downstream int variables requiring float conversions) then all this is unlikely to make any difference to your issue; it really just becomes a question of programming style.
 

  • Thanks 1
Link to comment
Share on other sites

21 hours ago, Gina said:

I've been using the UL method of making unsigned long constants in all my clients so these will all need correcting, though they seem to be working fine.

So the unsigned long and an int on a esp probably occupy the same number of bytes (not uncommon for this type of hardware) but the way the compiler interprets the value changes.. if you put an ul into the int, it will be interpreted as an int , which is OK for positive values below half the maximum, but awful for negative values. However if they are not the same length in bytes then you have the other problem, of a long overwriting adjacent short variables. 

  • Thanks 1
Link to comment
Share on other sites

I will get rid of all constants changed from integers to unsigned long because if these overlap other variables I would expect trouble in the future as this is to do with millis mounting up past the normal integer limit as time goes on.   Seems likely that this was why the wind speed calculations suddenly went wrong several days after the system was set up.  Let's see...  millis will exceed the integer limit at 32767 ms = 32.7s and the next byte at 256 times that giving 2.3 hours.  it will encroach on the next byte in just under 25days.  Just what damage would result depends on what data or program code follows.

Edited by Gina
typos
Link to comment
Share on other sites

As to what to do about the spike sometimes occurring when the anemometer is stationary, whilst I would like to know why and cure the cause I'm wondering if my time would be better spent on just killing it and getting on with other things.  I could just ignore it as the effects only last 10m.

Edited by Gina
Link to comment
Share on other sites

I'm leaving the debugging code in place as it doesn't detract from performance and if there is still a problem when the rig is deployed it may help to find the cause.  ATM I have the rig assembled onto the mast and in final test before deployment outside.

Link to comment
Share on other sites

Deployed and powered up.  Seems fine.  Very light and variable breeze.  Anemometer rotating at 1rps or less.  Wind direction is all over the place - just been watching the vane.  MQTT Explorer is watching the PulseCount.

1218825427_Screenshotfrom2020-09-0618-19-36.thumb.png.68822fd20ec2bbf45ee67a5dc543f222.png

Link to comment
Share on other sites

It's actually quite interesting watching the PulseCount as this shows the instantaneous wind speed (3s avg).  The Speed History is only showing real speed for the last half hour.  Before that is the testing.

1404784918_Screenshotfrom2020-09-0618-37-47.thumb.png.c07fe9cde86a8e399c85cb9901732a2b.png

Link to comment
Share on other sites

Although mostly the Wind display looks right, very occasionally the Transient Direction shows 255 in spite of the variable being operated on %16 so only values of 0-15 should occur.  Maybe I'll look at that tomorrow.

Link to comment
Share on other sites

Quote

//Get instantaneous wind direction - variable dirn
int readDirection(){    
    // Read wind vane optical sensor values
    dir1 = analogRead(An1);
    dir2 = analogRead(An2);
    dir3 = analogRead(An3);
    dir4 = analogRead(An4);
    // Convert Gray bits to integer
    int Gray = 0;
    if (dir1 > 2000) {Gray = 8;};
    if (dir2 > 2000) {Gray += 4;};
    if (dir3 > 2000) {Gray += 2;};
    if (dir4 > 2000) {Gray += 1;};
    //  Convert Gray to binary
    dirn = 15 - codeArray[Gray]; // correct rotation direction
    dirn = (dirn - 1) %16; // correct encoder for North
    return dirn;
}

The line 
     dirn = 15 - codeArray[Gray];
returns a value between 0 and 15.

The next line
     dirn = (dirn - 1) %16;
applies mod 16 to a value in the range -1 to 14.  Is the -1 really necessary? You want to shift the range over?  

Mod of negative numbers is notoriously dodgy with different languages implementing different solutions - some returning negative results which looks like what is happening here.

I think your solution is one of the following:

If you want to subtract 1 then add 15 instead, so the line becomes
     dirn = (dirn + 15) %16;

If the -1 was a mistake then remove it, giving
     dirn = dirn %16;
which is actually a pointless line because dirn is already in the range 0 to 15; is the line can actually be removed.

 

Edited by globular
  • Thanks 1
Link to comment
Share on other sites

If I were to rearrange the Gray to Binary conversion look-up table array I could replace these two lines

  dirn = 15 - codeArray[Gray]; // correct rotation direction
  dirn = (dirn + 15) %16; // correct encoder for North

with just

  dirn = codeArray[Gray];

Backwards to reverse the direction and shifted one round to correct for north.  No %16 required.

Edited by Gina
  • Like 1
Link to comment
Share on other sites

0,1,3,2,7,6,4,5,15,14,12,13,8,9,11,10 becomes
10,11,9,8,13,12,14,15,5,4,6,7,2,3,1,0 then shifted one way or the other which I'll work out tomorrow with a clear head!

Either
11,9,8,13,12,14,15,5,4,6,7,2,3,1,0,10 or
0,10,11,9,8,13,12,14,15,5,4,6,7,2,3,1

Link to comment
Share on other sites

My feeling is that it's more maintainable to keep the gray code table consistent with what one would expect and then do a little maths to get the direction correct than to reorder the gray codes and then find in a couple of years time when you come back to make some changes that you can't remember what on Earth you were doing.

Also, should you need to change the north direction, say, at some point in the future, it's far more straightforward to change the line that handles the offset than it is to reorder the gray code table.

James

Link to comment
Share on other sites

The two lines you are removing change the values in the array, not their positions.
So I think you want to change
0,1,3,2,7,6,4,5,15,14,12,13,8,9,11,10 
to
14,13,11,12,7,8,10,9,15,0,2,1,6,5,3,4 

As you say, a clear head tomorrow will make it clearer.

Link to comment
Share on other sites

12 minutes ago, JamesF said:

My feeling is that it's more maintainable to keep the gray code table consistent with what one would expect and then do a little maths to get the direction correct than to reorder the gray codes and then find in a couple of years time when you come back to make some changes that you can't remember what on Earth you were doing.

Also, should you need to change the north direction, say, at some point in the future, it's far more straightforward to change the line that handles the offset than it is to reorder the gray code table.

James

Yes, that was my thinking in the first place.  The reason for the two separate lines - one for orientation and the other for offset.

As for changing the North direction, I was wondering whether to add code to enable this to be changed remotely via MQTT.

Link to comment
Share on other sites

10 minutes ago, globular said:

The two lines you are removing change the values in the array, not their positions.
So I think you want to change
0,1,3,2,7,6,4,5,15,14,12,13,8,9,11,10 
to
14,13,11,12,7,8,10,9,15,0,2,1,6,5,3,4 

As you say, a clear head tomorrow will make it clearer.

Oh yes, you're right - the array is arranged in Gray code order not binary.  Agreed!!  Not touching it - too much trouble!!

Link to comment
Share on other sites

Code changed to

//Get instantaneous wind direction - variable dirn
void getDirection(){    
  int dirn = 0;
  // Read wind vane optical sensor values
  dir1 = analogRead(An1);
  dir2 = analogRead(An2);
  dir3 = analogRead(An3);
  dir4 = analogRead(An4);
  // Convert Gray bits to integer
  int Gray = 0;
  if (dir1 > 2000) {Gray = 8;};
  if (dir2 > 2000) {Gray += 4;};
  if (dir3 > 2000) {Gray += 2;};
  if (dir4 > 2000) {Gray += 1;};
  //  Convert Gray to binary
  dirn = 15 - codeArray[Gray]; // correct rotation direction
  dirn = (dirn + northOffset) %16; // correct encoder for North
  ++bin[dirn]; // increment appropriate bin for Consensus Averaging
  sendDirectionMessageInst(dirn); // send instantaneous direction 
}

with this at the beginning of the sketch

// Set value for North offset correction
int northOffset = 15; // use a value between 0 and 15
Edited by Gina
  • Like 1
Link to comment
Share on other sites

33 minutes ago, Gina said:

with this at the beginning of the sketch


// Set value for North offset correction
int northOffset = 15; // use a value between 0 and 15

This I like.  It's a much neater way of making it configurable and removes "magic numbers" from the code.

James

  • Thanks 1
Link to comment
Share on other sites

I think it should be easy enough to remote control the North Offset via MQTT.

In Node-RED
1889722615_Screenshotfrom2020-09-0622-51-31.png.2d7ef1cd952b2fca9e0dc7f12366b42d.png

This shows a numeric control in the Dashboard
713396741_Screenshotfrom2020-09-0622-53-36.png.bac3e173e9cd82be370d872baabacc1a.png

Then I just have to read the message in the sketch and convert it to an integer.  I'll look at that tomorrow.

Link to comment
Share on other sites

Running a test for remote number setting via MQTT.  Changed the topic from wind/northoffset to tes/northoffset and set up a test client on a new ESP32.  Changed the number in Dashboard and watched the Serial Monitor on the testClient.  Test successful.

void callback(char* topic, byte* message, unsigned int length) {
  Serial.print("Message arrived on topic: ");
  Serial.print(topic);
  Serial.print(". Message: ");
  String messageTemp;
  
  for (int i = 0; i < length; i++) {
    Serial.print((char)message[i]);
    messageTemp += (char)message[i];
  }
  Serial.println();

  if (String(topic) == "test/northoffset") {
    Serial.print("Message String : ");
    Serial.println(messageTemp);
    Serial.print("Number : ");
    Serial.println(messageTemp.toInt());
  }
}

61982302_Screenshotfrom2020-09-0711-50-20.thumb.png.12c6e13e6001956536ee4a3da9e5551b.png

Link to comment
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now
  • Recently Browsing   0 members

    • No registered users viewing this page.
×
×
  • Create New...

Important Information

We have placed cookies on your device to help make this website better. You can adjust your cookie settings, otherwise we'll assume you're okay to continue. By using this site, you agree to our Terms of Use.