BC Robotics

Raspberry Pi Pico Weather Station – Part 3

  PRODUCT TUTORIAL

Part 2 of this tutorial consisted of two different versions depending on your preferred programming language. In both the Arduino and MicroPython versions we set up the majority of the code and began pulling data from each of the sensors. If you have not yet completed Part 1 and Part 2 of the tutorial, please complete these before proceeding further.

In Part 3 of this tutorial series, we are going to figure out how to log our weather station’s data and display it on live updating graphs using a free web-based data service called ThingSpeak. ThingSpeak stores any data you send it and logs the time it receives it. Their service also allows you to create visualizations and data analysis – a very powerful tool! We are just going to touch on a few of the capabilities, so don’t be afraid to play around with ThingSpeak! 

Step 1 – Create a ThingSpeak account

Before we modify any of our code, or send anything anywhere, we need to go and create a free account at ThingSpeak. Jump on over to their site and generate an account.

Once your account is verified, log in. 

9%

Step 2 – Create a Channel

Now that we have an account set up we need to create a new channel to host and display our data. A ThingSpeak channel can have up to 8 different parameters sent to it. Since we only have 6 data parameters we are sending out, we will only need one channel for this project. Go ahead and click the “New Channel” button.

18%

Step 3 – Configure your Channel

We are going to fill out each of the fields as shown. It is important that each field is the correct parameter – so field 1 needs to be temperature, field 2 needs to be humidity, etc. Aside from that: feel free to give your channel and appropriate name and fill out whatever information you want to share about your project. Once you are done, click Save at the bottom and your channel will be created.

28%

Step 4 – Blank Channel

You should now see your channel, there isn’t much to see as there is no data for the graphs to plot… But we will get there! For now we are going to click the API Key tab at the top and get the necessary information we need to start sending data.

37%

Step 5 – Get Your API Key

The API key is a unique key – this is used to identify data being sent to ThingSpeak as yours – it can be thought of as a unique, somewhat secure address. Don’t share this key – bored people could use this to send false data to your channel. Copy this key down; we are going to need it in the next steps as we write the Arduino or Python code to send our data to ThinkSpeak.

46%

Step 6a - Arduino Code

If you were working through this project in Arduino, we only have a few things to change / add in our existing code. Start by opening your existing project in the Arduino IDE and ensure your Pico is connected.

First, we are going to import the Wifi library, set up a few static variables, and create the connection. You will need to paste your API Write Key into the quotes at “APIKEYHERE” and your WiFi credentials into the “SSID” and “PASSWORD” fields. 

Add the connection code just below the BME280 connection code, at the end of the core0 setup function. 

				
					#include <microDS18B20.h>
#include <Adafruit_Sensor.h>
#include <Adafruit_BME280.h>
#include <Wire.h>
#include <WiFi.h>

// Replace with your WiFi SSID and password
const char* ssid = "NETWORKNAME";
const char* password = "PASSWORD";

// Replace with your ThingSpeak API key
const char* apiKey = "YOURAPIKEY";

// Replace with your ThingSpeak Channel Number
unsigned long myChannelNumber = 12345;

WiFiClient client;

//Calculated Values
double temperature;
double humidity;
double pressure;
double caseTemperature;
double measuredWind;
String windHeading;
double rainfall;

//DS18B20
MicroDS18B20<2> ds;

//BME280
unsigned status;
Adafruit_BME280 bme;

//Multicore Flag
boolean readData = false;

//Rain Gauge
int rainFlag = 1;
int rainCount = 0;

//Anemometer
int windFlag = 1;
int windCount = 0;
unsigned long startTime;

void setup() {
  // put your setup code here, to run once:
  Serial.begin(115200); //Connect to Serial
  while (!Serial);      //Pause code while we wait for Serial to connect

  //Configure the BME
  Wire.setSDA(16);
  Wire.setSCL(17);

  status = bme.begin(0x77, &Wire);
  if (!status) {
    Serial.println("Could not find a valid BME280 sensor, check wiring, address, sensor ID!");
    while (1) delay(10);
  }
  
  // Connect to WiFi
  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");
}

void loop() {
  // put your main code here, to run repeatedly:
  delay(15000); //Wait 15 seconds
  collectData(); // Run our data collection function

  //Print our results to the serial monitor
  Serial.print("Temperature: ");
  Serial.print(temperature);
  Serial.println("°C");

  Serial.print("Humidity: ");
  Serial.print(humidity);
  Serial.println("%");

  Serial.print("Barometric Pressure: ");
  Serial.print(pressure);
  Serial.println(" kPa");

  Serial.print("Case Temperature: ");
  Serial.print(caseTemperature);
  Serial.println("°C");

  Serial.print("Wind: ");
  Serial.print(windHeading);
  Serial.print(" ");
  Serial.print(measuredWind);
  Serial.println(" km/h");

  Serial.print("Rainfall Measured: ");
  Serial.print(rainfall);
  Serial.println("mm");

  Serial.println();
}

