Example #1
0
def rapidWind(Msg,wfpiconsole):

    """ Handles RapidWind Websocket messages received from either SKY or TEMPEST
        module

    INPUTS:
        Msg                 Websocket messages received from SKY or TEMPEST
        wfpiconsole         wfpiconsole object
    """

    # Replace missing observations from Rapid Wind Websocket JSON
    # with NaN
    Ob = [x if x != None else NaN for x in Msg['ob']]

    # Discard duplicate Rapid Wind Websocket messages
    if 'RapidMsg' in wfpiconsole.Obs:
        if wfpiconsole.Obs['RapidMsg']['ob'][0] == Ob[0]:
            print('Discarding duplicate Rapid Wind Websocket message')
            return

    # Extract observations from latest Rapid Wind Websocket JSON
    Time    = [Ob[0],'s']
    WindSpd = [Ob[1],'mps']
    WindDir = [Ob[2],'degrees']

    # Extract wind direction from previous SKY Rapid-Wind Websocket JSON
    if 'RapidMsg' in wfpiconsole.Obs:
        Ob = [x if x != None else NaN for x in wfpiconsole.Obs['RapidMsg']['ob']]
        WindDirOld = [Ob[2],'degrees']
    else:
        WindDirOld = [0,'degrees']

    # If windspeed is zero, freeze direction at last direction of non-zero wind
    # speed and edit latest Rapid Wind Websocket JSON. Calculate wind shift
    if WindSpd[0] == 0:
        WindDir = WindDirOld
        Msg['ob'][2] = WindDirOld[0]

    # Store latest Rapid Wind wfpiconsole.Observation JSON message
    wfpiconsole.Obs['RapidMsg'] = Msg

    # Calculate derived variables from Rapid Wind observations
    WindDir = derive.CardinalWindDirection(WindDir,WindSpd)

    # Convert observation units as required
    WindSpd = observation.Units(WindSpd,wfpiconsole.config['Units']['Wind'])
    WindDir = observation.Units(WindDir,'degrees')

    # Update wfpiconsole display with derived Rapid Wind observations
    wfpiconsole.Obs['rapidShift'] = WindDir[0] - WindDirOld[0]
    wfpiconsole.Obs['rapidSpd']   = observation.Format(WindSpd,'Wind')
    wfpiconsole.Obs['rapidDir']   = observation.Format(WindDir,'Direction')

    # Animate wind rose arrow if WindSpeedPanel panel is active
    if hasattr(wfpiconsole,'WindSpeedPanel'):
        for panel in getattr(wfpiconsole,'WindSpeedPanel'):
            panel.animateWindRose()

    # Return wfpiconsole object
    return wfpiconsole
def indoorAir(Msg, wfpiconsole):
    """ Handles Websocket messages received from indoor AIR module

    INPUTS:
        Msg                 Websocket messages received from indoor AIR module
        wfpiconsole         wfpiconsole object
    """

    # Replace missing observations in latest indoor AIR Websocket JSON with NaN
    Ob = [x if x != None else NaN for x in Msg['obs'][0]]

    # Discard duplicate indoor AIR Websocket messages
    if 'inAirMsg' in wfpiconsole.Obs:
        if wfpiconsole.Obs['inAirMsg']['obs'][0] == Ob[0]:
            print('Discarding duplicate indoor AIR Websocket message')
            return

    # Extract indoor AIR device ID and API flag, and station configuration
    # object
    Device = wfpiconsole.config['Station']['InAirID']
    flagAPI = wfpiconsole.flagAPI[3]
    Config = wfpiconsole.config

    # Extract required observations from latest indoor AIR Websocket JSON
    Time = [Ob[0], 's']
    Temp = [Ob[2], 'c']

    # Store latest indoor AIR Websocket message
    wfpiconsole.Obs['inAirMsg'] = Msg

    # Extract required derived observations
    minTemp = wfpiconsole.Obs['inTempMin']
    maxTemp = wfpiconsole.Obs['inTempMax']

    # Calculate derived variables from indoor AIR observations
    MaxTemp, MinTemp = derive.TempMaxMin(Time, Temp, maxTemp, minTemp, Device,
                                         Config, flagAPI)

    # Convert observation units as required
    Temp = observation.Units(Temp, Config['Units']['Temp'])
    MaxTemp = observation.Units(MaxTemp, Config['Units']['Temp'])
    MinTemp = observation.Units(MinTemp, Config['Units']['Temp'])

    # Store derived indoor AIR observations in Data dictionary
    derivedObs = {}
    derivedObs['inTemp'] = observation.Format(Temp, 'Temp')
    derivedObs['inTempMax'] = observation.Format(MaxTemp, 'Temp')
    derivedObs['inTempMin'] = observation.Format(MinTemp, 'Temp')

    # Update wfpiconsole display with derived indoor AIR observations
    updateDisplay(derivedObs, wfpiconsole, 'indoorAir')

    # Set flags for required API calls
    wfpiconsole.flagAPI[3] = 0

    # Return wfpiconsole object
    return wfpiconsole
def evtStrike(Msg, wfpiconsole):
    """ Handles lightning strike event Websocket messages received from either
        AIR or TEMPEST module

    INPUTS:
        Msg                 Websocket messages received from AIR or TEMPEST
        wfpiconsole         wfpiconsole object
    """

    # Discard duplicate evt_strike Websocket messages
    if 'evtStrikeMsg' in wfpiconsole.Obs:
        if wfpiconsole.Obs['evtStrikeMsg']['evt'][0] == Msg['evt'][0]:
            print('Discarding duplicate evt_strike Websocket message')
            return

    # Extract required observations from latest evt_strike Websocket JSON
    StrikeTime = [Msg['evt'][0], 's']
    StrikeDist = [Msg['evt'][1], 'km']

    # Store latest Rapid Wind wfpiconsole.Observation JSON message
    wfpiconsole.Obs['evtStrikeMsg'] = Msg

    # Calculate derived variables from evt_strike observations
    StrikeDeltaT = derive.StrikeDeltaT(StrikeTime)

    # Convert observation units as required
    StrikeDist = observation.Units(StrikeDist,
                                   wfpiconsole.config['Units']['Distance'])

    # Update wfpiconsole display with derived Rapid Wind observations
    wfpiconsole.Obs['StrikeDeltaT'] = observation.Format(
        StrikeDeltaT, 'TimeDelta')
    wfpiconsole.Obs['StrikeDist'] = observation.Format(StrikeDist,
                                                       'StrikeDistance')

    # If required, open secondary lightning panel to show strike has been
    # detected
    if wfpiconsole.config['Display']['LightningPanel'] == '1':
        for ii, Button in enumerate(wfpiconsole.CurrentConditions.buttonList):
            if "Lightning" in Button[2]:
                wfpiconsole.CurrentConditions.SwitchPanel([], Button)

    # Set and animate lightning bolt icon if LightningPanel panel is active
    if hasattr(wfpiconsole, 'LightningPanel'):
        for panel in getattr(wfpiconsole, 'LightningPanel'):
            panel.setLightningBoltIcon()
            panel.animateLightningBoltIcon()

    # Return wfpiconsole object
    return wfpiconsole
