On Monday, January 23, 2017 at 2:55:27 AM UTC-5, Janez Kranjski wrote:
>
> Tnx.
> Now another problem :-)
>
> Check attached log file (realtime.xml problem).
>>
>>
>>
please try the attached 0.19rc2

i have not tested with hardware that emits partial packets, so there might 
be more key errors like that.

one of these days we need to add a 'partial packets' mode to the 
simulator...

m

 

-- 
You received this message because you are subscribed to the Google Groups 
"weewx-user" group.
To unsubscribe from this group and stop receiving emails from it, send an email 
to weewx-user+unsubscr...@googlegroups.com.
For more options, visit https://groups.google.com/d/optout.
# $Id: crt.py 1662 2017-01-22 15:01:26Z mwall $
# Copyright 2013-2017 Matthew Wall
# Distributed under terms of the GPLv3
# thanks to gary roderick for significant contributions to this code

"""Emit loop data to file in Cumulus realtime format.

   http://wiki.sandaysoft.com/a/Realtime.txt
   http://wiki.sandaysoft.com/a/Webtags

Nominally this will output to a file called realtime.txt every at LOOP packet.
Optionally it will output to realtime.xml (in XML format).  Optionally it will
output to a file called sunbird (for location and sunrise/sunset information).

To install, put this file in bin/user/crt.py, then add this to your weewx.conf:

[CumulusRealTime]
    filename = /path/to/realtime.txt

[Engine]
    [[Services]]
        archive_services = ..., user.crt.CumulusRealTime

If no unit_system is specified, the units will be those of the database.

Other parameters may be specified to control units and output:

[CumulusRealTime]
    realtime_txt = /path/to/realtime.txt
    realtime_xml = /path/to/realtime.xml
    sunbird = /path/to/sunbird.txt
    date_separator = /
    none = NULL
    unit_system = (US | METRIC | METRICWX)
    wind_units = (meter_per_second | mile_per_hour | km_per_hour | knot)
    temperature_units = (degree_C | degree_F)
    pressure_units = (hPa | mbar | inHg)
    rain_units = (mm | inch)
    cloudbase_units = (foot | meter)

Note that most of the code in this file is to calculate/lookup data that
are not directly provided in a LOOP packet.

The cumulus 'specification' for realtime.txt is ambiguous in places:

  - pressure trend interval is not specified, we use 3 hours for pressure
  - temperature trend interval is not specified, we use 3 hours for temperature
  
The following assumptions are based on the Cumulus realtime and webtag docs:
  - wind avg speed/wind avg dir interval is not specified. The equivalent 
    Cumulus Webtags for wind avg speed and avg wind direction default to 
    10 minute averages so we use 10 minutes as well.
  - wind direction bearings in degrees are set from > 0 to 360 with 0
    indicating calm.  refer to the #avgbearing Webtag. this is different to
    the standard used in weewx.
  - possible Cumulus windrun units are km, miles, km and nm corresponding to 
    wind speeds in m/s, mph, km/h, kts.  weewx does not know what a nautical
    mile (nm) is.
  
The following assumptions are based on examination of realtime.txt instances
from a number of live Cumulus sites:
  - time zone is not specified, local time is used throughout
  - how to handle None/NULL is not specified.  Examination of realtime.txt 
    from a number of live Cumulus sites indicates when there is no wind 
    (average or gust) wind speeds are set to zero. For other fields that
    may be None/Null we return the 'none' parameter setting (default = NULL) 
    from the weewx.conf [CumulusRealTime] section.
  - ordinal wind directions are set to --- when there is no wind.
"""

# FIXME: implement the additional xml parameters for records and yesterday

# FIXME: consider in-memory caching so that database queries are not
#        necessary after the first invocation

import math
import time
import syslog
from distutils.version import StrictVersion

import weewx
import weewx.almanac
import weewx.manager
import weewx.wxformulas
import weeutil.weeutil
import weedb
from weewx.engine import StdService

VERSION = "0.19rc2"

REQUIRED_WEEWX = "3.5.0"
if StrictVersion(weewx.__version__) < StrictVersion(REQUIRED_WEEWX):
    raise weewx.UnsupportedFeature("weewx %s or greater is required, found %s"
                                   % (REQUIRED_WEEWX, weewx.__version__))

def logmsg(level, msg):
    syslog.syslog(level, 'crt: %s' % msg)

def logdbg(msg):
    logmsg(syslog.LOG_DEBUG, msg)

def loginf(msg):
    logmsg(syslog.LOG_INFO, msg)

def logerr(msg):
    logmsg(syslog.LOG_ERR, msg)


# FIXME: get these from the forecast extension
# FIXME: ensure the forecast extension makes these available via i18n
FORECAST_TEXT = {
    'A': 'Settled fine',
    'B': 'Fine weather',
    'C': 'Becoming fine',
    'D': 'Fine, becoming less settled',
    'E': 'Fine, possible showers',
    'F': 'Fairly fine, improving',
    'G': 'Fairly fine, possible showers early',
    'H': 'Fairly fine, showery later',
    'I': 'Showery early, improving',
    'J': 'Changeable, mending',
    'K': 'Fairly fine, showers likely',
    'L': 'Rather unsettled clearing later',
    'M': 'Unsettled, probably improving',
    'N': 'Showery, bright intervals',
    'O': 'Showery, becoming less settled',
    'P': 'Changeable, some rain',
    'Q': 'Unsettled, short fine intervals',
    'R': 'Unsettled, rain later',
    'S': 'Unsettled, some rain',
    'T': 'Mostly very unsettled',
    'U': 'Occasional rain, worsening',
    'V': 'Rain at times, very unsettled',
    'W': 'Rain at frequent intervals',
    'X': 'Rain, very unsettled',
    'Y': 'Stormy, may improve',
    'Z': 'Stormy, much rain'
}

