Own barometer eg."forecast" -> Zambretti implementation

So I was wondering if anybody went down the road of creating a ruleset for virtual barometer and it does not look like so … at least on this forum.
I’ve had some spare time and many measurements from my home-weather-sensors-house-garden-station … which need some use :slight_smile:

Any help,ideas,thoughts are appreciated as this code is WORK IN PROGRESS and as it is from today, it’s messy and need indeed some love :slight_smile:

from couple of webpages i’ve gathered how to read barometer, but i havent found anything about time-wise eg. what is slow/fast drop etc. so for now, i’m using hourly measurements to determine speed of change, trending etc.
not everything is used and not everything is right, but we’ll get there.

I think biggest question for now is how big have to be change in which timeperiod to be able to say it’s falling rapidly or steadily and then display proper informations.
I can combine these with many other values I already have, like dew point, cloudiness, temperature, humidity, windspeed, winddirection, trends … but as I’m not meteorologist, not sure what with what to combine properly.

(and yes I know there is openweather, but i want use data on exact location which can be very different from forecast miles away) :slight_smile:

interesting source might be this:
http://www.truganinaweather.com/projects/zambretti-forecaster-project.htm

edit > final code in solution (for Northen Hemisphere)

Forecasting is more involved than just monitoring the barmoetric pressure. You also have to take into account wind direction and elevation plays a role too. Also, manuals I’ve read for analog barometers say that checking too often may lead to a misleading impression on what’s going on. A couple times a day is about all you need to check because you mainly care about the long term trend. There are atmospheric tides and other conditions that can cause lots of variation over a short period of time.

From what I can find and what I remember, the definition of “rapidly” is going to depend on correlating observations for your specific area. So if you see the pressure fall over a 24 hour period and a couple days later you have a storm, you’ve found at least one instance of “rapid”.

You should take à look at Sagercaster binding

1 Like

wow thanks, looks promising! :slight_smile:

I live in British Columbia, Canada. Two weekends ago we had historic rainfall that caused massive flooding throughout the province. It was attributed to an “atmospheric river”, which I’d never heard of, but is apparently a thing for meteorologists.

It rained non-stop through a Saturday and Sunday, rained even harder on the Monday morning, and then it just stopped, and the afternoon was bright and sunny. Apparently it might be back later this week.

This is less than six months after the “heat dome” that caused massive drought in Western North America in the summer. So went from having no rain for weeks on end to an insane amount of rain in a handful of days.

It really reinforces the relationship between weather patterns and chaos theory.

this is about barometric forecast not super fancy overcomplicated 100% accurate forecast… barometers are with us for quite a while and I like that idea.

So in the meanwhile I’ve adapted Zambretti Forecaster into DSL

// barometer forecast
rule "Weather: Zambretti Forecaster"
when
    Time cron "0 */30 * * * ?"
