EvaluationEfficiencyOptimiz.../davisAPI/PyWeather/scripts/weatherpub.py

241 lines
7.4 KiB
Python

#!/usr/bin/env python
#
# PyWeather example script for reading PyWeather stations uploading to one or
# more PyWeather publication sites
#
# Author: Patrick C. McGinty
# Email: pyweather@tuxcoder.com
# Date: Sunday, May 02 2010
'''
Periodically read data from a local weather station and upload to the PyWeather
publication site.
'''
import os
import sys
import time
import logging
import optparse
import configparser
import weather.stations
import weather.stations.netatmo
import weather.services
log = logging.getLogger('')
# Intervals (in minutes) between each archive record generated by the weather
# station:
ARCHIVE_INTERVAL = 10
# Gust 'time to live'; define many minutes should gust be reported:
GUST_TTL = 10
GUST_MPH_MIN = 7 # minimum mph of gust above avg wind speed to report
# Publication Services Lookup Table
# key expected to match optparse destination parameter
# value defines class object of publication service
PUB_SERVICES = {
'wug': weather.services.Wunderground,
'pws': weather.services.PwsWeather,
'pwsweather': weather.services.PwsWeather,
'file': weather.services.TextFile,
}
STATIONS = {
'netatmo': weather.stations.netatmo.NetatmoStation,
'vantage_pro': weather.stations.VantagePro,
}
class NoSensorException(Exception):
pass
class WindGust(object):
NO_VALUE = ('NA', 'NA')
def __init__(self):
self.value = self.NO_VALUE
self.count = 0
def get(self, station, interval):
'''
return gust data, if above threshold value and current time is inside
reporting window period
'''
rec = station.fields['Archive']
# process new data
if rec:
threshold = station.fields['WindSpeed10Min'] + GUST_MPH_MIN
if rec['WindHi'] >= threshold:
self.value = (rec['WindHi'], rec['WindHiDir'])
self.count = GUST_TTL * 60 / interval
else:
self.value = self.NO_VALUE
# return gust value, if remaining time is left, and valid
if self.count:
self.count -= 1
else:
self.value = self.NO_VALUE
log.debug('wind gust of {0} mph from {1}'.format(*self.value))
return self.value
WindGust = WindGust()
def weather_update(station, pub_sites, interval):
'''
main execution loop. query weather data and post to online service.
'''
point = station.get_reading()
# santity check weather data
if point.temperature_f > 200:
raise NoSensorException(
'Out of range temperature value: %.1f, check sensors' %
(point.temperature_f,))
gust = None
gust_dir = None
if isinstance(station, weather.stations.VantagePro):
# Wind is only supported in VantagePro.
gust, gust_dir = WindGust.get(station, interval)
# upload data in the following order:
for ps in pub_sites:
try: # try block necessary to attempt every publisher
ps.set(
pressure=point.pressure,
dewpoint=point.dew_point_f,
humidity=point.humidity,
tempf=point.temperature_f,
rainin=point.rain_rate_in,
rainday=point.rain_day_in,
windspeed=point.wind_speed_mph,
winddir=point.wind_direction,
windgust=gust,
windgustdir=gust_dir,
dateutc=point.time.strftime("%Y-%m-%d %H:%M:%S"))
ps.publish()
# TODO: add user-friendly name
log.info("Published to %s", ps.__class__)
except (Exception) as e:
log.exception('publisher %s: %s' % (ps.__class__.__name__, e))
def init_log(quiet, debug):
'''
setup system logging to desired verbosity.
'''
from logging.handlers import SysLogHandler
fmt = logging.Formatter(
os.path.basename(sys.argv[0]) +
".%(name)s %(levelname)s - %(message)s")
facility = SysLogHandler.LOG_DAEMON
syslog = SysLogHandler(address='/dev/log', facility=facility)
syslog.setFormatter(fmt)
log.addHandler(syslog)
if not quiet:
console = logging.StreamHandler()
console.setFormatter(fmt)
log.addHandler(console)
log.setLevel(logging.INFO)
if debug:
log.setLevel(logging.DEBUG)
def get_pub_services(opts, config):
'''
use values in opts data to generate instances of publication services.
'''
sites = []
for p_key in list(vars(opts).keys()):
args = getattr(opts, p_key)
if p_key in PUB_SERVICES and args:
if isinstance(args, tuple):
ps = PUB_SERVICES[p_key](*args)
else:
ps = PUB_SERVICES[p_key](args)
sites.append(ps)
if config:
for p_key in config['general']['publication'].split(','):
ps = PUB_SERVICES[p_key](**config[p_key])
sites.append(ps)
return sites
def get_options(parser):
'''
read command line options to configure program behavior.
'''
# station services
# publication services
pub_g = optparse.OptionGroup(
parser, "Publication Services",
'One or more publication service must be specified to enable upload '
'of weather data.',)
pub_g.add_option(
'-w', '--wundergound', nargs=2, type='string', dest='wug',
help='Weather Underground service; WUG=[SID(station ID), PASSWORD]')
pub_g.add_option(
'-p', '--pws', nargs=2, type='string', dest='pws',
help='PWS service; PWS=[SID(station ID), PASSWORD]')
pub_g.add_option(
'-f', '--file', nargs=1, type='string', dest='file',
help='Local file; FILE=[FILE_NAME]')
parser.add_option_group(pub_g)
parser.add_option(
'-d', '--debug', dest='debug', action="store_true",
default=False, help='enable verbose debug logging')
parser.add_option(
'-q', '--quiet', dest='quiet', action="store_true",
default=False, help='disable all console logging')
parser.add_option(
'-t', '--tty', dest='tty', default='/dev/ttyS0',
help='set serial port device [/dev/ttyS0]')
parser.add_option(
'-n', '--interval', dest='interval', default=60,
type='int', help='polling/update interval in seconds [60]')
parser.add_option('-c', '--config', dest='config_path', default=None,
type='str', help='path to the configuration file')
return parser.parse_args()
if __name__ == '__main__':
parser = optparse.OptionParser()
opts, args = get_options(parser)
init_log(opts.quiet, opts.debug)
config = None
if opts.config_path:
config = configparser.ConfigParser()
config.read_file(open(opts.config_path))
# configure publication service defined in command-line args
pub_sites = get_pub_services(opts, config)
if not pub_sites:
log.error('no publication service defined')
sys.exit(-1)
if config:
station_name = config['general']['station']
station = STATIONS[station_name](**config[station_name])
else:
# Only VantagePro is supported without config.
station = weather.stations.VantagePro(opts.tty, ARCHIVE_INTERVAL)
while True:
try:
weather_update(station, pub_sites, opts.interval)
except (Exception) as e:
log.exception(e)
# pause until next update time
next_update = opts.interval - (time.time() % opts.interval)
log.info('sleep')
time.sleep(next_update)