def Tempest(Msg, wfpiconsole):
    """ Handles Websocket messages received from TEMPEST module

    INPUTS:
        Msg                 Websocket messages received from TEMPEST module
        wfpiconsole         wfpiconsole object
    """

    # Replace missing observations from latest TEMPEST Websocket JSON with NaN
    Ob = [x if x != None else NaN for x in Msg['obs'][0]]

    # Discard duplicate TEMPEST Websocket messages
    if 'TempestMsg' in wfpiconsole.Obs:
        if wfpiconsole.Obs['TempestMsg']['obs'][0] == Ob[0]:
            print('Discarding duplicate TEMPEST Websocket message')
            return

    # Extract TEMPEST device ID, API flag, and station configuration object
    Device = wfpiconsole.config['Station']['TempestID']
    flagAPI = wfpiconsole.flagAPI[0]
    Config = wfpiconsole.config

    # Extract required observations from latest TEMPEST Websocket JSON
    Time = [Ob[0], 's']
    WindSpd = [Ob[2], 'mps']
    WindGust = [Ob[3], 'mps']
    WindDir = [Ob[4], 'degrees']
    Pres = [Ob[6], 'mb']
    Temp = [Ob[7], 'c']
    Humidity = [Ob[8], '%']
    UV = [Ob[10], 'index']
    Radiation = [Ob[11], 'Wm2']
    Rain = [Ob[12], 'mm']
    Strikes = [Ob[15], 'count']

    # Extract lightning strike data from the latest AIR Websocket JSON "Summary"
    # object
    StrikeTime = [
        Msg['summary']['strike_last_epoch']
        if 'strike_last_epoch' in Msg['summary'] else NaN, 's'
    ]
    StrikeDist = [
        Msg['summary']['strike_last_dist']
        if 'strike_last_dist' in Msg['summary'] else NaN, 'km'
    ]
    Strikes3hr = [
        Msg['summary']['strike_count_3h']
        if 'strike_count_3h' in Msg['summary'] else NaN, 'count'
    ]

    # Store latest TEMPEST Websocket message
    wfpiconsole.Obs['TempestMsg'] = Msg

    # Extract required derived observations
    minPres = wfpiconsole.Obs['MinPres']
    maxPres = wfpiconsole.Obs['MaxPres']
    minTemp = wfpiconsole.Obs['outTempMin']
    maxTemp = wfpiconsole.Obs['outTempMax']
    StrikeCount = {
        'Today': wfpiconsole.Obs['StrikesToday'],
        'Month': wfpiconsole.Obs['StrikesMonth'],
        'Year': wfpiconsole.Obs['StrikesYear']
    }
    rainAccum = {
        'Today': wfpiconsole.Obs['TodayRain'],
        'Yesterday': wfpiconsole.Obs['YesterdayRain'],
        'Month': wfpiconsole.Obs['MonthRain'],
        'Year': wfpiconsole.Obs['YearRain']
    }
    peakSun = wfpiconsole.Obs['peakSun']
    avgWind = wfpiconsole.Obs['AvgWind']
    maxGust = wfpiconsole.Obs['MaxGust']

    # Request TEMPEST data from the previous three hours
    Data3h = requestAPI.weatherflow.Last3h(Device, Time[0], Config)

    # Calculate derived variables from TEMPEST observations
    DewPoint = derive.DewPoint(Temp, Humidity)
    SLP = derive.SLP(Pres, Config)
    PresTrend = derive.SLPTrend(Pres, Time, Data3h, Config)
    FeelsLike = derive.FeelsLike(Temp, Humidity, WindSpd, Config)
    MaxTemp, MinTemp = derive.TempMaxMin(Time, Temp, maxTemp, minTemp, Device,
                                         Config, flagAPI)
    MaxPres, MinPres = derive.SLPMaxMin(Time, Pres, maxPres, minPres, Device,
                                        Config, flagAPI)
    StrikeCount = derive.StrikeCount(Strikes, StrikeCount, Device, Config,
                                     flagAPI)
    StrikeFreq = derive.StrikeFrequency(Time, Data3h, Config)
    StrikeDeltaT = derive.StrikeDeltaT(StrikeTime)
    FeelsLike = derive.FeelsLike(Temp, Humidity, WindSpd, Config)
    RainRate = derive.RainRate(Rain)
    rainAccum = derive.RainAccumulation(Rain, rainAccum, Device, Config,
                                        flagAPI)
    AvgWind = derive.MeanWindSpeed(WindSpd, avgWind, Device, Config, flagAPI)
    MaxGust = derive.MaxWindGust(WindGust, maxGust, Device, Config, flagAPI)
    WindSpd = derive.BeaufortScale(WindSpd)
    WindDir = derive.CardinalWindDirection(WindDir, WindSpd)
    peakSun = derive.peakSunHours(Radiation, peakSun, wfpiconsole.Astro,
                                  Device, Config, flagAPI)
    UVIndex = derive.UVIndex(UV)

    # Convert observation units as required
    Temp = observation.Units(Temp, Config['Units']['Temp'])
    MaxTemp = observation.Units(MaxTemp, Config['Units']['Temp'])
    MinTemp = observation.Units(MinTemp, Config['Units']['Temp'])
    DewPoint = observation.Units(DewPoint, Config['Units']['Temp'])
    FeelsLike = observation.Units(FeelsLike, Config['Units']['Temp'])
    SLP = observation.Units(SLP, Config['Units']['Pressure'])
    MaxPres = observation.Units(MaxPres, Config['Units']['Pressure'])
    MinPres = observation.Units(MinPres, Config['Units']['Pressure'])
    PresTrend = observation.Units(PresTrend, Config['Units']['Pressure'])
    StrikeDist = observation.Units(StrikeDist, Config['Units']['Distance'])
    RainRate = observation.Units(RainRate, Config['Units']['Precip'])
    TodayRain = observation.Units(rainAccum['Today'],
                                  Config['Units']['Precip'])
    YesterdayRain = observation.Units(rainAccum['Yesterday'],
                                      Config['Units']['Precip'])
    MonthRain = observation.Units(rainAccum['Month'],
                                  Config['Units']['Precip'])
    YearRain = observation.Units(rainAccum['Year'], Config['Units']['Precip'])
    WindSpd = observation.Units(WindSpd, Config['Units']['Wind'])
    WindDir = observation.Units(WindDir, Config['Units']['Direction'])
    WindGust = observation.Units(WindGust, Config['Units']['Wind'])
    AvgWind = observation.Units(AvgWind, Config['Units']['Wind'])
    MaxGust = observation.Units(MaxGust, Config['Units']['Wind'])
    FeelsLike = observation.Units(FeelsLike, Config['Units']['Temp'])

    # Store derived TEMPEST observations in dictionary
    derivedObs = {}
    derivedObs['outTemp'] = observation.Format(Temp, 'Temp')
    derivedObs['outTempMax'] = observation.Format(MaxTemp, 'Temp')
    derivedObs['outTempMin'] = observation.Format(MinTemp, 'Temp')
    derivedObs['DewPoint'] = observation.Format(DewPoint, 'Temp')
    derivedObs['FeelsLike'] = observation.Format(FeelsLike, 'Temp')
    derivedObs['Pres'] = observation.Format(SLP, 'Pressure')
    derivedObs['MaxPres'] = observation.Format(MaxPres, 'Pressure')
    derivedObs['MinPres'] = observation.Format(MinPres, 'Pressure')
    derivedObs['PresTrend'] = observation.Format(PresTrend, 'Pressure')
    derivedObs['StrikeDeltaT'] = observation.Format(StrikeDeltaT, 'TimeDelta')
    derivedObs['StrikeDist'] = observation.Format(StrikeDist, 'StrikeDistance')
    derivedObs['StrikeFreq'] = observation.Format(StrikeFreq,
                                                  'StrikeFrequency')
    derivedObs['Strikes3hr'] = observation.Format(Strikes3hr, 'StrikeCount')
    derivedObs['StrikesToday'] = observation.Format(StrikeCount['Today'],
                                                    'StrikeCount')
    derivedObs['StrikesMonth'] = observation.Format(StrikeCount['Month'],
                                                    'StrikeCount')
    derivedObs['StrikesYear'] = observation.Format(StrikeCount['Year'],
                                                   'StrikeCount')
    derivedObs['Humidity'] = observation.Format(Humidity, 'Humidity')
    derivedObs['FeelsLike'] = observation.Format(FeelsLike, 'Temp')
    derivedObs['RainRate'] = observation.Format(RainRate, 'Precip')
    derivedObs['TodayRain'] = observation.Format(TodayRain, 'Precip')
    derivedObs['YesterdayRain'] = observation.Format(YesterdayRain, 'Precip')
    derivedObs['MonthRain'] = observation.Format(MonthRain, 'Precip')
    derivedObs['YearRain'] = observation.Format(YearRain, 'Precip')
    derivedObs['WindSpd'] = observation.Format(WindSpd, 'Wind')
    derivedObs['WindGust'] = observation.Format(WindGust, 'Wind')
    derivedObs['AvgWind'] = observation.Format(AvgWind, 'Wind')
    derivedObs['MaxGust'] = observation.Format(MaxGust, 'Wind')
    derivedObs['WindDir'] = observation.Format(WindDir, 'Direction')
    derivedObs['Radiation'] = observation.Format(Radiation, 'Radiation')
    derivedObs['peakSun'] = observation.Format(peakSun, 'peakSun')
    derivedObs['UVIndex'] = observation.Format(UVIndex, 'UV')

    # Update wfpiconsole display with derived TEMPEST observations
    updateDisplay(derivedObs, wfpiconsole, 'Tempest')

    # Set flags for required API calls
    wfpiconsole.flagAPI[0] = 0

    # Return wfpiconsole object
    return wfpiconsole