then
    val p0   = (House_Pressure_Sealevel.state as Number).floatValue
    val p1   = House_Pressure_Sealevel.averageSince(now.minusMinutes(30))
    val p2   = House_Pressure_Sealevel.averageSince(now.minusMinutes(60))
    val p3   = House_Pressure_Sealevel.averageSince(now.minusMinutes(90))
    val p4   = House_Pressure_Sealevel.averageSince(now.minusMinutes(120))
    val p5   = House_Pressure_Sealevel.averageSince(now.minusMinutes(150))
    val p6   = House_Pressure_Sealevel.averageSince(now.minusMinutes(180))
    val p7   = House_Pressure_Sealevel.averageSince(now.minusMinutes(210))
    val p8   = House_Pressure_Sealevel.averageSince(now.minusMinutes(240))
    val p9   = House_Pressure_Sealevel.averageSince(now.minusMinutes(270))
    val p10  = House_Pressure_Sealevel.averageSince(now.minusMinutes(300))
    val p11  = House_Pressure_Sealevel.averageSince(now.minusMinutes(330))
    val p12  = House_Pressure_Sealevel.averageSince(now.minusMinutes(360))

    // calculate single differencies and normalize them to a change in pressure over 1h
    val pd1  = (p0 - p1)  * 2
    val pd2  = (p0 - p2)
    val pd3  = (p0 - p3)  / 1.5
    val pd4  = (p0 - p4)  / 2
    val pd5  = (p0 - p5)  / 2.5
    val pd6  = (p0 - p6)  / 3
    val pd7  = (p0 - p7)  / 3.5
    val pd8  = (p0 - p8)  / 4
    val pd9  = (p0 - p9)  / 4.5
    val pd10 = (p0 - p10) / 5
    val pd11 = (p0 - p11) / 5.5
    val pd12 = (p0 - p12) / 6
    // calculate average
    val pdiff = (pd1 + pd2 + pd3 + pd4 + pd5 + pd6 + pd7 + pd8 + pd9 + pd10 + pd11 + pd12) / 12
    val curr  = p0
    var String forecast = "Unknown"
    // calculate trend
    var Number trend = 0
    switch pdiff {
        case pdiff < -0.25  : trend = -1
        case pdiff >  0.25  : trend =  1
        default             : trend =  0
    }

    if(trend == -1)     // failing conditions
    {
        var zambretti = 0.0009746 * curr * curr - 2.1068 * curr + 1138.7019
        if(vMonth.state < 4 || vMonth.state > 9) zambretti = zambretti + 1
        zambretti = Math.round(zambretti)
        switch zambretti {
            case zambretti == 1: forecast = "Settled Fine"
            case zambretti == 2: forecast = "Fine Weather"
            case zambretti == 3: forecast = "Fine Becoming Less Settled"
            case zambretti == 4: forecast = "Fairly Fine Showers Later"
            case zambretti == 5: forecast = "Showery Becoming unsettled"
            case zambretti == 6: forecast = "Unsettled, Rain later"
            case zambretti == 7: forecast = "Rain at times, worse later"
            case zambretti == 8: forecast = "Rain at times, becoming very unsettled"
            case zambretti == 9: forecast = "Very Unsettled, Rain"
        }
    }
    else if(trend == 0) // steady conditions
    {
        var zambretti = 138.24 - 0.133 * curr
        zambretti = Math.round(zambretti)
        switch zambretti {
            case zambretti == 1: forecast = "Settled Fine"
            case zambretti == 2: forecast = "Fine Weather"
            case zambretti == 3: forecast = "Fine, Possibly showers"
            case zambretti == 4: forecast = "Fairly Fine, Showers likely"
            case zambretti == 5: forecast = "Showery Bright Intervals"
            case zambretti == 6: forecast = "Changeable some rain"
            case zambretti == 7: forecast = "Unsettled, rain at times"
            case zambretti == 8: forecast = "Rain at Frequent Intervals"
            case zambretti == 9: forecast = "Very Unsettled, Rain"
            case zambretti == 10: forecast = "Stormy, much rain"
        }
    }
    else                // rising conditions
    {
        var zambretti = 142.57 - 0.1376 * curr
        if(vMonth.state < 4 || vMonth.state > 9) zambretti = zambretti + 1
        zambretti = Math.round(zambretti)
        switch zambretti {
            case zambretti == 1: forecast = "Settled Fine"
            case zambretti == 2: forecast = "Fine Weather"
            case zambretti == 3: forecast = "Becoming Fine"
            case zambretti == 4: forecast = "Fairly Fine, Improving"
            case zambretti == 5: forecast = "Fairly Fine, Possibly showers, early"
            case zambretti == 6: forecast = "Showery Early, Improving"
            case zambretti == 7: forecast = "Changeable, Improving"
            case zambretti == 8: forecast = "Unsettled, Probably Improving"
            case zambretti == 9: forecast = "Very Unsettled, Rain"
            case zambretti == 10: forecast = "Unsettled, short fine Intervals"
            case zambretti == 11: forecast = "Very Unsettled, Finer at times"
            case zambretti == 12: forecast = "Stormy, possibly improving"
            case zambretti == 13: forecast = "Stormy, much rain"
        }
    }
    postUpdate(hBarometric_Zambretti, forecast)
end

credits: Weather Station – Raspberry Pi and Stuff

3 Likes

Thanks for sharing your code. I’ll test it with my I2C air pressure sensor and report back.

Please note that one of the input parameters is the season (vMonth). Therefore, the code must be modified if used in the Southern Hemisphere.

This could be a good candidate to be implemented as an automation module.

1 Like

Zambretti at work:

grafik

Zambretti Number 1 (ZN1) is defined in relation to the Zambretti numbers from the DSL script (ZND):

  • ZN1 1001-1009: falling conditions ZND 1-9
  • ZN1 1010-1019: steady conditions ZND 1-10
  • ZN1 1020-1032: rising conditions ZND 1-13

Does the Zambretti forecaster work? Well, I’m not sure …