void setup1() {
  // setup your code here for the second core
  pinMode(3, INPUT_PULLUP); //Rain
  rainFlag = digitalRead(3);

  pinMode(4, INPUT_PULLUP); //Anemometer
  windFlag = digitalRead(4);
  startTime = millis(); //Set the intitial start time
}

void loop1() {
  //put your main code here, to run repeatedly on the second core:
  if (readData == false) {

    //Rain Gauge
    int rainInput = digitalRead(3);   //Read the rain sensor input
    if (rainInput == LOW && rainFlag == HIGH) //Compare to our flag
    {
      rainCount++;  //The sensor has transited low, increase our count by 1
    }
    else {} //Otherwise dont do anything

    rainFlag = rainInput;    //Set our flag to match our input


    //Anemometer
    int windInput = digitalRead(4); //Read the Anemeometer input
    if (windInput == LOW && windFlag == HIGH) //Compare to our flag
    {
      windCount++;  //The sensor has transited low, increase our count by 1
    }
    else {} //Otherwise dont do anything

    windFlag = windInput;    //Set our flag to match our input

  }
  else {}
}


//Function to collect all of our data
void collectData() {

  //DS18B20 Temperature
  ds.requestTemp();   //Request temperature from the sensor
  delay(1000);        //Wait 1 second for a response

  //If the sensor retrives data, write it to our temperature variable. Otherwise, return an error message
  if (ds.readTemp())  {
    temperature = ds.getTemp();
  }
  else {
    //Sensor not connected
    Serial.println("DS18B20 Not Connected");
  }

  //BME280
  humidity = bme.readHumidity();
  pressure = bme.readPressure() / 1000; //Convert to kPa by dividing the result by 1000.
  caseTemperature = bme.readTemperature();

  //Wind Direction
  windHeading = calculateWindDirection();

  //Core 2 Values
  readData = true; //Pause count in Core 2
  delay(1);

  //Rainfall
  rainfall = rainCount * 0.2794; //Calculate how many mm of rain (count * tipping gauge volume in mm)
  rainCount = 0;  //Reset the rainfall count

  //Wind Speed

  unsigned long sampleDuration = millis() - startTime; //Calculate the duration of the measurement
  double duration = sampleDuration / 1000; //Convert to seconds from milliseconds
  measuredWind = (double)(windCount / duration) * 2.4;   //Figure out the average count per second, multiply by 2.4km/h (1 count/second = 2.4km/h)
  windCount = 0; //Reset the wind count

  readData = false; //Restart Count in Core 2
  startTime = millis(); //Grab the start time
}

//Calculate our wind direction
String calculateWindDirection()
{
  String s = "N/A";
  int reading = analogRead(A0);

  if (reading >= 250 && reading <= 284) {
    s = "ESE";
  } else if (reading >= 285 && reading <= 304) {
    s = "ENE";
  } else if (reading >= 305 && reading <= 324) {
    s = "E";
  } else if (reading >= 325 && reading <= 374) {
    s = "SSE";
  } else if (reading >= 375 && reading <= 450) {
    s = "SE";
  } else if (reading >= 451 && reading <= 509) {
    s = "SSW";
  } else if (reading >= 510 && reading <= 549) {
    s = "S";
  } else if (reading >= 550 && reading <= 649) {
    s = "NNE";
  } else if (reading >= 650 && reading <= 724) {
    s = "NE";
  } else if (reading >= 725 && reading <= 797) {
    s = "WSW";
  } else if (reading >= 798 && reading <= 824) {
    s = "SW";
  } else if (reading >= 825 && reading <= 874) {
    s = "NNW";
  } else if (reading >= 875 && reading <= 909) {
    s = "N";
  } else if (reading >= 910 && reading <= 934) {
    s = "WNW";
  } else if (reading >= 935 && reading <= 974) {
    s = "NW";
  } else if (reading >= 975 && reading <= 1023) {
    s = "W";
  } else {
    s = "N/A";
  }
  return s;
}
				
			

Next, you will need to install the ThingSpeak library from the Arduino Library Manager. Just as we did in the last part of the tutorial, this is done through Sketch > Include Library > Manage Libraries

Once that library has installed, we can add it to our code. Include the library and initialize ThingSpeak right after our WiFi connection in the setup function

				
					#include <microDS18B20.h>
#include <Adafruit_Sensor.h>
#include <Adafruit_BME280.h>
#include <Wire.h>
#include <WiFi.h>
#include <ThingSpeak.h>