COMPASS_POINTS = ['N', 'NNE', 'NE', 'ENE', 'E', 'ESE', 'SE', 'SSE',
                  'S', 'SSW', 'SW', 'WSW', 'W', 'WNW', 'NW', 'NNW', 'N']

# map weewx unit names to cumulus unit names
UNITS_PRES = {'inHg': 'in', 'mbar': 'mb', 'hPa': 'hPa'}
UNITS_TEMP = {'degree_C': 'C', 'degree_F': 'F'}
UNITS_RAIN = {'inch': 'in', 'mm': 'mm'}
UNITS_WIND = {'mile_per_hour': 'mph',
              'meter_per_second': 'm/s',
              'km_per_hour': 'km/h',
              'knot': 'kts'}
UNITS_CLOUDBASE = {'foot': 'ft', 'meter': 'm'}
# categorize the weewx-to-cumulus unit mappings
UNITS = {'pressure': UNITS_PRES,
         'temperature': UNITS_TEMP,
         'rain': UNITS_RAIN,
         'wind': UNITS_WIND,
         'cloudbase': UNITS_CLOUDBASE}
SPEED_TO_RUN = {'mile_per_hour': 'mile',
                'meter_per_second': 'km',
                'km_per_hour': 'km',
                'knot': 'mile'}

def _convert(from_v, from_units, to_units, group):
    """Given a value, units and unit group convert to different units."""
    vt = (from_v, from_units, group)
    return weewx.units.convert(vt, to_units)[0]

def _convert_us(from_v, from_us, to_units, obs, group):
    """Given obs type, value, unit system and unit group convert to units."""
    from_units = weewx.units.getStandardUnitType(from_us, obs)[0]
    return _convert(from_v, from_units, to_units, group)

def clamp_degrees(x):
    """Convert a bearing to a Cumulus bearing.
    
    weewx uses 0.0 inclusive to 360.0 (exclusive) bearings whilst Cumulus 
    uses 0.0 (exclusive) to 360.0 (inclusive) bearings. When the wind is 
    calm Cumulus emits a wind direction of 0 degrees.
    """
    if x is not None:
        return x if x != 0.0 else 360.0
    return None

def degree_to_compass(x):
    if x is None:
        return '---'
    idx = int((x + 11.25) / 22.5)
    return COMPASS_POINTS[idx]

def get_db_units(dbm):
    val = dbm.getSql("SELECT usUnits FROM %s LIMIT 1" % dbm.table_name)
    return val[0] if val is not None else None

def calc_avg_windspeed(dbm, ts, interval=600):
    sts = ts - interval
    val = dbm.getSql("SELECT AVG(windSpeed) FROM %s "
                     "WHERE dateTime>? AND dateTime<=?" % dbm.table_name,
                     (sts, ts))
    return val[0] if val is not None else None

def calc_avg_winddir(dbm, ts, interval=600):
    sts = ts - interval
    val = dbm.getSql("SELECT AVG(windDir) FROM %s "
                     "WHERE dateTime>? AND dateTime<=?" % dbm.table_name,
                     (sts, ts))
    return clamp_degrees(val[0]) if val is not None else None

def calc_max_gust_10min(dbm, ts):
    sts = ts - 600
    val = dbm.getSql("SELECT MAX(windGust) FROM %s "
                     "WHERE dateTime>? AND dateTime<=?" % dbm.table_name,
                     (sts, ts))
    return val[0] if val is not None else None

def calc_avg_winddir_10min(dbm, ts):
    sts = ts - 600
    val = dbm.getSql("SELECT AVG(windDir) FROM %s "
                     "WHERE dateTime>? AND dateTime<=?" % dbm.table_name,
                     (sts, ts))
    return clamp_degrees(val[0]) if val is not None else None

def calc_windrun(dbm, ts, db_us):
    """Calculate windrun since midnight.  returns a value in the distance units
    of the database (thus the temporal normalization).
    """
    run = 0
    sod_ts = weeutil.weeutil.startOfDay(ts)
    for row in dbm.genSql("SELECT `interval`,windSpeed FROM %s "
                          "WHERE dateTime>? AND dateTime<=?" % dbm.table_name,
                          (sod_ts, ts)):
        if row[1] is not None:
            inc = row[1] * row[0]
            if db_us == weewx.METRICWX:
                inc *= 60.0
            else:
                inc /= 60.0
            run += inc
    return run

def get_trend(label, dbm, ts, n=3):
    """Calculate the trend over past n hours, default to 3 hour window."""
    lastts = ts - n * 3600
    old_val = dbm.getSql("SELECT %s FROM %s "
                         "WHERE dateTime>? AND dateTime<=?" %
                         (label, dbm.table_name), (lastts, ts))
    if old_val is None or old_val[0] is None:
        return None
    return old_val[0]