def outdoorAir(Msg, wfpiconsole):
    """ Handles Websocket messages received from outdoor AIR module

    INPUTS:
        Msg                 Websocket messages received from outdoor AIR module
        wfpiconsole         wfpiconsole object
    """

    # Replace missing observations in latest outdoor AIR Websocket JSON with NaN
    Ob = [x if x != None else NaN for x in Msg['obs'][0]]

    # Discard duplicate outdoor AIR Websocket messages
    if 'outAirMsg' in wfpiconsole.Obs:
        if wfpiconsole.Obs['outAirMsg']['obs'][0] == Ob[0]:
            print('Discarding duplicate outdoor AIR Websocket message')
            return

    # Extract outdoor AIR device ID and API flag, and station configuration
    # object
    Device = wfpiconsole.config['Station']['OutAirID']
    flagAPI = wfpiconsole.flagAPI[2]
    Config = wfpiconsole.config

    # Extract required observations from latest outdoor AIR Websocket JSON
    Time = [Ob[0], 's']
    Pres = [Ob[1], 'mb']
    Temp = [Ob[2], 'c']
    Humidity = [Ob[3], '%']
    Strikes = [Ob[4], 'count']

    # Extract lightning strike data from the latest outdoor AIR Websocket JSON
    # "Summary" object
    StrikeTime = [
        Msg['summary']['strike_last_epoch']
        if 'strike_last_epoch' in Msg['summary'] else NaN, 's'
    ]
    StrikeDist = [
        Msg['summary']['strike_last_dist']
        if 'strike_last_dist' in Msg['summary'] else NaN, 'km'
    ]
    Strikes3hr = [
        Msg['summary']['strike_count_3h']
        if 'strike_count_3h' in Msg['summary'] else NaN, 'count'
    ]

    # Extract required derived observations
    minPres = wfpiconsole.Obs['MinPres']
    maxPres = wfpiconsole.Obs['MaxPres']
    minTemp = wfpiconsole.Obs['outTempMin']
    maxTemp = wfpiconsole.Obs['outTempMax']
    StrikeCount = {
        'Today': wfpiconsole.Obs['StrikesToday'],
        'Month': wfpiconsole.Obs['StrikesMonth'],
        'Year': wfpiconsole.Obs['StrikesYear']
    }

    # Request outdoor AIR data from the previous three hours
    Data3h = requestAPI.weatherflow.Last3h(Device, Time[0], Config)

    # Store latest outdoor AIR Websocket message
    wfpiconsole.Obs['outAirMsg'] = Msg

    # Extract required observations from latest SKY Websocket JSON
    while not 'SkyMsg' in wfpiconsole.Obs:
        time.sleep(0.01)
    Ob = [x if x != None else NaN for x in wfpiconsole.Obs['SkyMsg']['obs'][0]]
    WindSpd = [Ob[5], 'mps']

    # Calculate derived variables from AIR observations
    DewPoint = derive.DewPoint(Temp, Humidity)
    SLP = derive.SLP(Pres, Config)
    PresTrend = derive.SLPTrend(Pres, Time, Data3h, Config)
    FeelsLike = derive.FeelsLike(Temp, Humidity, WindSpd, Config)
    MaxTemp, MinTemp = derive.TempMaxMin(Time, Temp, maxTemp, minTemp, Device,
                                         Config, flagAPI)
    MaxPres, MinPres = derive.SLPMaxMin(Time, Pres, maxPres, minPres, Device,
                                        Config, flagAPI)
    StrikeCount = derive.StrikeCount(Strikes, StrikeCount, Device, Config,
                                     flagAPI)
    StrikeFreq = derive.StrikeFrequency(Time, Data3h, Config)
    StrikeDeltaT = derive.StrikeDeltaT(StrikeTime)

    # Convert observation units as required
    Temp = observation.Units(Temp, Config['Units']['Temp'])
    MaxTemp = observation.Units(MaxTemp, Config['Units']['Temp'])
    MinTemp = observation.Units(MinTemp, Config['Units']['Temp'])
    DewPoint = observation.Units(DewPoint, Config['Units']['Temp'])
    FeelsLike = observation.Units(FeelsLike, Config['Units']['Temp'])
    SLP = observation.Units(SLP, Config['Units']['Pressure'])
    MaxPres = observation.Units(MaxPres, Config['Units']['Pressure'])
    MinPres = observation.Units(MinPres, Config['Units']['Pressure'])
    PresTrend = observation.Units(PresTrend, Config['Units']['Pressure'])
    StrikeDist = observation.Units(StrikeDist, Config['Units']['Distance'])

    # Store derived outdoor AIR observations in dictionary
    derivedObs = {}
    derivedObs['outTemp'] = observation.Format(Temp, 'Temp')
    derivedObs['outTempMax'] = observation.Format(MaxTemp, 'Temp')
    derivedObs['outTempMin'] = observation.Format(MinTemp, 'Temp')
    derivedObs['DewPoint'] = observation.Format(DewPoint, 'Temp')
    derivedObs['FeelsLike'] = observation.Format(FeelsLike, 'Temp')
    derivedObs['Pres'] = observation.Format(SLP, 'Pressure')
    derivedObs['MaxPres'] = observation.Format(MaxPres, 'Pressure')
    derivedObs['MinPres'] = observation.Format(MinPres, 'Pressure')
    derivedObs['PresTrend'] = observation.Format(PresTrend, 'Pressure')
    derivedObs['StrikeDeltaT'] = observation.Format(StrikeDeltaT, 'TimeDelta')
    derivedObs['StrikeDist'] = observation.Format(StrikeDist, 'StrikeDistance')
    derivedObs['StrikeFreq'] = observation.Format(StrikeFreq,
                                                  'StrikeFrequency')
    derivedObs['Strikes3hr'] = observation.Format(Strikes3hr, 'StrikeCount')
    derivedObs['StrikesToday'] = observation.Format(StrikeCount['Today'],
                                                    'StrikeCount')
    derivedObs['StrikesMonth'] = observation.Format(StrikeCount['Month'],
                                                    'StrikeCount')
    derivedObs['StrikesYear'] = observation.Format(StrikeCount['Year'],
                                                   'StrikeCount')
    derivedObs['Humidity'] = observation.Format(Humidity, 'Humidity')

    # Update wfpiconsole display with derived outdoor AIR observations
    updateDisplay(derivedObs, wfpiconsole, 'outdoorAir')

    # Set flags for required API calls
    wfpiconsole.flagAPI[2] = 0

    # Return wfpiconsole object
    return wfpiconsole
