B4R Tutorial Weather Station using MQTT, UDP and Thingspeak

This is my humble effort to produce a weather station (based on this project). You will need:

1 x Wemos D1 Mini
1 x BME280 Sensor
1 x TP4056 Li-Ion Charging board
1 x 3.7V Li-Ion Battery
1 x Holder for the battery
1 x Solar Panel delivering at least 5V
1 x LED
1 220 ohm Resistor
1 x Jumper (as in PC jumper) or a cable
1 x 70 mm x 90 mm PCB
1 x 1N4007 Diode (to stop battery discharge)
Solder & Iron
Various headers, so you dont solder the Wemos to the PCB.
Screw connectors for the battery and solar cell

Firstly you need to make the PCB. The plan is here:

Fritzing.JPG


The jumper is required otherwise the Wemos will not wake up after deep sleep.

After soldering the board, I put mine in a 3D printed case and hung it up outside.

IMG_20210409_164811.jpg
IMG_20210409_164830.jpg
 
Last edited:

Mark Read

Well-Known Member
Licensed User
Now for the code.

Libraries required: rCore, rESP8266WiFi, rMQTT, rRandomAccessFile and the Library HERE.

Sender Code:
#Region Project Attributes
    #AutoFlushLogs: True
    #CheckArrayBounds: True
    #StackBufferSize: 300
#End Region

'Ctrl+Click to open the C code folder: ide://run?File=%WINDIR%\System32\explorer.exe&Args=%PROJECT%\Objects\Src

'Weather-Station-Solar-Wemos-D1-Ver1 by Mark Read 2021
'Hardware:  Wemos D1 Mini, BME280 - Humidity, Pressure And Temperature
'            with TP4056, Solar Panel And Lit-Ion Battery
'           
'- Serial monitor data And thingspeak also in subs
'- Upload to Thingspean via MQTT
'- Using deep sleep To preserve battery
'- Using UDP For remote data viewing
'   
'Status: under construction
'
'Connecting the BME280 Sensor:
'Sensor              ->  Board
'-----------------------------
'Vin (Voltage In)    ->  3.3V
'Gnd (Ground)        ->  Gnd
'SDA (Serial Data)   ->  D1
'SCK (Serial Clock)  ->  D2

Sub Process_Globals
    Public Serial1 As Serial
    Public WiFi As ESP8266WiFi
    Public Udp As WiFiUDP
    Public WiFiClient As WiFiSocket
    Private MQTT As MqttClient
    Private MQTTOptions As MqttConnectOptions
    
    Public MQTTApiKey As String="xxxxxxxxxxxxxxxxxxxx"                    ' from my Profile
    Public WriteAPIKey As String="xxxxxxxxxxxxxxxxxxxxx"                    ' Channel Settings for my test channel
   
    Public UserName As String="xxxx"                                    ' from my Profile
    Public WifiSSID As String="xxxxxx"
    Public WifiPass As String="xxxxxx"
    Public ThinkspeakServer As String="mqtt.thingspeak.com"
    Private ChannelID As String="xxxxxxxxxx"                            ' Destination channel number
    Private ClientID As String="MyWeatherSender"                    ' anything you wish
    
    Public UDPPort As Int=8888
    Public BroadcastIP() As Byte=Array As Byte(192,168,255,255)
    
    Public WiFiLEDPin As Pin
    Public ResetPin As Pin
    Public LEDPinNumer As Int=12    ' using pin D6 on Wemos
    Public TEMPERATURE, PRESSURE, HUMIDITY As Double
    Public HeatIndex, DewPoint, Altitude As Double
    
    Public BC As ByteConverter
    Public DeepSleepMinutes As Int=15
    
    'Public DelayTimer As Timer
    
    'Coefficents for the environmental calculations
    Dim hi_coeff1 As Double = -42.379
    Dim hi_coeff2  As Double = 2.04901523
    Dim hi_coeff3 As Double = 10.14333127
    Dim hi_coeff4 As Double = -0.22475541
    Dim hi_coeff5 As Double = -0.00683783
    Dim hi_coeff6 As Double = -0.05481717
    Dim hi_coeff7 As Double =  0.00122874
    Dim hi_coeff8 As Double =  0.00085282
    Dim hi_coeff9 As Double = -0.00000199
    
    Dim RefTemperature As Double = 15.0
    Dim RefPressure As Double = 1014.6
    'Dim RefAltitude As Double = 541            ' only used to calculate the equivalent Sea level pressure
    