def calc_trend(newval, oldval):
    if newval is None or oldval is None:
        return None
    return newval - oldval

def calc_rain_hour(dbm, ts):
    sts = ts - 3600
    val = dbm.getSql("SELECT SUM(rain) FROM %s "
                     "WHERE dateTime>? AND dateTime<=?" % dbm.table_name,
                     (sts, ts))
    return val[0] if val is not None else None

def calc_rain_month(dbm, ts):
    span = weeutil.weeutil.archiveMonthSpan(ts)
    val = dbm.getSql("SELECT SUM(rain) FROM %s "
                     "WHERE dateTime>? AND dateTime<=?" % dbm.table_name,
                     (span.start, ts))
    return val[0] if val is not None else None

def calc_rain_year(dbm, ts):
    span = weeutil.weeutil.archiveYearSpan(ts)
    val = dbm.getSql("SELECT SUM(rain) FROM %s "
                     "WHERE dateTime>? AND dateTime<=?" % dbm.table_name,
                     (span.start, ts))
    return val[0] if val is not None else None

def calc_rain_yesterday(dbm, ts):
    ts = weeutil.weeutil.startOfDay(ts)
    sts = ts - 3600 * 24
    val = dbm.getSql("SELECT SUM(rain) FROM %s "
                     "WHERE dateTime>? AND dateTime<=?" % dbm.table_name,
                     (sts, ts))
    return val[0] if val is not None else None

def calc_rain_day(dbm, ts):
    sts = weeutil.weeutil.startOfDay(ts)
    val = dbm.getSql("SELECT SUM(rain) FROM %s "
                     "WHERE dateTime>? AND dateTime<=?" % dbm.table_name, 
                     (sts, ts))
    return val[0] if val is not None else None

def calc_ET_today(dbm, ts):
    sts = weeutil.weeutil.startOfDay(ts)
    val = dbm.getSql("SELECT SUM(ET) FROM %s "
                     "WHERE dateTime>? AND dateTime<=?" % dbm.table_name,
                     (sts, ts))
    return val[0] if val is not None else None

def calc_minmax(label, dbm, ts, minmax='MAX'):
    sts = weeutil.weeutil.startOfDay(ts)
    val = dbm.getSql("SELECT %s(%s) FROM %s "
                     "WHERE dateTime>? AND dateTime<=?" %
                     (minmax, label, dbm.table_name), (sts, ts))
    if val is None:
        return None, None
    t = dbm.getSql("SELECT dateTime FROM %s "
                   "WHERE dateTime>? AND dateTime<=? AND %s=?" %
                   (dbm.table_name, label), (sts, ts, val[0]))
    if t is None:
        return None, None
    tstr = time.strftime("%H:%M", time.localtime(t[0]))
    return val[0], tstr

def calc_is_daylight(alm):
    sunrise = alm.sunrise.raw
    sunset = alm.sunset.raw
    if sunrise < alm.time_ts < sunset:
        return 1
    return 0

def calc_daylight_hours(alm):
    sunrise = alm.sunrise.raw
    sunset = alm.sunset.raw
    if alm.time_ts <= sunrise:
        return 0
    elif alm.time_ts < sunset:
        return (alm.time_ts - sunrise) / 3600.0
    return (sunset - sunrise) / 3600.0

def calc_is_sunny(rad, max_rad, threshold):
    if not rad or not max_rad:
        return 0
    if rad <= threshold * max_rad:
        return 0
    return 1

# indication of sensor contact depens on the weather station.  if the station
# has more than one indicator, then indicate failure if contact is lost with
# any one of them.
#
# Vantage
#   packet['rxCheckPercent'] == 0
#
# FineOffset
#   packet['status'] & 0x40
#
# TE923
#   packet['sensorX_state'] == STATE_MISSING_LINK
#   packet['wind_state'] == STATE_MISSING_LINK
#   packet['rain_state'] == STATE_MISSING_LINK
#   packet['uv_state'] == STATE_MISSING_LINK
#
# WMR100
# WMR200
# WMR9x8
#
# WS28xx
#   packet['rxCheckPercent'] == 0
#
# WS23xx
#   packet['cn'] == 'lost'
#
def lost_sensor_contact(packet):
    if 'rxCheckPercent' in packet and packet['rxCheckPercent'] == 0:
        return 1
    if 'cn' in packet and packet['cn'] == 'lost':
        return 1
    if (('windspeed_state' in packet and packet['windspeed_state'] == 'no_link') or
        ('rain_state' in packet and packet['rain_state'] == 'no_link') or
        ('uv_state' in packet and packet['uv_state'] == 'no_link') or
        ('h_1_state' in packet and packet['h_1_state'] == 'no_link') or
        ('h_2_state' in packet and packet['h_2_state'] == 'no_link') or
        ('h_3_state' in packet and packet['h_3_state'] == 'no_link') or
        ('h_4_state' in packet and packet['h_4_state'] == 'no_link') or
        ('h_5_state' in packet and packet['h_5_state'] == 'no_link')):
        return 1
    if 'status' in packet and packet['status'] & 0x40 != 0:
        return 1
    return 0