BTW, see Zambretti Forecaster - Wikipedia for the real device.

I think Zambretti tried to be more “precise” at time of invention than regular barometers were, not necessarily better or worse. I think algorithm is being proven by years and you can research about it if wanted.

in the meanwhile I’ve adapted my code to provide both Zabretti and “regular” barometer as well.
In comparison between them and with comparison with Sagercaster - it looks both works pretty much same “predicting” very similar results. Zambretti maybe slighly better. So on my end, this is what I wanted.
Old school “own” at my location prediction.

Feel free to adjust code, suggest changes etc. :slight_smile:

edit: updated code, some cleanup. wind adjust and code definition for optional map file to translate it better

// barometer forecast
rule "Weather: Barometric & Zambretti Forecaster"
when
    Time cron "0 0  9 * * ?" or // 9h               
    Time cron "0 0 15 * * ?" or // 15h
    Time cron "0 0 21 * * ?" or // 21h
    System started
then
    val p0   = (House_Pressure_Sealevel.state as Number).floatValue
    val p1   = House_Pressure_Sealevel.averageSince(now.minusMinutes(30))
    val p2   = House_Pressure_Sealevel.averageSince(now.minusMinutes(60))
    val p3   = House_Pressure_Sealevel.averageSince(now.minusMinutes(90))
    val p4   = House_Pressure_Sealevel.averageSince(now.minusMinutes(120))
    val p5   = House_Pressure_Sealevel.averageSince(now.minusMinutes(150))
    val p6   = House_Pressure_Sealevel.averageSince(now.minusMinutes(180))
    val p7   = House_Pressure_Sealevel.averageSince(now.minusMinutes(210))
    val p8   = House_Pressure_Sealevel.averageSince(now.minusMinutes(240))
    val p9   = House_Pressure_Sealevel.averageSince(now.minusMinutes(270))
    val p10  = House_Pressure_Sealevel.averageSince(now.minusMinutes(300))
    val p11  = House_Pressure_Sealevel.averageSince(now.minusMinutes(330))
    val p12  = House_Pressure_Sealevel.averageSince(now.minusMinutes(360))

    // calculate single differencies and normalize them to a change in pressure over 1h
    val pd1  = (p0 - p1)  * 2
    val pd2  = (p0 - p2)
    val pd3  = (p0 - p3)  / 1.5
    val pd4  = (p0 - p4)  / 2
    val pd5  = (p0 - p5)  / 2.5
    val pd6  = (p0 - p6)  / 3
    val pd7  = (p0 - p7)  / 3.5
    val pd8  = (p0 - p8)  / 4
    val pd9  = (p0 - p9)  / 4.5
    val pd10 = (p0 - p10) / 5
    val pd11 = (p0 - p11) / 5.5
    val pd12 = (p0 - p12) / 6
    // calculate average
    val pdiff = (pd1 + pd2 + pd3 + pd4 + pd5 + pd6 + pd7 + pd8 + pd9 + pd10 + pd11 + pd12) / 12
    
    // temperature
    val Number temp = (WS_Temperature.state as Number).floatValue
    
// barometer
    // high/normal/low pressure
    var Number cond = 2
    switch p0 {
        case p0 >  1022.689  : cond = 1   // high pressure
        case p0 <  1009.144  : cond = 3   // low pressure
        default              : cond = 2   // normal pressure
    }

    var Number speed = 1
    // rapid/slow or steady
    switch pdiff {
        case pdiff > 0.75   : speed = 3
        case pdiff > 0.42   : speed = 2
        case pdiff > 0.25   : speed = 1

        case pdiff < -0.75  : speed = 3
        case pdiff < -0.42  : speed = 2
        case pdiff < -0.25  : speed = 1
    }

    var String bf = "Unknown"
    // high pressure
    if(cond == 1){          
        if(speed == 1)     bf = "Continue Fair Weather"
        if(speed == 2)     bf = "Fair Weather"
        if(speed == 3)     bf = "Cloudy and Warmer"
    }
    // normal pressure
    else if (cond == 2){   
        if(speed == 1)     bf = "Continue Current Weather"
        if(speed == 2)     bf = "Little change of the Weather"
        if(speed == 3){
            if(temp < 0.5) bf = "Snow"
            else           bf = "Rain"
        }  
    }
    // low pressure
    else{                  
        if(speed == 1)     bf = "Clearing sky, Cooler Weather"
        if(speed == 2){
            if(temp < 0.5) bf = "Snow"
            else           bf = "Rain"
        }  
        if(speed == 3)     bf = "Storm"
    }
    postUpdate(hBarometric_Forecast, bf)