def Sky(Msg, wfpiconsole):
    """ Handles Websocket messages received from SKY module

    INPUTS:
        Msg                 Websocket messages received from SKY module
        wfpiconsole         wfpiconsole object
    """

    # Replace missing observations from latest SKY Websocket JSON with NaN
    Ob = [x if x != None else NaN for x in Msg['obs'][0]]

    # Discard duplicate SKY Websocket messages
    if 'SkyMsg' in wfpiconsole.Obs:
        if wfpiconsole.Obs['SkyMsg']['obs'][0] == Ob[0]:
            print('Discarding duplicate SKY Websocket message')
            return

    # Extract SKY device ID and API flag, and station configuration object
    Device = wfpiconsole.config['Station']['SkyID']
    flagAPI = wfpiconsole.flagAPI[1]
    Config = wfpiconsole.config

    # Extract required observations from latest SKY Websocket JSON
    Time = [Ob[0], 's']
    UV = [Ob[2], 'index']
    Rain = [Ob[3], 'mm']
    WindSpd = [Ob[5], 'mps']
    WindGust = [Ob[6], 'mps']
    WindDir = [Ob[7], 'degrees']
    Radiation = [Ob[10], 'Wm2']

    # Store latest SKY Websocket message
    wfpiconsole.Obs['SkyMsg'] = Msg

    # Extract required observations from latest AIR Websocket observations
    while not 'outAirMsg' in wfpiconsole.Obs:
        time.sleep(0.01)
    Ob = [
        x if x != None else NaN for x in wfpiconsole.Obs['outAirMsg']['obs'][0]
    ]
    Temp = [Ob[2], 'c']
    Humidity = [Ob[3], '%']

    # Set wind direction to None if wind speed is zero
    if WindSpd[0] == 0:
        WindDir = [None, 'degrees']

    # Extract required derived observations
    rainAccum = {
        'Today': wfpiconsole.Obs['TodayRain'],
        'Yesterday': wfpiconsole.Obs['YesterdayRain'],
        'Month': wfpiconsole.Obs['MonthRain'],
        'Year': wfpiconsole.Obs['YearRain']
    }
    peakSun = wfpiconsole.Obs['peakSun']
    avgWind = wfpiconsole.Obs['AvgWind']
    maxGust = wfpiconsole.Obs['MaxGust']

    # Calculate derived variables from SKY observations
    FeelsLike = derive.FeelsLike(Temp, Humidity, WindSpd, Config)
    RainRate = derive.RainRate(Rain)
    rainAccum = derive.RainAccumulation(Rain, rainAccum, Device, Config,
                                        flagAPI)
    AvgWind = derive.MeanWindSpeed(WindSpd, avgWind, Device, Config, flagAPI)
    MaxGust = derive.MaxWindGust(WindGust, maxGust, Device, Config, flagAPI)
    WindSpd = derive.BeaufortScale(WindSpd)
    WindDir = derive.CardinalWindDirection(WindDir, WindSpd)
    peakSun = derive.peakSunHours(Radiation, peakSun, wfpiconsole.Astro,
                                  Device, Config, flagAPI)
    UVIndex = derive.UVIndex(UV)

    # Convert observation units as required
    RainRate = observation.Units(RainRate, Config['Units']['Precip'])
    TodayRain = observation.Units(rainAccum['Today'],
                                  Config['Units']['Precip'])
    YesterdayRain = observation.Units(rainAccum['Yesterday'],
                                      Config['Units']['Precip'])
    MonthRain = observation.Units(rainAccum['Month'],
                                  Config['Units']['Precip'])
    YearRain = observation.Units(rainAccum['Year'], Config['Units']['Precip'])
    WindSpd = observation.Units(WindSpd, Config['Units']['Wind'])
    WindDir = observation.Units(WindDir, Config['Units']['Direction'])
    WindGust = observation.Units(WindGust, Config['Units']['Wind'])
    AvgWind = observation.Units(AvgWind, Config['Units']['Wind'])
    MaxGust = observation.Units(MaxGust, Config['Units']['Wind'])
    FeelsLike = observation.Units(FeelsLike, Config['Units']['Temp'])

    # Store derived SKY observations in dictionary
    derivedObs = {}
    derivedObs['FeelsLike'] = observation.Format(FeelsLike, 'Temp')
    derivedObs['RainRate'] = observation.Format(RainRate, 'Precip')
    derivedObs['TodayRain'] = observation.Format(TodayRain, 'Precip')
    derivedObs['YesterdayRain'] = observation.Format(YesterdayRain, 'Precip')
    derivedObs['MonthRain'] = observation.Format(MonthRain, 'Precip')
    derivedObs['YearRain'] = observation.Format(YearRain, 'Precip')
    derivedObs['WindSpd'] = observation.Format(WindSpd, 'Wind')
    derivedObs['WindGust'] = observation.Format(WindGust, 'Wind')
    derivedObs['AvgWind'] = observation.Format(AvgWind, 'Wind')
    derivedObs['MaxGust'] = observation.Format(MaxGust, 'Wind')
    derivedObs['WindDir'] = observation.Format(WindDir, 'Direction')
    derivedObs['Radiation'] = observation.Format(Radiation, 'Radiation')
    derivedObs['peakSun'] = observation.Format(peakSun, 'peakSun')
    derivedObs['UVIndex'] = observation.Format(UVIndex, 'UV')

    # Update wfpiconsole display with derived SKY observations
    updateDisplay(derivedObs, wfpiconsole, 'Sky')

    # Set flags for required API calls
    wfpiconsole.flagAPI[1] = 0

    # Return wfpiconsole object
    return wfpiconsole
