Skip to content
Snippets Groups Projects
model.py 4.93 KiB
Newer Older
Bruce Flynn's avatar
Bruce Flynn committed
import os
from datetime import datetime, timedelta

import rrdtool
import numpy as np

from .time import to_unix_timestamp
Bruce Flynn's avatar
Bruce Flynn committed
from .wind import wind_vector_degrees
def dewpoint(tempC, relhum):
    """
    Algorithm from Tom Whittaker tempC is the temperature in degrees Celsius,
    relhum is the relative humidity as a percentage.

    :param tempC: temperature in celsius
    :param relhum: relative humidity as a percentage
    """
    gasconst = 461.5
    latheat = 2500800.0

Bruce Flynn's avatar
Bruce Flynn committed
    dp = (1.0 / (1.0 / (273.15 + tempC) - gasconst * np.log((0.0 + relhum) / 100) / (latheat - tempC * 2397.5)))

    return np.minimum(dp - 273.15, tempC)


class RrdModel(object):

Bruce Flynn's avatar
Bruce Flynn committed
    keys = ('air_temp', 'rh', 'dewpoint',
            'wind_speed', 'winddir_north', 'winddir_east',
            'pressure', 'precip', 'accum_precip', 'solar_flux',
Bruce Flynn's avatar
Bruce Flynn committed
            'altimeter')

    def __init__(self, filepath):
        self._filepath = filepath
Bruce Flynn's avatar
Bruce Flynn committed
        self._averages = tuple()

    @property
    def averaging_intervals(self):
        """Lazy load averaging intervals from database.
        """
        if not self._averages:
            averages = []
            info = rrdtool.info(self._filepath)
            for key in info.keys():
                if key.startswith('rra') and key.endswith('pdp_per_row'):
                    averages.append(int(info[key]*info['step']))
            averages.sort()
            self._averages = tuple(averages)
        return self._averages

    def initialize(self, start=None):
Bruce Flynn's avatar
Bruce Flynn committed
        assert not os.path.exists(self._filepath)

        start = start or datetime.now() - timedelta(days=365)
        secs = to_unix_timestamp(start)
        rrdtool.create(self._filepath,
                       '--start={}'.format(secs),
                       '--step=5',
                       'DS:air_temp:GAUGE:10:-40:50',
                       'DS:rh:GAUGE:10:0:100',
                       'DS:dewpoint:GAUGE:10:0:100',
                       'DS:wind_speed:GAUGE:10:0:100',
                       'DS:winddir_north:GAUGE:10:-100:100',
                       'DS:winddir_east:GAUGE:10:-100:100',
                       'DS:pressure:GAUGE:10:0:1100',
                       'DS:precip:GAUGE:10:0:100',
                       'DS:accum_precip:GAUGE:10:0:100',
                       'DS:solar_flux:GAUGE:10:0:1000',
                       'DS:altimeter:GAUGE:10:0:100',
                       'RRA:AVERAGE:0.5:1:6307200',
                       'RRA:AVERAGE:0.5:12:525600',
                       'RRA:AVERAGE:0.5:60:105120',
                       'RRA:AVERAGE:0.5:360:17520')

    def _print(self, record):
        stamp = record.get_stamp()
        values = ':'.join([str(record[k]) for k in self.keys])
        values = '{:d}:{}'.format(to_unix_timestamp(stamp), values)
        print values

Bruce Flynn's avatar
Bruce Flynn committed
    def add_record(self, record):
        pass

    def get_slice(self, start, end, names=None, average=5):
Bruce Flynn's avatar
Bruce Flynn committed
        """Get a slice of data from the database.
Bruce Flynn's avatar
Bruce Flynn committed
        :param start: Start time as datetime
        :param end: Inclusive end time as datetime
        :param names: Names to query for, defaults to all available, see ``keys``
        :param average: Averaging interval supported by the database, see ``averaging_intervals``.
        """
        names = names or self.keys[:]

        if isinstance(start, datetime):
            start = to_unix_timestamp(start)
        if isinstance(end, datetime):
            end = to_unix_timestamp(end)

        # normalize request times to averaging interval
        start -= start % average
        end -= end % average

        range, columns, rawdata = rrdtool.fetch(self._filepath,
                                                'AVERAGE',
                                                '-r {:d}'.format(average),
                                                '-s {:d}'.format(start),
                                                '-e {:d}'.format(end))

        src_data = np.array(rawdata)
        dst_data = np.zeros((src_data.shape[0], len(names))) * float('nan')

        # get only the columns we're interested in
        for dst_idx, name in enumerate(names):
            if name in columns:
                dst_data[:,dst_idx] = src_data[:,columns.index(name)]

                # we compute dewpoint since it wasn't always available
                if name == 'dewpoint':
                    temp = src_data[:,self.keys.index('air_temp')].astype(np.float64)
                    rh = src_data[:,self.keys.index('rh')].astype(np.float64)
                    dst_data[:,dst_idx] = dewpoint(temp, rh)

            # get the wind direction in degrees from the vector components
            elif name == 'wind_dir':
                east = src_data[:,self.keys.index('winddir_east')].astype(np.float64)
                north = src_data[:,self.keys.index('winddir_north')].astype(np.float64)
Bruce Flynn's avatar
Bruce Flynn committed
                dst_data[:,dst_idx] = wind_vector_degrees(east, north)

        times = np.array([np.arange(start, end + average, average)])
        return np.concatenate((times.T, dst_data), axis=1)