//
// zambretti
    // calculate trend
    var Number trend = 0
    switch pdiff {
        case pdiff < -0.25  : trend = -1
        case pdiff >  0.25  : trend =  1
        default             : trend =  0
    }
   
    var Number curr = p0

    var String zf = ""
    // failing conditions
    if(trend < 0)
    {
        var zmb = Math.round(0.0009746 * curr * curr - 2.1068 * curr + 1138.7019)
        if(vMonth.state < 4 || vMonth.state > 9) zmb = zmb - 1
        switch (zmb.intValue) {
            case 1:  zf = "A"
            case 2:  zf = "B"
            case 3:  zf = "D"
            case 4:  zf = "H"
            case 5:  zf = "O"
            case 6:  zf = "R"
            case 7:  zf = "U"
            case 8:  zf = "V"
            case 9:  zf = "X"
            default: zf = "Unknown/Falling"
        }
    }
    // rising conditions
    else if(trend > 0) 
    {
        var zmb = Math.round(142.57 - 0.1376 * curr)
        if(vMonth.state < 4 || vMonth.state > 9) zmb = zmb + 1
        switch (zmb.intValue) {
            case 1:  zf = "A"
            case 2:  zf = "B"
            case 3:  zf = "C"
            case 4:  zf = "F"
            case 5:  zf = "G"
            case 6:  zf = "I"
            case 7:  zf = "J"
            case 8:  zf = "L"
            case 9:  zf = "M"
            case 10: zf = "Q"
            case 11: zf = "T"
            case 12: zf = "Y"
            case 13: zf = "Z"
            default: zf = "Unknown/Rising"
        }
    }
    // steady conditions
    else
    {
        var zmb = Math.round(138.24 - 0.133 * curr)
        switch (zmb.intValue) {
            case 1:  zf = "A"
            case 2:  zf = "B"
            case 3:  zf = "E"
            case 4:  zf = "K"
            case 5:  zf = "N"
            case 6:  zf = "P"
            case 7:  zf = "S"
            case 8:  zf = "W"
            case 9:  zf = "X"
            case 10: zf = "Z"
            default: zf = "Unknown/Steady"
        }
    }

    var String out = "-"
    switch (zf) {
        case "A" : out = "Settled fine weather"                     // Sun
        case "B" : out = "Fine weather"                             // Sun
        case "C" : out = "Becoming fine"                            // Sun
        case "D" : out = "Fine, Becoming less settled"              // Sun & Clouds
        case "E" : out = "Fine, Possibly showers"                   // Sun & Clouds
        case "F" : out = "Fairly fine, Improving"                   // Sun
        case "G" : out = "Fairly fine, Possibly showers early"      // Sun & Clouds
        case "H" : out = "Fairly fine, Showers later"               // Sun & Clouds
        case "I" : out = "Showery early, Improving"                 // Sun & Clouds
        case "J" : out = "Changeable, Improving"                    // Sun & Clouds
        case "K" : out = "Fairly Fine, Showers likely"              // Sun & Clouds
        case "L" : out = "Rather Unsettled clearing later"          // Sun & Clouds
        case "M" : out = "Unsettled, Probably improving"            // Sun & Clouds
        case "N" : out = "Showery bright intervals"                 // Sun & Clouds
        case "O" : out = "Showery becoming unsettled"               // Sun & Clouds
        case "P" : out = "Changeable some rain"                     // Sun & Clouds
        case "Q" : out = "Unsettled, Short fine intervals"          // Sun & Clouds
        case "R" : out = "Unsettled, Rain later"                    // Rain
        case "S" : out = "Unsettled, Rain at times"                 // Rain
        case "T" : out = "Very Unsettled, Finer at times"           // Sun & Clouds
        case "U" : out = "Rain at times, Worse later"               // Rain
        case "V" : out = "Rain at times, Becoming very unsettled"   // Rain
        case "W" : out = "Rain at Frequent Intervals"               // Rain
        case "X" : out = "Very unsettled, Rain"                     // Rain
        case "Y" : out = "Stormy, Possibly improving"               // Storm
        case "Z" : out = "Stormy, Much rain"                        // Storm
        default  : out = "Uknown"
    }
    postUpdate(hBarometric_Trend,trend)
    postUpdate(hBarometric_ZambrettiCode,zf)
    postUpdate(hBarometric_Zambretti, out)
    postUpdate(hBarometric_Time,now.toInstant.toString)