// Replace with your WiFi SSID and password
const char* ssid = "NETWORKNAME";
const char* password = "PASSWORD";

// Replace with your ThingSpeak API key
const char* apiKey = "YOURAPIKEY";

// Replace with your ThingSpeak Channel Number
unsigned long myChannelNumber = 12345;

WiFiClient client;

//Calculated Values
double temperature;
double humidity;
double pressure;
double caseTemperature;
double measuredWind;
String windHeading;
double rainfall;

//DS18B20
MicroDS18B20<2> ds;

//BME280
unsigned status;
Adafruit_BME280 bme;

//Multicore Flag
boolean readData = false;

//Rain Gauge
int rainFlag = 1;
int rainCount = 0;

//Anemometer
int windFlag = 1;
int windCount = 0;
unsigned long startTime;

void setup() {
  // put your setup code here, to run once:
  Serial.begin(115200); //Connect to Serial
  while (!Serial);      //Pause code while we wait for Serial to connect

  //Configure the BME
  Wire.setSDA(16);
  Wire.setSCL(17);

  status = bme.begin(0x77, &Wire);
  if (!status) {
    Serial.println("Could not find a valid BME280 sensor, check wiring, address, sensor ID!");
    while (1) delay(10);
  }
  
  // Connect to WiFi
  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");
  
  // Initialize ThingSpeak
  ThingSpeak.begin(client);
}

void loop() {
  // put your main code here, to run repeatedly:
  delay(15000); //Wait 15 seconds
  collectData(); // Run our data collection function

  //Print our results to the serial monitor
  Serial.print("Temperature: ");
  Serial.print(temperature);
  Serial.println("°C");

  Serial.print("Humidity: ");
  Serial.print(humidity);
  Serial.println("%");

  Serial.print("Barometric Pressure: ");
  Serial.print(pressure);
  Serial.println(" kPa");

  Serial.print("Case Temperature: ");
  Serial.print(caseTemperature);
  Serial.println("°C");

  Serial.print("Wind: ");
  Serial.print(windHeading);
  Serial.print(" ");
  Serial.print(measuredWind);
  Serial.println(" km/h");

  Serial.print("Rainfall Measured: ");
  Serial.print(rainfall);
  Serial.println("mm");

  Serial.println();
}

void setup1() {
  // setup your code here for the second core
  pinMode(3, INPUT_PULLUP); //Rain
  rainFlag = digitalRead(3);

  pinMode(4, INPUT_PULLUP); //Anemometer
  windFlag = digitalRead(4);
  startTime = millis(); //Set the intitial start time
}

void loop1() {
  //put your main code here, to run repeatedly on the second core:
  if (readData == false) {

    //Rain Gauge
    int rainInput = digitalRead(3);   //Read the rain sensor input
    if (rainInput == LOW && rainFlag == HIGH) //Compare to our flag
    {
      rainCount++;  //The sensor has transited low, increase our count by 1
    }
    else {} //Otherwise dont do anything

    rainFlag = rainInput;    //Set our flag to match our input


    //Anemometer
    int windInput = digitalRead(4); //Read the Anemeometer input
    if (windInput == LOW && windFlag == HIGH) //Compare to our flag
    {
      windCount++;  //The sensor has transited low, increase our count by 1
    }
    else {} //Otherwise dont do anything

    windFlag = windInput;    //Set our flag to match our input

  }
  else {}
}


//Function to collect all of our data
void collectData() {

  //DS18B20 Temperature
  ds.requestTemp();   //Request temperature from the sensor
  delay(1000);        //Wait 1 second for a response

  //If the sensor retrives data, write it to our temperature variable. Otherwise, return an error message
  if (ds.readTemp())  {
    temperature = ds.getTemp();
  }
  else {
    //Sensor not connected
    Serial.println("DS18B20 Not Connected");
  }

  //BME280
  humidity = bme.readHumidity();
  pressure = bme.readPressure() / 1000; //Convert to kPa by dividing the result by 1000.
  caseTemperature = bme.readTemperature();

  //Wind Direction
  windHeading = calculateWindDirection();

  //Core 2 Values
  readData = true; //Pause count in Core 2
  delay(1);

  //Rainfall
  rainfall = rainCount * 0.2794; //Calculate how many mm of rain (count * tipping gauge volume in mm)
  rainCount = 0;  //Reset the rainfall count

  //Wind Speed

  unsigned long sampleDuration = millis() - startTime; //Calculate the duration of the measurement
  double duration = sampleDuration / 1000; //Convert to seconds from milliseconds
  measuredWind = (double)(windCount / duration) * 2.4;   //Figure out the average count per second, multiply by 2.4km/h (1 count/second = 2.4km/h)
  windCount = 0; //Reset the wind count

  readData = false; //Restart Count in Core 2
  startTime = millis(); //Grab the start time
}