Example #7
0
def Download(metData, Config, dt):
    """ Download the latest daily and hourly weather forecast data using the
    WeatherFlow BetterForecast API

    INPUTS:
        metData             Dictionary holding weather forecast data
        Config              Station configuration
        dt                  Time in seconds since function last called

    OUTPUT:
        metData             Dictionary holding weather forecast data
    """

    # Get current time in station time zone
    Tz = pytz.timezone(Config['Station']['Timezone'])
    funcCalled = datetime.now(pytz.utc).astimezone(Tz)
    Midnight = int(
        Tz.localize(datetime(funcCalled.year, funcCalled.month,
                             funcCalled.day)).timestamp())
    funcError = 0

    # Set time format based on user configuration
    if Config['Display']['TimeFormat'] == '12 hr':
        if Config['System']['Hardware'] != 'Other':
            TimeFormat = '%-I %P'
        else:
            TimeFormat = '%I %p'
    else:
        TimeFormat = '%H:%M'

    # Download latest forecast data
    Data = requestAPI.weatherflow.Forecast(Config)

    # Verify API response and extract forecast
    if requestAPI.weatherflow.verifyResponse(Data, 'forecast'):
        metData['Dict'] = Data.json()
    else:
        funcError = 1
        if not 'Dict' in metData:
            metData['Dict'] = {}

    # Extract all forecast data from WeatherFlow JSON object
    try:
        # Extract all hourly and daily forecasts
        hourlyForecasts = (metData['Dict']['forecast']['hourly'])
        dailyForecasts = (metData['Dict']['forecast']['daily'])

        # Extract 'valid from' time of all available hourly forecasts and
        # retrieve forecast for the current hour
        Hours = list(forecast['time'] for forecast in hourlyForecasts)
        hoursInd = bisect.bisect(Hours, int(UNIX.time()))
        hourlyCurrent = hourlyForecasts[hoursInd]
        hourlyLocalDay = hourlyCurrent['local_day']

        # Extract 'Valid' until time of forecast for current hour
        Valid = Hours[bisect.bisect(Hours, int(UNIX.time()))]
        Valid = datetime.fromtimestamp(Valid, pytz.utc).astimezone(Tz)

        # Extract 'day_start_local' time of all available daily forecasts and
        # retrieve forecast for the current day
        dailyDayNum = list(forecast['day_num'] for forecast in dailyForecasts)
        dailyCurrent = dailyForecasts[dailyDayNum.index(hourlyLocalDay)]

        # Extract weather variables from current hourly forecast
        Temp = [hourlyCurrent['air_temperature'], 'c']
        WindSpd = [hourlyCurrent['wind_avg'], 'mps']
        WindGust = [hourlyCurrent['wind_gust'], 'mps']
        WindDir = [hourlyCurrent['wind_direction'], 'degrees']
        Icon = hourlyCurrent['icon'].replace('cc-', '')

        # Extract Precipitation Type, Percent, and Amount from current hourly
        # forecast
        if 'precip_type' in hourlyCurrent:
            PrecipType = hourlyCurrent['precip_type']
            if PrecipType not in ['rain', 'snow']:
                PrecipType = 'rain'
        else:
            PrecipType = 'rain'
        if 'precip_probability' in hourlyCurrent:
            PrecipPercnt = [hourlyCurrent['precip_probability'], '%']
        else:
            PrecipPercnt = [0, '%']
        if 'precip' in hourlyCurrent:
            PrecipAmount = [hourlyCurrent['precip'], 'mm']
        else:
            PrecipAmount = [0, 'mm']

        # Extract weather variables from current daily forecast
        highTemp = [dailyCurrent['air_temp_high'], 'c']
        lowTemp = [dailyCurrent['air_temp_low'], 'c']
        precipDay = [dailyCurrent['precip_probability'], '%']

        # Extract list of expected conditions and find time when expected conditions
        # will change
        conditionList = list(forecast['conditions']
                             for forecast in hourlyForecasts[hoursInd:])
        try:
            Ind = next(i for i, C in enumerate(conditionList)
                       if C != hourlyCurrent['conditions'])
        except StopIteration:
            Ind = len(conditionList) - 1
        Time = datetime.fromtimestamp(Hours[Ind], pytz.utc).astimezone(Tz)
        if Time.date() == funcCalled.date():
            Conditions = hourlyCurrent['conditions'].capitalize(
            ) + ' until ' + datetime.strftime(Time, TimeFormat) + ' today'
        elif Time.date() == funcCalled.date() + timedelta(days=1):
            Conditions = hourlyCurrent['conditions'].capitalize(
            ) + ' until ' + datetime.strftime(Time, TimeFormat) + ' tomorrow'
        else:
            Conditions = hourlyCurrent['conditions'].capitalize(
            ) + ' until ' + datetime.strftime(
                Time, TimeFormat) + ' on ' + Time.strftime('%A')

        # Calculate derived variables from forecast
        WindDir = derive.CardinalWindDirection(WindDir, WindSpd)

        # Convert forecast units as required
        Temp = observation.Units(Temp, Config['Units']['Temp'])
        highTemp = observation.Units(highTemp, Config['Units']['Temp'])
        lowTemp = observation.Units(lowTemp, Config['Units']['Temp'])
        WindSpd = observation.Units(WindSpd, Config['Units']['Wind'])
        WindGust = observation.Units(WindGust, Config['Units']['Wind'])
        WindDir = observation.Units(WindDir, Config['Units']['Direction'])
        PrecipAmount = observation.Units(PrecipAmount,
                                         Config['Units']['Precip'])

        # Define and format labels
        metData['Time'] = funcCalled
        metData['Valid'] = datetime.strftime(Valid, TimeFormat)
        metData['Temp'] = observation.Format(Temp, 'forecastTemp')
        metData['highTemp'] = observation.Format(highTemp, 'forecastTemp')
        metData['lowTemp'] = observation.Format(lowTemp, 'forecastTemp')
        metData['WindSpd'] = observation.Format(WindSpd, 'forecastWind')
        metData['WindGust'] = observation.Format(WindGust, 'forecastWind')
        metData['WindDir'] = observation.Format(WindDir, 'Direction')
        metData['PrecipPercnt'] = observation.Format(PrecipPercnt, 'Humidity')
        metData['PrecipDay'] = observation.Format(precipDay, 'Humidity')
        metData['PrecipAmount'] = observation.Format(PrecipAmount, 'Precip')
        metData['PrecipType'] = PrecipType
        metData['Conditions'] = Conditions
        metData['Icon'] = Icon
        metData['Status'] = ''

        # Check expected conditions icon is recognised
        if Icon in [
                'clear-day', 'clear-night', 'rainy', 'possibly-rainy-day',
                'possibly-rainy-night', 'snow', 'possibly-snow-day',
                'possibly-snow-night', 'sleet', 'possibly-sleet-day',
                'possibly-sleet-night', 'thunderstorm',
                'possibly-thunderstorm-day'
                'possibly-thunderstorm-night', 'windy', 'foggy', 'cloudy',
                'partly-cloudy-day', 'partly-cloudy-night'
        ]:
            metData['Icon'] = Icon
        else:
            metData['Icon'] = '--'

    # Unable to extract forecast data from JSON object. Set set forecast
    # variables to blank and indicate to user that forecast is unavailable
    except (IndexError, KeyError, ValueError):
        metData['Time'] = funcCalled
        metData['Valid'] = '--'
        metData['Temp'] = '--'
        metData['highTemp'] = '--'
        metData['lowTemp'] = '--'
        metData['WindSpd'] = '--'
        metData['WindGust'] = '--'
        metData['WindDir'] = '--'
        metData['PrecipPercnt'] = '--'
        metData['PrecipDay'] = '--'
        metData['PrecipAmount'] = '--'
        metData['PrecipType'] = '--'
        metData['Conditions'] = ''
        metData['Icon'] = '--'
        metData['Status'] = 'Forecast currently\nunavailable...'
        funcError = 1

    # Schedule new forecast to be downloaded at the top of the next hour, or in
    # 5 minutes if error was detected. Note secondsSched refers to number of
    # seconds since the function was last called.
    Now = datetime.now(pytz.utc).astimezone(Tz)
    downloadTime = Tz.localize(
        datetime.combine(Now.date(), time(Now.hour, 0, 0)) +
        timedelta(hours=1))
    if not funcError:
        secondsSched = math.ceil((downloadTime - funcCalled).total_seconds())
    else:
        secondsSched = 300 + math.ceil((funcCalled - Now).total_seconds())
    Clock.schedule_once(partial(Download, metData, Config), secondsSched)

    # Return metData dictionary
    return metData