End Sub

Private Sub AppStart
    Serial1.Initialize(115200)
    Log("AppStart")
    WiFiLEDPin.Initialize(LEDPinNumer, WiFiLEDPin.MODE_OUTPUT)
    ResetPin.Initialize(0, ResetPin.MODE_INPUT_PULLUP)
        
    If WiFi.IsConnected=False Then
        ConnectToNetwork
    End If
    
    'initialise BME280
    RunNative("setup",Null)
    
    'initialise MQTT
    MQTT.Initialize2(WiFiClient.Stream, ThinkspeakServer,1883, ClientID,"MQTT_MessageArrived", "MQTT_Disconnected")
    MQTTOptions.Initialize(UserName, MQTTApiKey)
    
    Log("I am awake ...")
    Private RunTime As ULong=    Millis()        'prepare a subroutine timer
    
    If WiFi.IsConnected=False Then
        ConnectToNetwork
    End If
    
    'get sensor data
    ReadSensor
    
    'calculate environmental values
    EnviroSettings
    
    'update remote viewer
    'BroadcastOverUDP
    
    'upload to thingspeak   
    UploadThinkgspeak
        
    Log("Time for one iteration of the read/write cycle: ", Millis()-RunTime, " ms")
    Log("")
    Log("*******************************************************")
    
    Log("Going to sleep ...")
        
    DeepSleep(DeepSleepMinutes*60*1000)        'pass ms to sub
    
End Sub

Sub UploadThinkgspeak
    Log("Trying to upload ...")
    If MQTT.Connect2(MQTTOptions) Then
        Log("Connected to thingspeak")
        Dim PayLoad, Topic As String
        PayLoad=JoinStrings(Array As String("field1=",TEMPERATURE,"&field2=",PRESSURE,"&field3=",Altitude,"&field4=", _
        HeatIndex,"&field5=",HUMIDITY,"&field6=",DewPoint))
        
        Log("Sending: ", PayLoad)
        
        Topic=JoinStrings(Array As String("channels/", ChannelID, "/publish/", WriteAPIKey))
        
        Log("Success=1, Failure=0: ",MQTT.Publish(Topic,PayLoad))
        
    Else
        Log("Error - Not connected")
    End If
    MQTT_Disconnected
End Sub

Sub MQTT_MessageArrived (Topic As String, Payload() As Byte)
    Log("Message arrived. Topic=", Topic, " payload: ", Payload)
End Sub

Sub MQTT_Disconnected
    Log("Disconnected")
    MQTT.Close
End Sub

Sub BroadcastOverUDP
    Dim bytes() As Byte
    Udp.Initialize(UDPPort, "UDP_PacketArrived")
    Udp.BeginPacket(BroadcastIP, UDPPort)
    bytes = BC.DoublesToBytes(Array As Double(TEMPERATURE))
    Udp.Write(bytes)
    bytes = BC.DoublesToBytes(Array As Double(PRESSURE))
    Udp.Write(bytes)
    bytes = BC.DoublesToBytes(Array As Double(HUMIDITY))
    Udp.Write(bytes)
    Udp.SendPacket
    Udp.Close
    
    Log("Packets sent")
End Sub

Sub UDP_PacketArrived (Data() As Byte, ip1() As Byte, port1 As UInt)
    ' we do not expect a return packet!
End Sub

Sub ReadSensor
    Log("Trying to read sensor ...")
    RunNative("read",Null)
      
    Dim T, P, H As Double
    'NumberFormat(TEMPERATURE,1,1) - last number is decimal places
    T=NumberFormat(TEMPERATURE,1,1)
    P=NumberFormat(PRESSURE,1,0)
    H=NumberFormat(HUMIDITY,1,0)
    
    TEMPERATURE=T
    PRESSURE=P
    HUMIDITY=H
    
    ' comment out when not needed
    'DebugLog
End Sub