class ZambrettiForecast():
    DEFAULT_FORECAST_BINDING = 'forecast_binding'
    DEFAULT_BINDING_DICT = {
        'database': 'forecast_sqlite',
        'manager': 'weewx.manager.Manager',
        'table_name': 'archive',
        'schema': 'user.forecast.schema'}

    def __init__(self, config_dict):
        self.forecasting_installed = False
        self.db_max_tries = 3
        self.db_retry_wait = 3
        try:
            self.dbm_dict = weewx.manager.get_manager_dict(
                config_dict['DataBindings'],
                config_dict['Databases'],
                ZambrettiForecast.DEFAULT_FORECAST_BINDING,
                default_binding_dict=ZambrettiForecast.DEFAULT_BINDING_DICT)
            weewx.manager.open_manager(self.dbm_dict)
            self.forecasting_installed = True
        except (weedb.DatabaseError, weewx.UnsupportedFeature,
                weewx.UnknownBinding, KeyError):
            pass

    def is_installed(self):
        return self.forecasting_installed

    def get_zambretti_code(self):
        if not self.forecasting_installed:
            return 0

        # FIXME: add api to forecast instead of doing all the work here
        with weewx.manager.open_manager(self.dbm_dict) as dbm:
            sql = "select dateTime,zcode from %s where method = 'Zambretti' order by dateTime desc limit 1" % dbm.table_name
#            sql = "select zcode from %s where method = 'Zambretti' and dateTime = (select max(dateTime) from %s where method = 'Zambretti')" % (dbm.table_name, dbm.table_name)
            for count in range(self.db_max_tries):
                try:
                    record = dbm.getSql(sql)
                    if record is not None:
                        return record[1]
                except Exception, e: # FIXME: make this more specific
                    logerr('get zambretti failed (attempt %d of %d): %s' %
                           ((count + 1), self.db_max_tries, e))
                    logdbg('waiting %d seconds before retry' %
                           self.db_retry_wait)
                    time.sleep(self.db_retry_wait)
        return 0

    # given a zambretti letter code A-Z, convert to digit 1-26
    @staticmethod
    def alpha_to_number(x):
        try:
            return ord(x) - 64
        except TypeError:
            return 0