Example #8
0
def ExtractDaily(app):
    metData = app.MetData
    dailyForecast = app.DailyForecast
    Config = app.config
    """
    INPUTS:
        metData             Dictionary holding weather forecast data
        dailyForecast       screen with array of panels
        Config              Station configuration
    """

    # Get current time in station time zone
    Tz = pytz.timezone(Config['Station']['Timezone'])
    Now = datetime.now(pytz.utc).astimezone(Tz)
    weekdays = [ 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun' ]

    for i in range(len(dailyForecast.panels)):
        d = {}
        try:
            wfDayDict = metData['Dict']['forecast']['daily'][i]

            # Extract weather variables from WeatherFlow forecast
            date    = "%02d/%02d" % (wfDayDict['month_num'], wfDayDict['day_num'])
            tempMax = [wfDayDict['air_temp_high'], 'c']
            tempMin = [wfDayDict['air_temp_low'], 'c']
            precip  = [wfDayDict['precip_probability'], '%']
            weather =  wfDayDict['icon']
            dt = datetime.fromtimestamp(wfDayDict['sunrise'], Tz)
            weekday = calendar.weekday(dt.year, wfDayDict['month_num'], wfDayDict['day_num'])
        except IndexError:
            date    = '0/0'
            tempMax = [ 0, 'c']
            tempMin = [ 0, 'c']
            precip  = [ 0, '%']
            weather = 'XX'
            weekday = 0
        except KeyError:
            date    = '0/0'
            tempMax = [ 0, 'c']
            tempMin = [ 0, 'c']
            precip  = [ 0, '%']
            weather = 'XX'
            weekday = 0

        # Convert forecast units as required
        tempMax = observation.Units(tempMax, Config['Units']['Temp'])
        tempMin = observation.Units(tempMin, Config['Units']['Temp'])

        dailyForecast.panels[i].date = date
        dailyForecast.panels[i].tempMax = ['{:.0f}'.format(tempMax[0]), tempMax[1]]
        dailyForecast.panels[i].tempMin = ['{:.0f}'.format(tempMin[0]), tempMin[1]]
        dailyForecast.panels[i].precip =  ['{:.0f}'.format(precip[0]), ' %']
        dailyForecast.panels[i].weekday = weekdays[weekday]

        # Define weather icon
        # Check expected conditions icon is recognised
        if weather in  ['clear-day', 'clear-night', 'rainy', 'possibly-rainy-day',
                        'possibly-rainy-night', 'snow', 'possibly-snow-day',
                        'possibly-snow-night', 'sleet', 'possibly-sleet-day',
                        'possibly-sleet-night', 'thunderstorm', 'possibly-thunderstorm-day'
                        'possibly-thunderstorm-night', 'windy', 'foggy', 'cloudy',
                        'partly-cloudy-day','partly-cloudy-night']:
            dailyForecast.panels[i].weather = weather
        else:
            dailyForecast.panels[i].weather = '--'
Example #9
0
def ExtractMetOffice(metData, Config):
    """ Parse the weather forecast from the UK MetOffice

    INPUTS:
        metData             Dictionary holding weather forecast data
        Config              Station configuration

    OUTPUT:
        metData             Dictionary holding weather forecast data
    """

    # Get current time in station time zone
    Tz = pytz.timezone(Config['Station']['Timezone'])
    Now = datetime.now(pytz.utc).astimezone(Tz)

    # Extract all forecast data from MetOffice JSON file. If  forecast is
    # unavailable, set forecast variables to blank and indicate to user that
    # forecast is unavailable
    try:
        Issued = str(metData['Dict']['SiteRep']['DV']['dataDate'][11:-4])
        metDict = (metData['Dict']['SiteRep']['DV']['Location']['Period'])
    except KeyError:
        metData['Time'] = Now
        metData['Temp'] = '--'
        metData['WindDir'] = '--'
        metData['WindSpd'] = '--'
        metData['Weather'] = 'ForecastUnavailable'
        metData['Precip'] = '--'
        metData['Valid'] = '--'

        # Attempt to download forecast again in 10 minutes and return
        # metData dictionary
        Clock.schedule_once(lambda dt: Download(metData, Config), 600)
        return metData

    # Extract date of all available forecasts, and retrieve forecast for
    # today
    Dates = list(item['value'] for item in metDict)
    metDict = metDict[Dates.index(Now.strftime('%Y-%m-%dZ'))]['Rep']

    # Extract 'valid from' time of all available three-hourly forecasts, and
    # retrieve forecast for the current three-hour period
    Times = list(int(item['$']) // 60 for item in metDict)
    metDict = metDict[bisect.bisect(Times, Now.hour) - 1]

    # Extract 'valid until' time for the retrieved forecast
    Valid = Times[bisect.bisect(Times, Now.hour) - 1] + 3
    if Valid == 24:
        Valid = 0

    # Extract weather variables from MetOffice forecast
    Temp = [float(metDict['T']), 'c']
    WindSpd = [float(metDict['S']) / 2.2369362920544, 'mps']
    WindDir = [metDict['D'], 'cardinal']
    Precip = [metDict['Pp'], '%']
    Weather = metDict['W']

    # Convert forecast units as required
    Temp = observation.Units(Temp, Config['Units']['Temp'])
    WindSpd = observation.Units(WindSpd, Config['Units']['Wind'])

    # Define and format labels
    metData['Time'] = Now
    metData['Issued'] = Issued
    metData['Valid'] = '{:02.0f}'.format(Valid) + ':00'
    metData['Temp'] = ['{:.1f}'.format(Temp[0]), Temp[1]]
    metData['WindDir'] = WindDir[0]
    metData['WindSpd'] = ['{:.0f}'.format(WindSpd[0]), WindSpd[1]]
    metData['Weather'] = Weather
    metData['Precip'] = Precip[0]

    # Return metData dictionary
    return metData
Example #10
0
def ExtractDarkSky(metData, Config):
    """ Parse the weather forecast from DarkSky

    INPUTS:
        metData             Dictionary holding weather forecast data
        Config              Station configuration

    OUTPUT:
        metData             Dictionary holding weather forecast data
    """

    # Get current time in station time zone
    Tz = pytz.timezone(Config['Station']['Timezone'])
    Now = datetime.now(pytz.utc).astimezone(Tz)

    # Extract all forecast data from DarkSky JSON file. If  forecast is
    # unavailable, set forecast variables to blank and indicate to user that
    # forecast is unavailable
    Tz = pytz.timezone(Config['Station']['Timezone'])
    try:
        metDict = (metData['Dict']['hourly']['data'])
    except KeyError:
        metData['Time'] = Now
        metData['Temp'] = '--'
        metData['WindDir'] = '--'
        metData['WindSpd'] = '--'
        metData['Weather'] = 'ForecastUnavailable'
        metData['Precip'] = '--'
        metData['Valid'] = '--'

        # Attempt to download forecast again in 10 minutes and return
        # metData dictionary
        Clock.schedule_once(lambda dt: Download(metData, Config), 600)
        return metData

    # Extract 'valid from' time of all available hourly forecasts, and
    # retrieve forecast for the current hourly period
    Times = list(item['time'] for item in metDict)
    metDict = metDict[bisect.bisect(Times, int(time.time())) - 1]

    # Extract 'Issued' and 'Valid' times
    Issued = Times[0]
    Valid = Times[bisect.bisect(Times, int(time.time()))]
    Issued = datetime.fromtimestamp(Issued, pytz.utc).astimezone(Tz)
    Valid = datetime.fromtimestamp(Valid, pytz.utc).astimezone(Tz)

    # Extract weather variables from DarkSky forecast
    Temp = [metDict['temperature'], 'c']
    WindSpd = [metDict['windSpeed'] / 2.2369362920544, 'mps']
    WindDir = [metDict['windBearing'], 'degrees']
    Precip = [metDict['precipProbability'] * 100, '%']
    Weather = metDict['icon']

    # Convert forecast units as required
    Temp = observation.Units(Temp, Config['Units']['Temp'])
    WindSpd = observation.Units(WindSpd, Config['Units']['Wind'])

    # Define and format labels
    metData['Time'] = Now
    metData['Issued'] = datetime.strftime(Issued, '%H:%M')
    metData['Valid'] = datetime.strftime(Valid, '%H:%M')
    metData['Temp'] = ['{:.1f}'.format(Temp[0]), Temp[1]]
    metData['WindDir'] = derive.CardinalWindDirection(WindDir)[2]
    metData['WindSpd'] = ['{:.0f}'.format(WindSpd[0]), WindSpd[1]]
    metData['Precip'] = '{:.0f}'.format(Precip[0])

    # Define weather icon
    if Weather == 'clear-day':
        metData['Weather'] = '1'
    elif Weather == 'clear-night':
        metData['Weather'] = '0'
    elif Weather == 'rain':
        metData['Weather'] = '12'
    elif Weather == 'snow':
        metData['Weather'] = '27'
    elif Weather == 'sleet':
        metData['Weather'] = '18'
    elif Weather == 'wind':
        metData['Weather'] = 'wind'
    elif Weather == 'fog':
        metData['Weather'] = '6'
    elif Weather == 'cloudy':
        metData['Weather'] = '7'
    elif Weather == 'partly-cloudy-day':
        metData['Weather'] = '3'
    elif Weather == 'partly-cloudy-night':
        metData['Weather'] = '2'
    else:
        metData['Weather'] = 'ForecastUnavailable'

    # Return metData dictionary
    return metData
Example #11
0
    def format_derived_variables(self, config, device_type):

        """ Format derived variables from available device observations

        INPUTS:
            config              Console configuration object
            device_type         Device type
        """

        # Convert derived variable units from obs_out_air and obs_st observations
        if device_type in ('obs_out_air', 'obs_st', 'obs_all'):
            outTemp        = observation.Units(self.device_obs['outTemp'],              config['Units']['Temp'])
            feelsLike      = observation.Units(self.derive_obs['feelsLike'],            config['Units']['Temp'])
            dewPoint       = observation.Units(self.derive_obs['dewPoint'],             config['Units']['Temp'])
            outTempDiff    = observation.Units(self.derive_obs['outTempDiff'],          config['Units']['Temp'])
            outTempTrend   = observation.Units(self.derive_obs['outTempTrend'],         config['Units']['Temp'])
            outTempMax     = observation.Units(self.derive_obs['outTempMax'],           config['Units']['Temp'])
            outTempMin     = observation.Units(self.derive_obs['outTempMin'],           config['Units']['Temp'])
            humidity       = observation.Units(self.device_obs['humidity'],             config['Units']['Other'])
            SLP            = observation.Units(self.derive_obs['SLP'],                  config['Units']['Pressure'])
            SLPTrend       = observation.Units(self.derive_obs['SLPTrend'],             config['Units']['Pressure'])
            SLPMax         = observation.Units(self.derive_obs['SLPMax'],               config['Units']['Pressure'])
            SLPMin         = observation.Units(self.derive_obs['SLPMin'],               config['Units']['Pressure'])
            strikeDist     = observation.Units(self.device_obs['strikeDist'],           config['Units']['Distance'])
            strikeDeltaT   = observation.Units(self.derive_obs['strikeDeltaT'],         config['Units']['Other'])
            strikeFreq     = observation.Units(self.derive_obs['strikeFreq'],           config['Units']['Other'])
            strike3hr      = observation.Units(self.device_obs['strike3hr'],            config['Units']['Other'])
            strikeToday    = observation.Units(self.derive_obs['strikeCount']['today'], config['Units']['Other'])
            strikeMonth    = observation.Units(self.derive_obs['strikeCount']['month'], config['Units']['Other'])
            strikeYear     = observation.Units(self.derive_obs['strikeCount']['year'],  config['Units']['Other'])

        # Convert derived variable units from obs_sky and obs_st observations
        if device_type in ('obs_sky', 'obs_st', 'obs_all'):
            rainRate       = observation.Units(self.derive_obs['rainRate'],               config['Units']['Precip'])
            todayRain      = observation.Units(self.derive_obs['rainAccum']['today'],     config['Units']['Precip'])
            yesterdayRain  = observation.Units(self.derive_obs['rainAccum']['yesterday'], config['Units']['Precip'])
            monthRain      = observation.Units(self.derive_obs['rainAccum']['month'],     config['Units']['Precip'])
            yearRain       = observation.Units(self.derive_obs['rainAccum']['year'],      config['Units']['Precip'])
            radiation      = observation.Units(self.device_obs['radiation'],              config['Units']['Other'])
            uvIndex        = observation.Units(self.derive_obs['uvIndex'],                config['Units']['Other'])
            peakSun        = observation.Units(self.derive_obs['peakSun'],                config['Units']['Other'])
            windSpd        = observation.Units(self.derive_obs['windSpd'],                config['Units']['Wind'])
            windDir        = observation.Units(self.derive_obs['windDir'],                config['Units']['Direction'])
            windGust       = observation.Units(self.device_obs['windGust'],               config['Units']['Wind'])
            windAvg        = observation.Units(self.derive_obs['windAvg'],                config['Units']['Wind'])
            windMax        = observation.Units(self.derive_obs['gustMax'],                config['Units']['Wind'])

        # Convert derived variable units from obs_in_air observations
        if device_type in ('obs_in_air',  'obs_all'):
            inTemp         = observation.Units(self.device_obs['inTemp'],    config['Units']['Temp'])
            inTempMax      = observation.Units(self.derive_obs['inTempMax'], config['Units']['Temp'])
            inTempMin      = observation.Units(self.derive_obs['inTempMin'], config['Units']['Temp'])

        # Convert derived variable units from rapid_wind observations
        if device_type in ('rapid_wind', 'obs_all'):
            rapidWindSpd   = observation.Units(self.device_obs['rapidWindSpd'], config['Units']['Wind'])
            rapidWindDir   = observation.Units(self.derive_obs['rapidWindDir'], 'degrees')

        # Convert derived variable units from available evt_strike observations
        if device_type in ('evt_strike', 'obs_all'):
            strikeDist     = observation.Units(self.device_obs['strikeDist'],   config['Units']['Distance'])
            strikeDeltaT   = observation.Units(self.derive_obs['strikeDeltaT'], config['Units']['Other'])

        # Format derived variables from obs_air and obs_st observations
        if device_type in ('obs_out_air', 'obs_st', 'obs_all'):
            self.display_obs['outTemp']       = observation.Format(outTemp,      'Temp')
            self.display_obs['FeelsLike']     = observation.Format(feelsLike,    'Temp')
            self.display_obs['DewPoint']      = observation.Format(dewPoint,     'Temp')
            self.display_obs['outTempDiff']   = observation.Format(outTempDiff,  'Temp')
            self.display_obs['outTempTrend']  = observation.Format(outTempTrend, 'Temp')
            self.display_obs['outTempMax']    = observation.Format(outTempMax,   ['Temp', 'Time'], config)
            self.display_obs['outTempMin']    = observation.Format(outTempMin,   ['Temp', 'Time'], config)
            self.display_obs['Humidity']      = observation.Format(humidity,     'Humidity')
            self.display_obs['SLP']           = observation.Format(SLP,          'Pressure')
            self.display_obs['SLPTrend']      = observation.Format(SLPTrend,     'Pressure')
            self.display_obs['SLPMax']        = observation.Format(SLPMax,       ['Pressure', 'Time'], config)
            self.display_obs['SLPMin']        = observation.Format(SLPMin,       ['Pressure', 'Time'], config)
            self.display_obs['StrikeDist']    = observation.Format(strikeDist,   'StrikeDistance')
            self.display_obs['StrikeDeltaT']  = observation.Format(strikeDeltaT, 'TimeDelta')
            self.display_obs['StrikeFreq']    = observation.Format(strikeFreq,   'StrikeFrequency')
            self.display_obs['Strikes3hr']    = observation.Format(strike3hr,    'StrikeCount')
            self.display_obs['StrikesToday']  = observation.Format(strikeToday,  'StrikeCount')
            self.display_obs['StrikesMonth']  = observation.Format(strikeMonth,  'StrikeCount')
            self.display_obs['StrikesYear']   = observation.Format(strikeYear,   'StrikeCount')

        # Format derived variables from obs_sky and obs_st observations
        if device_type in ('obs_sky', 'obs_st', 'obs_all'):
            self.display_obs['Radiation']     = observation.Format(radiation,     'Radiation')
            self.display_obs['UVIndex']       = observation.Format(uvIndex,       'UV')
            self.display_obs['peakSun']       = observation.Format(peakSun,       'peakSun')
            self.display_obs['RainRate']      = observation.Format(rainRate,      'Precip')
            self.display_obs['TodayRain']     = observation.Format(todayRain,     'Precip')
            self.display_obs['YesterdayRain'] = observation.Format(yesterdayRain, 'Precip')
            self.display_obs['MonthRain']     = observation.Format(monthRain,     'Precip')
            self.display_obs['YearRain']      = observation.Format(yearRain,      'Precip')
            self.display_obs['WindSpd']       = observation.Format(windSpd,       'Wind')
            self.display_obs['WindGust']      = observation.Format(windGust,      'Wind')
            self.display_obs['AvgWind']       = observation.Format(windAvg,       'Wind')
            self.display_obs['MaxGust']       = observation.Format(windMax,       'Wind')
            self.display_obs['WindDir']       = observation.Format(windDir,       'Direction')

        # Format derived variables from obs_in_air observations
        if device_type in ('obs_in_air', 'obs_all'):
            self.display_obs['inTemp']        = observation.Format(inTemp,    'Temp')
            self.display_obs['inTempMax']     = observation.Format(inTempMax, ['Temp', 'Time'], config)
            self.display_obs['inTempMin']     = observation.Format(inTempMin, ['Temp', 'Time'], config)

        # Format derived variables from rapid_wind observations
        if device_type in ('rapid_wind', 'obs_all'):
            self.display_obs['rapidSpd']      = observation.Format(rapidWindSpd, 'Wind')
            self.display_obs['rapidDir']      = observation.Format(rapidWindDir, 'Direction')

        # Format derived variables from evt_strike observations
        if device_type in ('evt_strike', 'obs_all'):
            self.display_obs['StrikeDist']    = observation.Format(strikeDist,   'StrikeDistance')
            self.display_obs['StrikeDeltaT']  = observation.Format(strikeDeltaT, 'TimeDelta')

        # Update display with new variables
        self.update_display(device_type)
    def parse_forecast(self):
        """ Parse the latest daily and hourly weather forecast from the
        WeatherFlow BetterForecast API and format for display based on user
        specified units
        """

        # Extract Forecast dictionary
        Forecast = self.met_data['Response']

        # Get current time in station time zone
        Tz = pytz.timezone(self.app.config['Station']['Timezone'])
        Now = datetime.now(pytz.utc).astimezone(Tz)

        # Set time format based on user configuration
        if self.app.config['Display']['TimeFormat'] == '12 hr':
            if self.app.config['System']['Hardware'] == 'Other':
                TimeFormat = '%#I %p'
            else:
                TimeFormat = '%-I %p'
        else:
            TimeFormat = '%H:%M'

        # Extract all forecast data from WeatherFlow JSON object
        try:
            # Extract all hourly and daily forecasts
            hourlyForecasts = (Forecast['forecast']['hourly'])
            dailyForecasts = (Forecast['forecast']['daily'])

            # Extract 'valid from' time of all available hourly forecasts and
            # retrieve forecast for the current hour
            Hours = list(forecast['time'] for forecast in hourlyForecasts)
            hoursInd = bisect.bisect(Hours, int(UNIX.time()))
            hourlyCurrent = hourlyForecasts[hoursInd]
            hourlyLocalDay = hourlyCurrent['local_day']

            # Extract 'Valid' until time of forecast for current hour
            Valid = Hours[bisect.bisect(Hours, int(UNIX.time()))]
            Valid = datetime.fromtimestamp(Valid, pytz.utc).astimezone(Tz)

            # Extract 'day_start_local' time of all available daily forecasts and
            # retrieve forecast for the current day
            dailyDayNum = list(forecast['day_num']
                               for forecast in dailyForecasts)
            dailyCurrent = dailyForecasts[dailyDayNum.index(hourlyLocalDay)]

            # Extract weather variables from current hourly forecast
            Temp = [hourlyCurrent['air_temperature'], 'c']
            WindSpd = [hourlyCurrent['wind_avg'], 'mps']
            WindGust = [hourlyCurrent['wind_gust'], 'mps']
            WindDir = [hourlyCurrent['wind_direction'], 'degrees']
            Icon = hourlyCurrent['icon']

            # Extract Precipitation Type, Percent, and Amount from current hourly
            # forecast
            if 'precip_type' in hourlyCurrent:
                if hourlyCurrent['precip_type'] in ['rain', 'snow']:
                    PrecipType = hourlyCurrent['precip_type'].title() + 'fall'
                else:
                    PrecipType = hourlyCurrent['precip_type'].title()
            else:
                PrecipType = 'Rainfall'
            if 'precip_probability' in hourlyCurrent:
                PrecipPercnt = [hourlyCurrent['precip_probability'], '%']
            else:
                PrecipPercnt = [0, '%']
            if 'precip' in hourlyCurrent:
                PrecipAmount = [hourlyCurrent['precip'], 'mm']
            else:
                PrecipAmount = [0, 'mm']

            # Extract weather variables from current daily forecast
            highTemp = [dailyCurrent['air_temp_high'], 'c']
            lowTemp = [dailyCurrent['air_temp_low'], 'c']
            precipDay = [dailyCurrent['precip_probability'], '%']

            # Extract list of expected conditions and find time when expected conditions
            # will change
            conditionList = list(forecast['conditions']
                                 for forecast in hourlyForecasts[hoursInd:])
            try:
                Ind = next(i for i, C in enumerate(conditionList)
                           if C != hourlyCurrent['conditions'])
            except StopIteration:
                Ind = len(conditionList) - 1
            Time = datetime.fromtimestamp(Hours[Ind], pytz.utc).astimezone(Tz)
            if Time.date() == Now.date():
                Conditions = hourlyCurrent['conditions'].capitalize(
                ) + ' until ' + datetime.strftime(Time, TimeFormat) + ' today'
            elif Time.date() == Now.date() + timedelta(days=1):
                Conditions = hourlyCurrent['conditions'].capitalize(
                ) + ' until ' + datetime.strftime(Time,
                                                  TimeFormat) + ' tomorrow'
            else:
                Conditions = hourlyCurrent['conditions'].capitalize(
                ) + ' until ' + datetime.strftime(
                    Time, TimeFormat) + ' on ' + Time.strftime('%A')

            # Calculate derived variables from forecast
            WindDir = derive.cardinalWindDir(WindDir, WindSpd)

            # Convert forecast units as required
            Temp = observation.Units(Temp, self.app.config['Units']['Temp'])
            highTemp = observation.Units(highTemp,
                                         self.app.config['Units']['Temp'])
            lowTemp = observation.Units(lowTemp,
                                        self.app.config['Units']['Temp'])
            WindSpd = observation.Units(WindSpd,
                                        self.app.config['Units']['Wind'])
            WindGust = observation.Units(WindGust,
                                         self.app.config['Units']['Wind'])
            WindDir = observation.Units(WindDir,
                                        self.app.config['Units']['Direction'])
            PrecipAmount = observation.Units(
                PrecipAmount, self.app.config['Units']['Precip'])

            # Define and format labels
            self.met_data['Valid'] = datetime.strftime(Valid, TimeFormat)
            self.met_data['Temp'] = observation.Format(Temp, 'forecastTemp')
            self.met_data['highTemp'] = observation.Format(
                highTemp, 'forecastTemp')
            self.met_data['lowTemp'] = observation.Format(
                lowTemp, 'forecastTemp')
            self.met_data['WindSpd'] = observation.Format(
                WindSpd, 'forecastWind')
            self.met_data['WindGust'] = observation.Format(
                WindGust, 'forecastWind')
            self.met_data['WindDir'] = observation.Format(WindDir, 'Direction')
            self.met_data['PrecipPercnt'] = observation.Format(
                PrecipPercnt, 'Humidity')
            self.met_data['PrecipDay'] = observation.Format(
                precipDay, 'Humidity')
            self.met_data['PrecipAmount'] = observation.Format(
                PrecipAmount, 'Precip')
            self.met_data['PrecipType'] = PrecipType
            self.met_data['Conditions'] = Conditions
            self.met_data['Icon'] = Icon
            self.met_data['Status'] = ''

            # Check expected conditions icon is recognised
            if Icon in [
                    'clear-day', 'clear-night', 'rainy', 'possibly-rainy-day',
                    'possibly-rainy-night', 'snow', 'possibly-snow-day',
                    'possibly-snow-night', 'sleet', 'possibly-sleet-day',
                    'possibly-sleet-night', 'thunderstorm',
                    'possibly-thunderstorm-day', 'possibly-thunderstorm-night',
                    'windy', 'foggy', 'cloudy', 'partly-cloudy-day',
                    'partly-cloudy-night'
            ]:
                self.met_data['Icon'] = Icon
            else:
                self.met_data['Icon'] = '-'

            # Update display
            self.update_display()

            # Update forecast icon
            if hasattr(self.app, 'ForecastPanel'):
                for panel in getattr(self.app, 'ForecastPanel'):
                    panel.setForecastIcon()

            # Schedule new forecast
            Clock.schedule_once(self.schedule_forecast)

        # Unable to extract forecast data from JSON object. Set forecast
        # variables to blank and indicate to user that forecast is unavailable
        except (IndexError, KeyError, ValueError):
            Clock.schedule_once(self.fail_forecast)