//Calculate our wind direction
String calculateWindDirection()
{
  String s = "N/A";
  int reading = analogRead(A0);

  if (reading >= 250 && reading <= 284) {
    s = "ESE";
  } else if (reading >= 285 && reading <= 304) {
    s = "ENE";
  } else if (reading >= 305 && reading <= 324) {
    s = "E";
  } else if (reading >= 325 && reading <= 374) {
    s = "SSE";
  } else if (reading >= 375 && reading <= 450) {
    s = "SE";
  } else if (reading >= 451 && reading <= 509) {
    s = "SSW";
  } else if (reading >= 510 && reading <= 549) {
    s = "S";
  } else if (reading >= 550 && reading <= 649) {
    s = "NNE";
  } else if (reading >= 650 && reading <= 724) {
    s = "NE";
  } else if (reading >= 725 && reading <= 797) {
    s = "WSW";
  } else if (reading >= 798 && reading <= 824) {
    s = "SW";
  } else if (reading >= 825 && reading <= 874) {
    s = "NNW";
  } else if (reading >= 875 && reading <= 909) {
    s = "N";
  } else if (reading >= 910 && reading <= 934) {
    s = "WNW";
  } else if (reading >= 935 && reading <= 974) {
    s = "NW";
  } else if (reading >= 975 && reading <= 1023) {
    s = "W";
  } else {
    s = "N/A";
  }
  return s;
}
				
			

Finally, we will take our sensor data and use the ThingSpeak library to format our data and send it to ThingSpeak. Ensure each ‘field’ matches however you have it set up in ThingSpeak.

ThingSpeak has a data limit of one message per 15 seconds, so we have located this within our primary loop, where we originally created the 15 second delay.

				
					#include <microDS18B20.h>
#include <Adafruit_Sensor.h>
#include <Adafruit_BME280.h>
#include <Wire.h>
#include <WiFi.h>
#include <ThingSpeak.h>

// Replace with your WiFi SSID and password
const char* ssid = "NETWORKNAME";
const char* password = "PASSWORD";

// Replace with your ThingSpeak API key
const char* apiKey = "YOURAPIKEY";

// Replace with your ThingSpeak Channel Number
unsigned long myChannelNumber = 12345;

WiFiClient client;

//Calculated Values
double temperature;
double humidity;
double pressure;
double caseTemperature;
double measuredWind;
String windHeading;
double rainfall;

//DS18B20
MicroDS18B20<2> ds;

//BME280
unsigned status;
Adafruit_BME280 bme;

//Multicore Flag
boolean readData = false;

//Rain Gauge
int rainFlag = 1;
int rainCount = 0;

//Anemometer
int windFlag = 1;
int windCount = 0;
unsigned long startTime;

void setup() {
  // put your setup code here, to run once:
  Serial.begin(115200); //Connect to Serial
  while (!Serial);      //Pause code while we wait for Serial to connect

  //Configure the BME
  Wire.setSDA(16);
  Wire.setSCL(17);

  status = bme.begin(0x77, &Wire);
  if (!status) {
    Serial.println("Could not find a valid BME280 sensor, check wiring, address, sensor ID!");
    while (1) delay(10);
  }
  
  // Connect to WiFi
  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");
  
  // Initialize ThingSpeak
  ThingSpeak.begin(client);
}

void loop() {
  // put your main code here, to run repeatedly:
  delay(15000); //Wait 15 seconds
  collectData(); // Run our data collection function

  /*
  //Print our results to the serial monitor
  Serial.print("Temperature: ");
  Serial.print(temperature);
  Serial.println("°C");

  Serial.print("Humidity: ");
  Serial.print(humidity);
  Serial.println("%");

  Serial.print("Barometric Pressure: ");
  Serial.print(pressure);
  Serial.println(" kPa");

  Serial.print("Case Temperature: ");
  Serial.print(caseTemperature);
  Serial.println("°C");

  Serial.print("Wind: ");
  Serial.print(windHeading);
  Serial.print(" ");
  Serial.print(measuredWind);
  Serial.println(" km/h");

  Serial.print("Rainfall Measured: ");
  Serial.print(rainfall);
  Serial.println("mm");

  Serial.println();
  */
  
  //Set each field
  ThingSpeak.setField(1, (float)temperature);
  ThingSpeak.setField(2, (float)humidity);
  ThingSpeak.setField(3, (float)pressure);
  ThingSpeak.setField(4, (float)measuredWind);
  ThingSpeak.setField(5, windHeading);
  ThingSpeak.setField(6, (float)rainfall);
  
  int result = ThingSpeak.writeFields(myChannelNumber, apiKey);
  if (result == 200) {
    Serial.println("Success");
  } else {
    Serial.println("Failed");
  }
}

