Jump to content

NLCbanner2024.jpg.2478be509670e60c2d6efd04834b8b47.jpg

Weather Station Ideas


Gina

Recommended Posts

This would enable me to set the North direction remotely if I decide to turn the wind sensor mast to move the wind vane out of the view from the mount.  Anyone see any disadvantage to doing this?  I might repeat the offset value back to the Dashboard as confirmation.

Link to comment
Share on other sites

This is the new sketch.

// Wind speed and direction 2020-09-07

/*********
  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 value for North offset correction
int northOffset = 15; // use a value between 0 and 15

// 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};  // index Gray (local)

// 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};  // indexes dirn, i, I (local)

// 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();

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

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
      client.subscribe("wind/northoffset");
    } 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
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 
}
//
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 sendNorthOffset(){
  // Convert the number to a char array
  char numString[8];
  dtostrf(northOffset, 1, 0, numString);
  Serial.print("northOffset: ");
  Serial.println(numString);
  client.publish("wind/northoffset/confirm", numString);
} 
void speed20sum(){
  sendringIndex();
  sendNorthOffset();
//  sendNumberMessage2(Number);
//  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 >= 20) {speed20sum(); 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

Wind measuring rig taken down and brought indoors.  Taken apart and new sketch uploaded to ESP32, put back together and a quick test to check it's working before taking it back out and attaching to obsy corner post.  Powered up and working again.

1887663972_Screenshotfrom2020-09-0719-42-46.thumb.png.79649cf34d7f65579f7795ee0b2ab86c.png

Link to comment
Share on other sites

With a steadier wind direction today I have been able to correct the North offset by 2 more points.  We have had light and variable winds since getting the wind sensors working properly and it needs a nice steady breeze to set the North Offset more accurately.

1451992251_Screenshotfrom2020-09-1711-18-18.thumb.png.3efb6b4b1fdcdeedfa48fa2c19b1ca40.png

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

  • 4 weeks later...
13 minutes ago, Gina said:

I'm sure some of the MQTT & NodeRED experts out there have the answer 👍

Do you generate a time stamp in the esp devices?  I use the Rtc time module and take the time from the router.

  • Thanks 1
Link to comment
Share on other sites

2 hours ago, Gina said:

What I need to know is how to display it in my NodeRED Dashboard.

Sorry, I don’t know NodeRed. Atm I’m playing with influxdb and grafana. If I can get the esp to post its data directly into influxdb, I may be able to skip Mqtt altogether. Wireless udp as opposed to tcp may be the way forward. Udp doesn’t use connections, and there’s a risk that messages are lost. But the traffic is very low anyway, and an occasional lost message is no big deal.

  • Thanks 1
Link to comment
Share on other sites

I'm not a dashboard expert, but a couple of suggestions/points/questions:

- What timestamp do you want to show? The current system time on your node-red server, or the time a reading was generated on your sensor ESP? I assume the former.

- I've created a simple Text element on a blank dashboard, and injected a timestamp into it.

image.png.ca824f69cc2a4e67b148f05ce7e65f5e.png

- You would need to do some Javascript formatting in a Function node to convert the timestamp into a readable date/time format. Also, I think change the Inject node to a repeat interval every x seconds to align with your dashboard refresh period. If x is less than the dashboard refresh, the timestamp will never be too much out of date.

- Actually, I just set it to repeat every 1 second and it forces a refresh on the dashboard every second. You probably don't need that level of precision! 

Would that work for you?

image.png.34f29715fe4d488eab1532b828fa3bc2.png

  • Thanks 1
Link to comment
Share on other sites

I think the answer may be to generate the date/time string in one of the clients and send the text to the Dashboard.  I already create text from input to the client for my ROR automation which will use the same system.

Link to comment
Share on other sites

2 hours ago, Gina said:

I don't have those in NodeRED.

Hi Gina - not sure what you're referring to here? The Inject and Text nodes?

2 hours ago, Gina said:

I think the answer may be to generate the date/time string in one of the clients and send the text to the Dashboard.  I already create text from input to the client for my ROR automation which will use the same system.

I was thinking about this again last night, and I see three possible entry points.

1. As you say, get the sensor to generate the date/time string. Then it's just a sensor reading that you want to display as Text on your dashboard.
Pros: It's an accurate representation of the time when the reading was taken, not when the server received/displayed it.
Cons: You need your sensor node to be able to tell the time, and to be properly time synced. If it's an ESP with NTP client on it, that's fine. If it's a simple sensor with a RTC, that may also be fine.
You will have a different date/time string for each sensor, so should probably display the date/time for each reading/group or readings; or display the most recent timestamp only.

2. Generate the date/time string from the current time on Node Red when it receives the mqtt message. Something like this:

image.png.ef7f3b1c5173e11628306fed646227f1.png

Pros: Fairly accurate representation of the sensor time - shows the time the message was received by the server.
Updates the dashboard every time a message is received; no more, no less.
Shows the date/time the last message was received, so is a very good indicator of the dashboard 'freshness'
Cons: Doesn't show the exact time the measurement was taken

3. As I posted earlier: Generate a timestamp every n seconds directly into the dashboard Text element.
Pros: Very simple to do
Cons: Is just like having a clock on the dashboard - doesn't actually represent the time of receipt of any message.
Doesn't give any indication of dashboard 'freshness'. (so really not a good idea after all!)

 

  • Thanks 1
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.