end

I’ve been looking to do something similar myself and want to add your code to my rules.

Can you confirm what “.items” I need?

Guess its:

String hBarometric
String hBarometric_Zambretti

hi,
latest code is using these outputs
first one is “regular” barometer - just for some research/comparison
second one is populated directly by the rule as string
third is populated by zambretti letter so you can create map file as you want.

// barometric
String   hBarometric_Forecast       "Barometer [%s]"                    <sun_clouds>
String   hBarometric_Zambretti      "Next 12h [%s]"                     <sun_clouds>
String   hBarometric_ZambrettiCode  "Zambretti [MAP(zambretti.map):%s]" <sun_clouds>
Number   hBarometric_Trend          "Trend [MAP(zambretti.map):%s]"     <line>
DateTime hBarometric_Time           "Update [%1$tR]"                    <time>

but indeed adjust your output as you need

optional map file to adjust zambretti codes is

//A = Settled fine weather
//B = Fine weather
//C = Becoming fine
//D = Fine, Becoming less settled
//E = Fine, Possibly showers
//F = Fairly fine, Improving
//G = Fairly fine, Possibly showers early
//H = Fairly fine, Showers later
//I = Showery early, Improving
//J = Changeable, Improving
//K = Fairly Fine, Showers likely
//L = Rather Unsettled clearing later
//M = Unsettled, Probably improving
//N = Showery bright intervals
//O = Showery becoming unsettled
//P = Changeable some rain
//Q = Unsettled, Short fine intervals
//R = Unsettled, Rain later
//S = Unsettled, Rain at times
//T = Very Unsettled, Finer at times
//U = Rain at times, Worse later
//V = Rain at times, Becoming very unsettled
//W = Rain at Frequent Intervals
//X = Very unsettled, Rain
//Y = Stormy, Possibly improving
//Z = Stormy, Much rain

Unknown = Out of scale
NULL = Unknown

A = Sun
B = Sun
C = Sun
D = Sun & Clouds
E = Sun & Clouds
F = Sun
G = Sun & Clouds
H = Sun & Clouds
I = Sun & Clouds
J = Sun & Clouds
K = Sun & Clouds
L = Sun & Clouds
M = Sun & Clouds
N = Sun & Clouds
O = Sun & Clouds
P = Sun & Clouds
Q = Sun & Clouds
R = Rain
S = Rain
T = Sun & Clouds
U = Rain
V = Rain
W = Rain
X = Rain
Y = Storm
Z = Storm

-1 = Falling
1 = Rising
0 = Steady

as a next step I want to display some icons based on the letters :slight_smile:

Hi, all seems to be working, nice work.

I did add - = Unknown to the ‘.map’ file as I was getting some transformation errors initially, but now its updated with data the mapping is working.

I also didn’t have any default ‘persistence’ set as I use InfluxDB to store temp/pressure data, so I’ve set that to RRD4j and its picked up the previous pressure values.

I see you have changed the rule a bit and added a local temperature as well rather than a season for rain/snow. That should be a lot more accurate.

I’ll keep an eye on the output and see how accurate the predictions are. :smiley:

yes, I’ve changed it because now it’s Autumn and snowing anyway, so … I’m trying to guess if snow is accurate that way.

I’ve removed adjustment for wind directions, as it is already averaged in our version of the zambretti number.
As far as i can tell, it’s pretty accurate.

I’m now just trying to set proper times to calculate, as it seems best result at 9am and in the afternoon. But might be ok-ish ever 2hours or so as well.

btw I’m using influx as well, so should be working… :wink:

I’m in the South of the UK and we don’t often get snow, so temperature is definitely more important in trying to predict it.

I am getting a failure now with vMonth in the rule. (The name 'vMonth' cannot be resolved to an item or type; line 109, column 12, length 6 ). Where are you defining that items state?

rule "Helpers: Calculate month"
when
  System started or
  Time cron "0 2 0 1 * ? *"
then
  vMonth.postUpdate(now.getMonthValue)
end

For general purpose I have several rules for these kind of values here and there

I see. I’ve added:

	// month of year
	val vMonth = now.getMonthValue

to the start section and removed the .state from the the vMonth.state and set to vMonth. Seems to work.

this indeed will work as well. I’m using vMonth on various places, so it’s bit easier to have it already defined :slight_smile:

1 Like