void setup1() {
  // setup your code here for the second core
  pinMode(3, INPUT_PULLUP); //Rain
  rainFlag = digitalRead(3);

  pinMode(4, INPUT_PULLUP); //Anemometer
  windFlag = digitalRead(4);
  startTime = millis(); //Set the intitial start time
}

void loop1() {
  //put your main code here, to run repeatedly on the second core:
  if (readData == false) {

    //Rain Gauge
    int rainInput = digitalRead(3);   //Read the rain sensor input
    if (rainInput == LOW && rainFlag == HIGH) //Compare to our flag
    {
      rainCount++;  //The sensor has transited low, increase our count by 1
    }
    else {} //Otherwise dont do anything

    rainFlag = rainInput;    //Set our flag to match our input


    //Anemometer
    int windInput = digitalRead(4); //Read the Anemeometer input
    if (windInput == LOW && windFlag == HIGH) //Compare to our flag
    {
      windCount++;  //The sensor has transited low, increase our count by 1
    }
    else {} //Otherwise dont do anything

    windFlag = windInput;    //Set our flag to match our input

  }
  else {}
}


//Function to collect all of our data
void collectData() {

  //DS18B20 Temperature
  ds.requestTemp();   //Request temperature from the sensor
  delay(1000);        //Wait 1 second for a response

  //If the sensor retrives data, write it to our temperature variable. Otherwise, return an error message
  if (ds.readTemp())  {
    temperature = ds.getTemp();
  }
  else {
    //Sensor not connected
    Serial.println("DS18B20 Not Connected");
  }

  //BME280
  humidity = bme.readHumidity();
  pressure = bme.readPressure() / 1000; //Convert to kPa by dividing the result by 1000.
  caseTemperature = bme.readTemperature();

  //Wind Direction
  windHeading = calculateWindDirection();

  //Core 2 Values
  readData = true; //Pause count in Core 2
  delay(1);

  //Rainfall
  rainfall = rainCount * 0.2794; //Calculate how many mm of rain (count * tipping gauge volume in mm)
  rainCount = 0;  //Reset the rainfall count

  //Wind Speed

  unsigned long sampleDuration = millis() - startTime; //Calculate the duration of the measurement
  double duration = sampleDuration / 1000; //Convert to seconds from milliseconds
  measuredWind = (double)(windCount / duration) * 2.4;   //Figure out the average count per second, multiply by 2.4km/h (1 count/second = 2.4km/h)
  windCount = 0; //Reset the wind count

  readData = false; //Restart Count in Core 2
  startTime = millis(); //Grab the start time
}

//Calculate our wind direction
String calculateWindDirection()
{
  String s = "N/A";
  int reading = analogRead(A0);

  if (reading >= 250 && reading <= 284) {
    s = "ESE";
  } else if (reading >= 285 && reading <= 304) {
    s = "ENE";
  } else if (reading >= 305 && reading <= 324) {
    s = "E";
  } else if (reading >= 325 && reading <= 374) {
    s = "SSE";
  } else if (reading >= 375 && reading <= 450) {
    s = "SE";
  } else if (reading >= 451 && reading <= 509) {
    s = "SSW";
  } else if (reading >= 510 && reading <= 549) {
    s = "S";
  } else if (reading >= 550 && reading <= 649) {
    s = "NNE";
  } else if (reading >= 650 && reading <= 724) {
    s = "NE";
  } else if (reading >= 725 && reading <= 797) {
    s = "WSW";
  } else if (reading >= 798 && reading <= 824) {
    s = "SW";
  } else if (reading >= 825 && reading <= 874) {
    s = "NNW";
  } else if (reading >= 875 && reading <= 909) {
    s = "N";
  } else if (reading >= 910 && reading <= 934) {
    s = "WNW";
  } else if (reading >= 935 && reading <= 974) {
    s = "NW";
  } else if (reading >= 975 && reading <= 1023) {
    s = "W";
  } else {
    s = "N/A";
  }
  return s;
}
				
			

That’s it for coding changes, uploading this code to the Pico W should now update your ThingSpeak channel every 15 seconds. In the next few steps we will look at configuring this data.

55%

Step 6b - MicroPython Code

If you were working through this project in MicroPython, we only have a few things to change / add in our existing code. Start by opening your existing project in Thonny and ensure your Pico is connected.