class CumulusRealTime(StdService):

    def __init__(self, engine, config_dict):
        super(CumulusRealTime, self).__init__(engine, config_dict)
        loginf("service version is %s" % VERSION)
        self.altitude_m = weewx.units.convert(
            engine.stn_info.altitude_vt, "meter")[0]
        self.latitude = engine.stn_info.latitude_f
        self.longitude = engine.stn_info.longitude_f
        self.location = engine.stn_info.location

        d = config_dict.get('CumulusRealTime', {})
        self.realtime_txt = d.get('filename')
        if not self.realtime_txt:
            self.realtime_txt = d.get('realtime_txt', '/var/tmp/realtime.txt')
        if self.realtime_txt:
            loginf("realtime txt output goes to %s" % self.realtime_txt)
        self.realtime_xml = d.get('realtime_xml')
        if self.realtime_xml:
            loginf("realtime xml output goes to %s" % self.realtime_xml)
        self.sunbird_txt = d.get('sunbird_txt')
        if self.sunbird_txt:
            loginf("sunbird output goes to %s" % self.sunbird_txt)

        if (not self.realtime_txt and
            not self.realtime_xml and
            not self.sunbird_txt):
            loginf("aborted: no output files specified")
            return

        self.datesep = d.get('date_separator', '/')
        self.sunny_threshold = float(d.get('sunny_threshold', 0.75))
        self.nonesub = d.get('none', 'NULL')
        loginf("'None' values will be displayed as %s" % self.nonesub)

        # source unit system is the database unit system
        self.db_us = None
        # initialise packet unit system
        self.pkt_us = None

        # get the unit system for display
        us = None
        us_label = d.get('unit_system', None)
        if us_label is not None:
            if us_label in weewx.units.unit_constants:
                loginf("units will be displayed as %s" % us_label)
                us = weewx.units.unit_constants[us_label]
            else:
                logerr("unknown unit_system %s" % us_label)
        self.unit_system = us

        # get any overrides to the display units
        self.units = dict()
        for x in UNITS:
            ukey = '%s_units' % x
            if ukey in d:
                if d[ukey] in UNITS[x]:
                    loginf("%s units will be displayed as %s" % (x, d[ukey]))
                    self.units[x] = d[ukey]
                else:
                    logerr("unknown unit '%s' for %s" % (d[ukey], ukey))

        # configure forecasting
        self.forecast = ZambrettiForecast(config_dict)
        loginf("zambretti forecast: %s" % self.forecast.is_installed())

        # configure the binding
        binding = d.get('binding', 'loop').lower()
        loginf("binding is %s" % binding)
        if binding == 'loop':
            self.bind(weewx.NEW_LOOP_PACKET, self.handle_new_loop)
        else:
            self.bind(weewx.NEW_ARCHIVE_RECORD, self.handle_new_archive)

    def handle_new_loop(self, event):
        self.handle_data(event.packet)

    def handle_new_archive(self, event):
        self.handle_data(event.record)

    def handle_data(self, event_data):
        try:
            dbm = self.engine.db_binder.get_manager('wx_binding')
            data = self.calculate(event_data, dbm)
            if self.realtime_txt:
                self.write_data(self.realtime_txt,
                                self.create_realtime_string(data))
            if self.realtime_xml:
                self.write_data(self.realtime_xml,
                                self.create_realtime_xml(data))
            if self.sunbird_txt:
                self.write_data(self.sunbird_txt,
                                self.create_sunbird_string(data))
        except Exception, e: # FIXME: make this catch more specific
            logdbg("crt: Exception while handling data: %s" % e)
            weeutil.weeutil.log_traceback('crt: **** ')
            raise

    def write_data(self, filename, data):
        with open(filename, 'w') as f:
            f.write(data)
            f.write("\n")

    # convert from database unit system to specified units
    def _cvt(self, from_v, to_units, obs, group):
        if self.db_us is None:
            return None
        return _convert_us(from_v, self.db_us, to_units, obs, group)

    # convert from database unit system to specified unit system
    def _cvt_us(self, from_v, to_us, obs, group):
        to_units = weewx.units.getStandardUnitType(to_us, obs)[0]
        return self._cvt(from_v, to_units, obs, group)

    # convert observation in group pressure
    def _cvt_p(self, obs, packet, unit):
        return _convert_us(packet.get(obs), self.pkt_us, unit, 
                           obs, 'group_pressure')

    # convert observation in group temperature
    def _cvt_t(self, obs, packet, unit):
        return _convert_us(packet.get(obs), self.pkt_us, unit, 
                           obs, 'group_temperature')

    # convert observation in group rainrate
    def _cvt_rr(self, obs, packet, unit):
        return _convert_us(packet.get(obs), self.pkt_us, unit, 
                           obs, 'group_rainrate')

    # convert observation in group speed
    def _cvt_w(self, obs, packet, unit):
        return _convert_us(packet.get(obs), self.pkt_us, unit, 
                           obs, 'group_speed')

    # convert observation group altitude
    def _cvt_a(self, obs, packet, unit):
        return _convert_us(packet.get(obs), self.pkt_us, unit, 
                           obs, 'group_altitude')

    # calculate the data elements that that weewx does not provide directly.
    def calculate(self, packet, dbm):
        ts = packet.get('dateTime')

        # the 'from' unit system is whatever the database is using.  get it
        # from the database once then cache it for use in conversions.  if
        # there is not yet a database, return an empty dict and we'll get it
        # the next time around.
        if self.db_us is None:
            try:
                self.db_us = get_db_units(dbm)
            except weedb.DatabaseError, e:
                logerr("cannot determine database units: %s" % e)
                return dict()

        # the 'to' unit system defaults to the unit system of the packet
        # (typically the same unit system as the database, but it might not
        # be), but if a different unit system is specified, use that...
        self.pkt_us = packet.get('usUnits')
        if self.unit_system is not None and self.unit_system != self.pkt_us:
            packet = weewx.units.to_std_system(packet, self.unit_system)
            self.pkt_us = self.unit_system

        # ... then get any other specialized units.
        p_u = self.units.get(
            'pressure',
            weewx.units.getStandardUnitType(self.pkt_us, 'barometer')[0])
        t_u = self.units.get(
            'temperature',
            weewx.units.getStandardUnitType(self.pkt_us, 'outTemp')[0])
        r_u = self.units.get(
            'rain',
            weewx.units.getStandardUnitType(self.pkt_us, 'rain')[0])
        w_u = self.units.get(
            'wind',
            weewx.units.getStandardUnitType(self.pkt_us, 'windSpeed')[0])
        cb_u = self.units.get(
            'cloudbase',
            weewx.units.getStandardUnitType(self.pkt_us, 'altitude')[0])

        # cumulus does not use cm for rain, but weewx might
        if r_u == 'cm':
            r_u = 'mm'
        # infer rain rate units from rain units
        rr_u = '%s_per_hour' % r_u
        # infer windrun units from wind units
        wr_u = SPEED_TO_RUN.get(w_u, 'mile')

        # now create the dictionary of data
        data = dict(packet)
        data['units_wind'] = UNITS_WIND.get(w_u, w_u)
        data['units_temperature'] = UNITS_TEMP.get(t_u, t_u)
        data['units_pressure'] = UNITS_PRES.get(p_u, p_u)
        data['units_rain'] = UNITS_RAIN.get(r_u, r_u)
        data['units_cloudbase'] = UNITS_CLOUDBASE.get(cb_u, cb_u)

        data['barometer'] = self._cvt_p('barometer', packet, p_u)
        data['inTemp'] = self._cvt_t('inTemp', packet, t_u)
        data['outTemp'] = self._cvt_t('outTemp', packet, t_u)
        data['dewpoint'] = self._cvt_t('dewpoint', packet, t_u)
        data['heatindex'] = self._cvt_t('heatindex', packet, t_u)
        data['humidex'] = self._cvt_t('humidex', packet, t_u)
        data['windchill'] = self._cvt_t('windchill', packet, t_u)
        data['appTemp'] = self._cvt_t('appTemp', packet, t_u)
        data['windSpeed'] = self._cvt_w('windSpeed', packet, w_u)
        data['rainRate'] = self._cvt_rr('rainRate', packet, rr_u)
        data['cumulus_windDir'] = clamp_degrees(packet.get('windDir'))
        data['windDir_compass'] = degree_to_compass(packet.get('windDir'))
        data['windSpeed_avg'] = self._cvt(
            calc_avg_windspeed(dbm, ts), w_u, 'windSpeed', 'group_speed')
        v = _convert_us(packet.get('windSpeed'), self.pkt_us, 'knot',
                        'windSpeed', 'group_speed')
        data['windSpeed_beaufort'] = weewx.wxformulas.beaufort(v)
        wr = calc_windrun(dbm, ts, self.db_us)
        data['windrun'] = self._cvt(wr, wr_u, 'windrun', 'group_distance')
        # weewx does not know of nautical miles so if wind speed units are knot
        # then we have a windrun in miles and we need to manually convert it to
        # nautical miles
        if w_u == 'knot' and data['windrun'] is not None:
            data['windrun'] /= 1.15077945
        data['cloudbase'] = self._cvt_a('cloudbase', packet, cb_u)
        p1 = packet.get('barometer')
        p2 = get_trend('barometer', dbm, ts)
        p2 = self._cvt_us(p2, self.pkt_us, 'barometer', 'group_pressure')
        data['pressure_trend'] = calc_trend(p1, p2)
        t1 = packet.get('outTemp')
        t2 = get_trend('outTemp', dbm, ts)
        t2 = self._cvt_us(t2, self.pkt_us, 'outTemp', 'group_temperature')
        data['temperature_trend'] = calc_trend(t1, t2)

        data['rain_month'] = self._cvt(
            calc_rain_month(dbm, ts), r_u, 'rain', 'group_rain')
        data['rain_year'] = self._cvt(
            calc_rain_year(dbm, ts), r_u, 'rain', 'group_rain')
        data['rain_yesterday'] = self._cvt(
            calc_rain_yesterday(dbm, ts), r_u, 'rain', 'group_rain')
        data['dayRain'] = self._cvt(
            calc_rain_day(dbm, ts), r_u, 'rain', 'group_rain')

        v, t = calc_minmax('outTemp', dbm, ts, 'MAX')
        data['outTemp_max'] = self._cvt(
            v, t_u, 'outTemp', 'group_temperature')
        data['outTemp_max_time'] = t
        v, t = calc_minmax('outTemp', dbm, ts, 'MIN')
        data['outTemp_min'] = self._cvt(
            v, t_u, 'outTemp', 'group_temperature')
        data['outTemp_min_time'] = t
        v, t = calc_minmax('barometer', dbm, ts, 'MAX')
        data['pressure_max'] = self._cvt(
            v, p_u, 'barometer', 'group_pressure')
        data['pressure_max_time'] = t
        v, t = calc_minmax('barometer', dbm, ts, 'MIN')
        data['pressure_min'] = self._cvt(
            v, p_u, 'barometer', 'group_pressure')
        data['pressure_min_time'] = t
        v, t = calc_minmax('windSpeed', dbm, ts, 'MAX')
        data['windSpeed_max'] = self._cvt(
            v, w_u, 'windSpeed', 'group_speed')
        data['windSpeed_max_time'] = t
        v, t = calc_minmax('windGust', dbm, ts, 'MAX')
        data['windGust_max'] = self._cvt(
            v, w_u, 'windGust', 'group_speed')
        data['windGust_max_time'] = t

        data['10min_high_gust'] = self._cvt(
            calc_max_gust_10min(dbm, ts), w_u, 'windSpeed', 'group_speed')
        v = calc_avg_winddir_10min(dbm, ts)
        data['10min_avg_wind_bearing'] = v
        data['avg_wind_dir'] = degree_to_compass(v)

        data['rain_hour'] = self._cvt(
            calc_rain_hour(dbm, ts), r_u, 'rain', 'group_rain')

        data['ET_today'] = calc_ET_today(dbm, ts)
        data['lost_sensors_contact'] = lost_sensor_contact(packet)

        t_C = _convert_us(packet.get('outTemp'), self.pkt_us, 'degree_C',
                          'outTemp', 'group_temperature')
        p_mbar = _convert_us(packet.get('barometer'), self.pkt_us, 'mbar',
                             'barometer', 'group_pressure')
        alm = weewx.almanac.Almanac(ts, self.latitude, self.longitude,
                                    self.altitude_m, t_C, p_mbar)
        data['is_daylight'] = calc_is_daylight(alm)
        data['sunshine_hours'] = calc_daylight_hours(alm)
        data['is_sunny'] = calc_is_sunny(data.get('radiation'),
                                         data.get('max_rad'),
                                         self.sunny_threshold)
        code = self.forecast.get_zambretti_code()
        data['zambretti_code'] = ZambrettiForecast.alpha_to_number(code)

        # these fields are needed by xml
        data['zambretti_text'] = FORECAST_TEXT.get(code)

        # these fields are needed by sunbird
        data['sunrise'] = alm.sunrise.raw
        data['sunset'] = alm.sunset.raw

        return data

    @staticmethod
    def _fmt_ts(ts, fmt=None):
        if not ts:
            return "00-000-0000 00:00"
        if fmt is None:
            fmt = "%d-%b-%Y %H:%M"
        return time.strftime(fmt, time.localtime(ts))

    def _fmt(self, data, label, places=None):
        value = data.get(label)
        if value is None:
            value = self.nonesub
        elif places is not None:
            try:
                v = float(value)
                fmt = "%%.%df" % places
                value = fmt % v
            except ValueError:
                pass
        return str(value)

    # the * indicates a field that is not part of a typical LOOP packet
    # the ## indicates calculation is not yet implemented
    def create_realtime_string(self, data):
        fields = []
        p_dp = 2 if data['units_pressure'] == 'in' else 1
        r_dp = 2 if data['units_rain'] == 'in' else 1
        datefmt = "%%d%s%%m%s%%y" % (self.datesep, self.datesep)
        fields.append(self._fmt_ts(data['dateTime'], datefmt))      # 1
        fields.append(self._fmt_ts(data['dateTime'], "%H:%M:%S"))   # 2
        fields.append(self._fmt(data, 'outTemp', 1))                # 3
        fields.append(self._fmt(data, 'outHumidity', 0))            # 4
        fields.append(self._fmt(data, 'dewpoint', 1))               # 5
        fields.append(self._fmt(data, 'windSpeed_avg', 1))          # 6  *
        fields.append(self._fmt(data, 'windSpeed', 1))              # 7
        fields.append(self._fmt(data, 'cumulus_windDir', 0))        # 8
        fields.append(self._fmt(data, 'rainRate', r_dp))            # 9
        fields.append(self._fmt(data, 'dayRain', r_dp))             # 10
        fields.append(self._fmt(data, 'barometer', p_dp))           # 11
        fields.append(self._fmt(data, 'windDir_compass'))           # 12 *
        fields.append(self._fmt(data, 'windSpeed_beaufort'))        # 13 *
        fields.append(self._fmt(data, 'units_wind'))                # 14 *
        fields.append(self._fmt(data, 'units_temperature'))         # 15 *
        fields.append(self._fmt(data, 'units_pressure'))            # 16 *
        fields.append(self._fmt(data, 'units_rain'))                # 17 *
        fields.append(self._fmt(data, 'windrun', 1))                # 18 *
        fields.append(self._fmt(data, 'pressure_trend', p_dp))      # 19 *
        fields.append(self._fmt(data, 'rain_month', r_dp))          # 20 *
        fields.append(self._fmt(data, 'rain_year', r_dp))           # 21 *
        fields.append(self._fmt(data, 'rain_yesterday', r_dp))      # 22 *
        fields.append(self._fmt(data, 'inTemp', 1))                 # 23
        fields.append(self._fmt(data, 'inHumidity', 0))             # 24
        fields.append(self._fmt(data, 'windchill', 1))              # 25
        fields.append(self._fmt(data, 'temperature_trend', 1))      # 26 *
        fields.append(self._fmt(data, 'outTemp_max', 1))            # 27 *
        fields.append(self._fmt(data, 'outTemp_max_time'))          # 28 *
        fields.append(self._fmt(data, 'outTemp_min', 1))            # 29 *
        fields.append(self._fmt(data, 'outTemp_min_time'))          # 30 *
        fields.append(self._fmt(data, 'windSpeed_max', 1))          # 31 *
        fields.append(self._fmt(data, 'windSpeed_max_time'))        # 32 *
        fields.append(self._fmt(data, 'windGust_max', 1))           # 33 *
        fields.append(self._fmt(data, 'windGust_max_time'))         # 34 *
        fields.append(self._fmt(data, 'pressure_max', p_dp))        # 35 *
        fields.append(self._fmt(data, 'pressure_max_time'))         # 36 *
        fields.append(self._fmt(data, 'pressure_min', p_dp))        # 37 *
        fields.append(self._fmt(data, 'pressure_min_time'))         # 38 *
        fields.append('%s' % weewx.__version__)                       # 39
        fields.append('0')                                            # 40
        fields.append(self._fmt(data, '10min_high_gust', 1))        # 41 *
        fields.append(self._fmt(data, 'heatindex', 1))              # 42 *
        fields.append(self._fmt(data, 'humidex', 1))                # 43 *
        fields.append(self._fmt(data, 'UV', 0))                     # 44
        fields.append(self._fmt(data, 'ET_today', r_dp))            # 45 *
        fields.append(self._fmt(data, 'radiation', 0))              # 46
        fields.append(self._fmt(data, '10min_avg_wind_bearing', 0)) # 47 *
        fields.append(self._fmt(data, 'rain_hour', r_dp))           # 48 *
        fields.append(self._fmt(data, 'zambretti_code'))            # 49 *
        fields.append(self._fmt(data, 'is_daylight'))               # 50 *
        fields.append(self._fmt(data, 'lost_sensors_contact'))      # 51 *
        fields.append(self._fmt(data, 'avg_wind_dir'))              # 52 *
        fields.append(self._fmt(data, 'cloudbase', 0))              # 53 *
        fields.append(self._fmt(data, 'units_cloudbase'))           # 54 *
        fields.append(self._fmt(data, 'appTemp', 1))                # 55 *
        fields.append(self._fmt(data, 'sunshine_hours', 1))         # 56 *
        fields.append(self._fmt(data, 'maxSolarRad', 1))            # 57
        fields.append(self._fmt(data, 'is_sunny'))                  # 58 *
        return ' '.join(fields)

    @staticmethod
    def _fmt_xml(tag, value):
        return '<realtime><data %s>%s</data></realtime>' % (tag, value)

    def create_realtime_xml(self, data):
        ts = time.localtime(data['dateTime'])
        lines = []
        lines.append('<?xml version="1.0" encoding="ISO-8859-15" ?>')
        lines.append('<maintag>')
        lines.append('<misc><data misc="refresh_time">'
                     '%04d.%02d.%02d. %02d%02d%02d</data></misc>' %
                     (ts.tm_year, ts.tm_mon, ts.tm_mday,
                      ts.tm_hour, ts.tm_min, ts.tm_sec))
        lines.append('<misc><data misc="forecast_nr">%s</data></misc>' %
                     data['zambretti_code'])
        lines.append(self._fmt_xml('misc="winddir"', data['cumulus_windDir']))
        lines.append(self._fmt_xml('misc="location"', self.location))
        lines.append(self._fmt_xml('realtime="forecast_text"', data['zambretti_text']))
        lines.append(self._fmt_xml('realtime="sunrise"', data['sunrise']))
        lines.append(self._fmt_xml('realtime="sunset"', data['sunset']))
        lines.append(self._fmt_xml('realtime="temp"', data['outTemp']))
        lines.append(self._fmt_xml('realtime="intemp"', data['inTemp']))
        lines.append(self._fmt_xml('realtime="hum"', data['outHumidity']))
        lines.append(self._fmt_xml('realtime="inhum"', data['inHumidity']))
        lines.append(self._fmt_xml('realtime="press"', data['barometer']))
        lines.append(self._fmt_xml('realtime="presstrendval"', data['pressure_trend']))
        lines.append(self._fmt_xml('realtime="dew"', data['dewpoint']))
        lines.append(self._fmt_xml('realtime="current_rainfall"', data['rain']))
        lines.append(self._fmt_xml('realtime="last_hour_rainfall"', data['rain_hour']))
        lines.append(self._fmt_xml('realtime="avg_windspeed"', data['windSpeed']))
        lines.append(self._fmt_xml('realtime="high_windgust"', data['10min_high_gust']))
        lines.append(self._fmt_xml('realtime="windunit"', data['units_wind']))
        lines.append(self._fmt_xml('realtime="wchill"', data['windchill']))
        lines.append(self._fmt_xml('realtime="avg_dir"', data['avg_wind_dir']))
        lines.append(self._fmt_xml('today="today_temp_high"', data['outTemp_max']))
        lines.append(self._fmt_xml('today="today_temp_low"', data['outTemp_min']))
        lines.append(self._fmt_xml('today="today_press_high"', data['pressure_max']))
        lines.append(self._fmt_xml('today="today_press_low"', data['pressure_min']))
        lines.append(self._fmt_xml('today="today_hour_rainfall"', data['rain_hour']))
        lines.append(self._fmt_xml('today="today_rainfall"', data['dayRain']))
        lines.append(self._fmt_xml('today="today_max_windspeed"', data['windSpeed_max']))
        lines.append(self._fmt_xml('today="today_max_windgust"', data['windGust_max']))

        # FIXME: implement yesterday hi/lo and record hi/lo