Sub DebugLog
    Log("Temperature: ", TEMPERATURE)
    Log("Pressure: ", PRESSURE)
    Log("Altitude: ", Altitude)
    Log("HeatIndex: ", HeatIndex)
    Log("Humidity: ", HUMIDITY)
    Log("DewPoint: ", DewPoint)
    
End Sub

Sub EnviroSettings
    If TEMPERATURE=0 Or PRESSURE=0 Or HUMIDITY=0 Then
         Return    'cannot calulate with zero
    End If
    
    '******************************************************************************************************
    ' Altitude
    If PRESSURE>0 And RefPressure>0 And RefTemperature>0 Then
        Altitude = Power(RefPressure / PRESSURE,0.190234)-1
        Altitude = Altitude*((RefTemperature + 273.15) / 0.0065)
    End If
        
    '******************************************************************************************************
    ' Heat Index
    Dim t As Double=(TEMPERATURE * (9/5) +32)                ' we need degree F
    
    ' Using both Rothfusz And Steadman's equations
    ' http://www.wpc.ncep.noaa.gov/html/heatindex_equation.shtml
    
    If t<= 40 Then
        HeatIndex=TEMPERATURE
    Else
        HeatIndex = 0.5 * (t + 61 + ((t - 68) * 1.2) + (HUMIDITY * 0.094))
        
        If HeatIndex>=79 Then
        'http://www.wolframalpha.com/input/?source=nav&i=b%3D+x1+%2B+x2*T+%2B+x3*H+%2B+x4*T*H+%2B+x5*T*T+%2B+x6*H*H+%2B+x7*T*T*H+%2B+x8*T*H*H+%2B+x9*T*T*H*H
        HeatIndex = hi_coeff1 + (hi_coeff2 + hi_coeff4 * HUMIDITY + t * (hi_coeff5 + hi_coeff7 * HUMIDITY)) * t _
         + (hi_coeff3 + HUMIDITY * (hi_coeff6 + t * (hi_coeff8 + hi_coeff9 * t))) * HUMIDITY
            
        If (HUMIDITY<13) And (t>=80) And (t<=112) Then
                HeatIndex =HeatIndex-((13 - HUMIDITY) * 0.25) * Sqrt((17 - Abs(t - 95)) * 0.05882)
        Else If (HUMIDITY>85) And (t>=80) And (t<=87) Then
                HeatIndex = HeatIndex+(0.02 * (HUMIDITY - 85) * (87 - t))
        End If
        End If
    End If
    
    HeatIndex = (HeatIndex-32)*(5/9)        'convert back to celcius
    '******************************************************************************************************
    ' Dew Point
    'Equations courtesy of Brian McNoldy from http://andrew.rsmas.miami.edu;
    
    If TEMPERATURE>0 And HUMIDITY>0 Then
        'DewPoint = 243.04 * (Logarithm(Humidity/100,10) + ((17.625 * Temperature)/(243.04 + Temperature)))(17.625 - Logarithm(Humidity/100,10) - ((17.625 * Temperature)/(243.04 + Temperature)))
        DewPoint = TEMPERATURE - ((100 -HUMIDITY)/5)
    End If
End Sub


Sub ConnectToNetwork
    If WiFi.IsConnected Then
        'Log("disconnecting Wifi")
        WiFiLEDPin.DigitalWrite(False)
        Return
        'WiFi.Disconnect
    End If
    If WiFi.Connect2(WifiSSID, WifiPass) Then
        Log("Connected successfully to: ", WifiSSID)
        Log(WiFi.LocalIp)
        WiFiLEDPin.DigitalWrite(True)
    Else
        Log("Failed to connect.")
    End If
End Sub

Private Sub DeepSleep(ms As ULong)
    WiFiLEDPin.DigitalWrite(False)
    RunNative("deepSleep", ms * 1000)
End Sub

#if C
void deepSleep(B4R::Object* o) {
   ESP.deepSleep(o->toULong());
}
#end if


#if C
#include <Wire.h>
#include "cactus_io_BME280_I2C.h"
BME280_I2C bme(0x76);

void setup(B4R::Object* o){
bme.begin();
  bme.setTempCal(-1);
}