First, we are going to import the necessary libraries, set up a few static variables, and create the connection. You will need to paste your API Write Key into the quotes at “APIKEYHERE” and your WiFi credentials into the “SSID” and “PASSWORD” fields. 

				
					import onewire, ds18x20, time, bme280, utime, _thread
import urequests, network
from machine import Pin, I2C, ADC

spLock = _thread.allocate_lock() # creating semaphore

HTTP_HEADERS = {'Content-Type': 'application/json'} 
THINGSPEAK_WRITE_API_KEY = 'APIKEYHERE' #Enter your Write api key here  

#Network Connection
ssid = 'NETWORKNAME'  #Enter your SSID here
password = '123ABC'   #Enter your Network Password

#Pico W Wifi Connection
sta_if=network.WLAN(network.STA_IF)
sta_if.active(True)

if not sta_if.isconnected():
    print('Connecting...')
    sta_if.connect(ssid, password)
    while not sta_if.isconnected():
     pass
print('Network configuration:', sta_if.ifconfig()) 

#Global count variables
windCount = 0
rainCount = 0

#Create Core 0 Sensors

#DS18B20
tempSensor = Pin(2, Pin.IN) #Assign the sensor bus to pin 2
sensor = ds18x20.DS18X20(onewire.OneWire(tempSensor)) #create the sensor object
roms = sensor.scan() #scan the bus for sensors

#BME280
i2c=I2C(0,sda=Pin(16), scl=Pin(17), freq=400000) #assign the I2C bus to pins 16, 17 (Qwiic Connector)
bme = bme280.BME280(i2c=i2c, address=0x77) #Create the BME object

#Wind Vane
windDir = ADC(Pin(26)) #Assign the Wind Vane to ADC0 (Pin 26)


#Calculate Wind Direction and return as a a string
def calculate_wind_direction():
    s = "N/A"
    reading = windDir.read_u16() / 64 #Read A0, convert to 10-bit (0-1023)

    if 250 <= reading <= 284:
        s = "ESE"
    elif 285 <= reading <= 304:
        s = "ENE"
    elif 305 <= reading <= 324:
        s = "E"
    elif 325 <= reading <= 374:
        s = "SSE"
    elif 375 <= reading <= 450:
        s = "SE"
    elif 451 <= reading <= 509:
        s = "SSW"
    elif 510 <= reading <= 549:
        s = "S"
    elif 550 <= reading <= 649:
        s = "NNE"
    elif 650 <= reading <= 724:
        s = "NE"
    elif 725 <= reading <= 797:
        s = "WSW"
    elif 798 <= reading <= 824:
        s = "SW"
    elif 825 <= reading <= 874:
        s = "NNW"
    elif 875 <= reading <= 909:
        s = "N"
    elif 910 <= reading <= 934:
        s = "WNW"
    elif 935 <= reading <= 974:
        s = "NW"
    elif 975 <= reading <= 1023:
        s = "W"
    else:
        s = "N/A"

    return s  #Return our wind direction

#Core 1 handles our monitoring of the Rain Gauge and Anemometer and hosts the Core 1 sensors (Rain Gauge, Anemometer)
def core1_task():

    #Rain Gauge
    rainInput = Pin(3, Pin.IN, Pin.PULL_UP)
    rainFlag = 0

    #Anemometer
    windInput = Pin(4, Pin.IN, Pin.PULL_UP)
    windFlag = 0

    while True:
        spLock.acquire() # Acquire semaphore lock

        #Rain Gauge
        if(rainInput.value() == 0 and rainFlag == 1): #Compare to our flag to look for a LOW transit
            global rainCount #Ensure we write to the global count variable
            rainCount += 1 #Since the sensor has transited low, increase the count by 1

        rainFlag = rainInput.value() #Set our flag to match our input


        #Anemometer
        if(windInput.value() ==  0 and windFlag == 1): #Compare to our flag to look for a LOW transit
            global windCount #Ensure we write to the global count variable
            windCount += 1 #Since the sensor has transited low, increase the count by 1

        windFlag = windInput.value() #Set our flag to match our input

        utime.sleep(0.01) # 0.01 sec or 10us delay
        spLock.release()

#Start Core 1
_thread.start_new_thread(core1_task, ())