#        lines.append(self._fmt_xml('yesterday="yesterday_temp_high"', x))
#        lines.append(self._fmt_xml('yesterday="yesterday_temp_low"', x))
#        lines.append(self._fmt_xml('yesterday="yesterday_press_high"', x))
#        lines.append(self._fmt_xml('yesterday="yesterday_press_low"', x))
#        lines.append(self._fmt_xml('yesterday="yesterday_rainfall"', data['rain_yesterday']))
#        lines.append(self._fmt_xml('yesterday="yesterday_max_windspeed"', x))
#        lines.append(self._fmt_xml('yesterday="yesterday_max_windgust"', x))
#        lines.append(self._fmt_xml('record="rec_high_temp"', x))
#        lines.append(self._fmt_xml('record="rec_low_temp"', x))
#        lines.append(self._fmt_xml('record="rec_high_press"', x))
#        lines.append(self._fmt_xml('record="rec_low_press"', x))
#        lines.append(self._fmt_xml('record="rec_day_rainfall"', x))
#        lines.append(self._fmt_xml('record="rec_hour_rainfall"', x))
#        lines.append(self._fmt_xml('record="rec_max_windspeed"', x))
#        lines.append(self._fmt_xml('record="rec_max_windgust"', x))
        lines.append('</maintag>')
        return '\n'.join(lines)

    def create_sunbird_string(self, data):
        # FIXME: what format is expected here?
        # FIXME: are the datetime supposed to be local or utc?
        dtfmt = "%Y.%m.%d %H:%M:%S"
        parts = []
        parts.append("%s" % self.longitude)
        parts.append("%s" % self.latitude)
        parts.append(self._fmt_ts(data.get('sunrise'), dtfmt))
        parts.append(self._fmt_ts(data.get('sunset'), dtfmt))
        parts.append(self._fmt_ts(data['dateTime'], dtfmt))
        parts.append(self.location)
        return '|'.join(parts)

Reply via email to