void read (B4R::Object* o) {
  
  bme.readSensor();
 
 
  
    b4r_main::_temperature=bme.getTemperature_C();
    b4r_main::_pressure=bme.getPressure_MB();
    b4r_main::_humidity=bme.getHumidity();
}

#End if

Deep sleep is set for 15 minutes. Upload the code and THEN set the jumper. If you cannot upload, maybe the jumper is set!

The Wemos connects to the Internet, reads the BME sensor, sends the data to your channel at Thingspeak and the broadcasts the data via UDP to the listeners (line 47). I am broadcasting to two networks, me and my neighbour, thats why I have 2 x 255. Change as required. Environmental calculations are also included for Heat Index, Dew Point and Altitude.
 

Mark Read

Well-Known Member
Licensed User
Just for Information. The wake-up and sending of data and then back to sleep takes about 500 milliseconds. I have tested this on a fully charged battery and no solar cell. The battery was dead (under 3.3V) after three weeks. Charging during the day and running from battery overnight is no problem.

Do not try and run from solar alone, without the battery, the TP4056 deos not like this and reduces the voltage to under 3.3V: The Wemos never wakes up!
 

Mark Read

Well-Known Member
Licensed User
So now you have two possibilites:

1. Download the free App from Thingspeak to show your data on a mobile phone or tablet.
2. Build the UDP receiver.
 

Mark Read

Well-Known Member
Licensed User
For the receiver you will need:

1 x Wemos mini
1 x 0.96" SSD1306 Display
4 x Jumper wires

Connect:

Wemos-----SSD1306
GND ------ GND
3.3V ------- VCC
D1 --------- SCL
D2 --------- SDA

Libraries required: rAdafruitSSD1306 (HERE), rAdafruitGFX (link on same page). rCore, rESP8266, rESP8266WiFi, rGlobalstore, rRandonAcccessFile.

Of course you will need some code:

UDP Receiver:
#Region Project Attributes
    #AutoFlushLogs: True
    #CheckArrayBounds: True
    #StackBufferSize: 600
    #DefineExtra: #include "rCore.h"       'workaround due to bug in version 3.70
#End Region
'Ctrl+Click to open the C code folder: ide://run?File=%WINDIR%\System32\explorer.exe&Args=%PROJECT%\Objects\Src

Sub Process_Globals
    Public Serial1 As Serial
    Private WiFi As ESP8266WiFi
    Private Udp As WiFiUDP
    Private WifiSSID As String="xxxxxxxx"
    Private WifiPass As String="xxxxxxxxx"
    Private ssd As AdafruitSSD1306
    Private d1pins As D1Pins
   
End Sub

Private Sub AppStart
    Serial1.Initialize(115200)
    Log("AppStart")
    ssd.InitializeI2C(d1pins.D6, 0x3c)            'initialise SSD1306
    ssd.ClearDisplay
    ssd.GFX.SetCursor(0, 0)
    ssd.GFX.ConfigureText(2, ssd.WHITE, False)
    ssd.GFX.DrawText("Wifi ...").DrawText(CRLF).DrawText(CRLF)
    ssd.Display
   
    If WiFi.IsConnected=False Then
        ConnectToNetwork
    End If
    Udp.Initialize(8888,"UDP_PacketArrived")
   
    ssd.ClearDisplay
    ssd.GFX.SetCursor(0, 0)
    ssd.GFX.ConfigureText(2, ssd.WHITE, False)
    ssd.GFX.DrawText("Wifi .. OK").DrawText(CRLF).DrawText(CRLF)
    ssd.GFX.DrawText("No UDP Data")
    ssd.Display  
End Sub

Sub ConnectToNetwork
    If WiFi.IsConnected Then
        Return
    End If
    If WiFi.Connect2(WifiSSID, WifiPass) Then
        Log("Connected successfully to: ", WifiSSID)
        Log(WiFi.LocalIp)
    Else
        Log("Failed to connect.")
    End If
End Sub