#Main Loop
while True:
    utime.sleep(15) #Wait 15 Seconds
    spLock.acquire() #Acquire semaphore lock

    #Get DS18B20 Temperature
    sensor.convert_temp()
    temperature = round(sensor.read_temp(roms[0]),1) #Read first sensor in array

    #Read BME280 Values
    humidity = bme.read_compensated_data()[2] / 1024
    pressure = (bme.read_compensated_data()[1] // 256) / 1000 #Convert to kPa by dividing the result by 1000
    caseTemperature = bme.read_compensated_data()[0] / 100

    #Get Wind Direction
    windHeading = calculate_wind_direction()

    #Calculate Wind Speed
    measuredWind = (windCount / 15) * 2.4
    windCount = 0

    #Calculate Rainfall
    rainfall = rainCount * 0.2794
    rainCount = 0

    #Print Results
    print("Temperature: " , temperature , "°C")
    print("Humidity: ", humidity , "%")
    print("Barometric Pressure: " , pressure, " kPa")
    print("Case Temperature: " , caseTemperature , "°C")
    print("Wind: " , windHeading , " " , measuredWind, " km/h")
    print("Rainfall Measured: " , rainfall, "mm")
    print(" ")

    spLock.release()
				
			

Next, we are going to comment out the print statements that allowed us to see our sensor data, create a new variable to store the formatted string required by ThingSpeak, and send the data. Ensure each ‘field’ matches however you have it set up in ThingSpeak.

ThingSpeak has a data limit of one message per 15 seconds, so we have located this within our main loop, where we originally created the 15 second delay.

				
					import onewire, ds18x20, time, bme280, utime, _thread
import urequests, network
from machine import Pin, I2C, ADC

spLock = _thread.allocate_lock() # creating semaphore

HTTP_HEADERS = {'Content-Type': 'application/json'} 
THINGSPEAK_WRITE_API_KEY = 'APIKEYHERE' #Enter your Write api key here  

#Network Connection
ssid = 'NETWORKNAME'  #Enter your SSID here
password = '123ABC'   #Enter your Network Password

#Pico W Wifi Connection
sta_if=network.WLAN(network.STA_IF)
sta_if.active(True)

if not sta_if.isconnected():
    print('Connecting...')
    sta_if.connect(ssid, password)
    while not sta_if.isconnected():
     pass
print('Network configuration:', sta_if.ifconfig()) 

#Global count variables
windCount = 0
rainCount = 0

#Create Core 0 Sensors

#DS18B20
tempSensor = Pin(2, Pin.IN) #Assign the sensor bus to pin 2
sensor = ds18x20.DS18X20(onewire.OneWire(tempSensor)) #create the sensor object
roms = sensor.scan() #scan the bus for sensors

#BME280
i2c=I2C(0,sda=Pin(16), scl=Pin(17), freq=400000) #assign the I2C bus to pins 16, 17 (Qwiic Connector)
bme = bme280.BME280(i2c=i2c, address=0x77) #Create the BME object

#Wind Vane
windDir = ADC(Pin(26)) #Assign the Wind Vane to ADC0 (Pin 26)


#Calculate Wind Direction and return as a a string
def calculate_wind_direction():
    s = "N/A"
    reading = windDir.read_u16() / 64 #Read A0, convert to 10-bit (0-1023)

    if 250 <= reading <= 284:
        s = "ESE"
    elif 285 <= reading <= 304:
        s = "ENE"
    elif 305 <= reading <= 324:
        s = "E"
    elif 325 <= reading <= 374:
        s = "SSE"
    elif 375 <= reading <= 450:
        s = "SE"
    elif 451 <= reading <= 509:
        s = "SSW"
    elif 510 <= reading <= 549:
        s = "S"
    elif 550 <= reading <= 649:
        s = "NNE"
    elif 650 <= reading <= 724:
        s = "NE"
    elif 725 <= reading <= 797:
        s = "WSW"
    elif 798 <= reading <= 824:
        s = "SW"
    elif 825 <= reading <= 874:
        s = "NNW"
    elif 875 <= reading <= 909:
        s = "N"
    elif 910 <= reading <= 934:
        s = "WNW"
    elif 935 <= reading <= 974:
        s = "NW"
    elif 975 <= reading <= 1023:
        s = "W"
    else:
        s = "N/A"

    return s  #Return our wind direction

#Core 1 handles our monitoring of the Rain Gauge and Anemometer and hosts the Core 1 sensors (Rain Gauge, Anemometer)
def core1_task():

    #Rain Gauge
    rainInput = Pin(3, Pin.IN, Pin.PULL_UP)
    rainFlag = 0

    #Anemometer
    windInput = Pin(4, Pin.IN, Pin.PULL_UP)
    windFlag = 0

    while True:
        spLock.acquire() # Acquire semaphore lock

        #Rain Gauge
        if(rainInput.value() == 0 and rainFlag == 1): #Compare to our flag to look for a LOW transit
            global rainCount #Ensure we write to the global count variable
            rainCount += 1 #Since the sensor has transited low, increase the count by 1

        rainFlag = rainInput.value() #Set our flag to match our input


        #Anemometer
        if(windInput.value() ==  0 and windFlag == 1): #Compare to our flag to look for a LOW transit
            global windCount #Ensure we write to the global count variable
            windCount += 1 #Since the sensor has transited low, increase the count by 1

        windFlag = windInput.value() #Set our flag to match our input

        utime.sleep(0.01) # 0.01 sec or 10us delay
        spLock.release()

#Start Core 1
_thread.start_new_thread(core1_task, ())

#Main Loop
while True:
    utime.sleep(15) #Wait 15 Seconds
    spLock.acquire() #Acquire semaphore lock

    #Get DS18B20 Temperature
    sensor.convert_temp()
    temperature = round(sensor.read_temp(roms[0]),1) #Read first sensor in array

    #Read BME280 Values
    humidity = bme.read_compensated_data()[2] / 1024
    pressure = (bme.read_compensated_data()[1] // 256) / 1000 #Convert to kPa by dividing the result by 1000
    caseTemperature = bme.read_compensated_data()[0] / 100

    #Get Wind Direction
    windHeading = calculate_wind_direction()

    #Calculate Wind Speed
    measuredWind = (windCount / 15) * 2.4
    windCount = 0

    #Calculate Rainfall
    rainfall = rainCount * 0.2794
    rainCount = 0

    #Print Results
    #print("Temperature: " , temperature , "°C")
    #print("Humidity: ", humidity , "%")
    #print("Barometric Pressure: " , pressure, " kPa")
    #print("Case Temperature: " , caseTemperature , "°C")
    #print("Wind: " , windHeading , " " , measuredWind, " km/h")
    #print("Rainfall Measured: " , rainfall, "mm")
    #print(" ")
    
    sendData = {'field1':temperature, 'field2':humidity, 'field3':pressure, 'field4':measuredWind, 'field5':windHeading, 'field6':rainfall}
    
    request = urequests.post( 'http://api.thingspeak.com/update?api_key=' + THINGSPEAK_WRITE_API_KEY, json = sendData, headers = HTTP_HEADERS )  
    request.close() 

    spLock.release()
				
			

That’s it for coding changes, running this code should now update your ThingSpeak channel every 15 seconds. In the next few steps we will look at configuring this data. 

55%

Step 7– Channel Updating

Now that we have data streaming out to ThingSpeak, jump over to your channel. You should now see data starting to appear on the graphs! By default they will show the last ~ 60 data points (roughly the last 15 minutes when sent at a 15 second interval).

64%

Step 8– Configuring Graphs

Each of the graphs can be configured by clicking the “pencil” logo in the top right corner of the graph. Add the title, X & Y axis labels, change the colors as you see fit. We also recommend setting the minimum value to zero on the wind speed and rainfall graphs to improve the auto formatting of the graph.

There are quite a few other options available as well – you can graph over much longer periods of time, thin the data reporting on the graph out, and even change the type of graph.

73%

Step 9 - Sharing

By default, ThingSpeak Channels are private, but if you wish to share this information with the world, simply jump over to the sharing tab in your Channel and change the setting. If set to public, people can browse your channel, see your data, and you can even host each graph on external webpages. 

82%

Step 10 - External Graphs

To host graphs (and other visualizations) on external sites, ThingSpeak provides code that can be pasted into the HTML of your website. The code can be found by clicking the “chat bubble” logo in the top right of any graph or display widget. You can even create a basic HTML file containing all of these code snippets and host it on your own computer (or the Raspberry Pi itself) to create a basic weather dashboard!

91%

Step 11 – Going Forwards

We are barely scratching the surface of what is possible with ThingSpeak – there are a lot of opportunities to improve how data is displayed and analyzed. You can even go ahead and generate additional information from your data like windchill, humidity, high/low temperature maximums, and others within the MATLAB Analysis tools. The Gauge Widget allows you to create dynamic gauges that display your data. 

With the Weather Station now complete, it will need to be suitable located. Weather Underground has excellent information on placing your weather station – we suggest having a read through it as there are a lot of things to consider ( Weather Underground Tutorial )

Depending on your location, some of these recommendations may not be reasonable. Also, when locating the station, consider that you will have to get power to the station and it will need a reliable WiFi connection.

Moving forwards, additional sensors can be added and additional ThingSpeak channels can be created to collect even more data. Lightning, Air Quality, UV Levels, and more!

100%

6 thoughts on “Raspberry Pi Pico Weather Station – Part 3”

Leave a Reply

Your email address will not be published.

Select the fields to be shown. Others will be hidden. Drag and drop to rearrange the order.
  • Image
  • SKU
  • Rating
  • Price
  • Stock
  • Availability
  • Add to cart
  • Description
  • Content
  • Weight
  • Dimensions
  • Additional information
  • Attributes
  • Custom attributes
  • Custom fields
Click outside to hide the comparison bar
Compare