Sub UDP_PacketArrived (Data() As Byte, ip() As Byte, port As UInt)
    Dim bc As ByteConverter
    Dim i As Int
    i=bc.IndexOf(Data," ")
   
    Log("****************************************")
    Log("Data received from: ",ip(0),".", ip(1),".", ip(2),".", ip(3))
    Log(Data, "- No of Bytes: ", Data.Length)
    Log("****************************************")
   
    'typical data
    '3.52 50.51 954.36
       
    GlobalStore.Put(0,bc.SubString2(Data,0,i-1))        'Temperature
    GlobalStore.Put(1,bc.SubString2(Data,i+1,i+5))        'Humidity
    GlobalStore.Put(2,bc.SubString2(Data,i+7,i+10))        'Pressure
   
    Log("Temperature: ",GlobalStore.Slot0 )
    Log("Humidity: ", GlobalStore.Slot1)
    Log("Pressure: ", GlobalStore.Slot2)
       
    OLED_Display("")
End Sub

Sub OLED_Display(Tag As Byte)
    'Show temperature
    ssd.ClearDisplay
    ssd.GFX.SetCursor(0, 0)
    ssd.GFX.ConfigureText(2, ssd.WHITE, False)
    ssd.GFX.DrawText("Temp").DrawText(CRLF).DrawText(CRLF)
    ssd.GFX.ConfigureText(3, ssd.WHITE, False)
    ssd.GFX.DrawText(GlobalStore.Slot0)
    ssd.GFX.ConfigureText(2, ssd.WHITE, False)
    ssd.GFX.DrawText(" oC")
    ssd.Display
    Delay(10000)
    'Show Humidity
    ssd.ClearDisplay
    ssd.GFX.SetCursor(0, 0)
    ssd.GFX.ConfigureText(2, ssd.WHITE, False)
    ssd.GFX.DrawText("Humidity").DrawText(CRLF).DrawText(CRLF)
    ssd.GFX.ConfigureText(3, ssd.WHITE, False)
    ssd.GFX.DrawText(GlobalStore.Slot1)
    ssd.GFX.ConfigureText(2, ssd.WHITE, False)
    ssd.GFX.DrawText(" %")
    ssd.Display
    Delay(10000)
    'Show Pressure
    ssd.ClearDisplay
    ssd.GFX.SetCursor(0, 0)
    ssd.GFX.ConfigureText(2, ssd.WHITE, False)
    ssd.GFX.DrawText("Pressure").DrawText(CRLF).DrawText(CRLF)
    ssd.GFX.ConfigureText(3, ssd.WHITE, False)
    ssd.GFX.DrawText(GlobalStore.Slot2)
    ssd.GFX.ConfigureText(2, ssd.WHITE, False)
    ssd.GFX.DrawText(" hPa")
    ssd.Display
    Delay(10000)
    CallSubPlus("OLED_Display",50,"")
End Sub

I am only sending and receiving Temperature, Humidity and Pressure via UDP. The rest goes to Thingspeak. I have this in a 3D printed case sitting beside my PC, running on an USB phone charger.

Heres a few pictures of the finished object.

IMG_20210409_164708.jpg
IMG_20210409_164722.jpg
IMG_20210409_164737.jpg
 
Last edited:

Mark Read

Well-Known Member
Licensed User
Yet another piece of information. Both of these projects I have finished in the Arduino IDE but it was more fun in B4R. Also the code was shorter, compare:

Weather Station: Arduino 421 lines, B4R 298 lines
UDP Receiver: Arduino 233 lines, B4R 114 lines

And just to say, my coding is by no means perfect. I am sure others could do better. Have fun.
 

amorosik

Well-Known Member
Licensed User
Excellent work
When using B4R how do you debug the program?
Do you use the serial port to send out information or other system?
 

Mark Read

Well-Known Member
Licensed User
I use the log function in the B4R IDE and secondly, I have a test channel in Thingspeak where I can send data and check that it arrives correctly. I did not want to upset my original data channel as it has ben running for three years now.
 

Mark Read

Well-Known Member
Licensed User
Important Update: As you can see in Post #1, I am powering the BMP280 from the 3.3V line on the Wemos. In the last few months where it was very hot, the BMP had a tendancy to lock up. This appears to be a known problem.

The simple solution is to power the BMP from a digital IO pin. If the BMP cannot be activated, turn of the pin, wait about 5 seconds and turn on again. Small changes are required in the code but I will let you do that.
 
Top