#!/usr/bin/env python
"""Ryan Burns <rdburns@gmail.com>
Temperature logger.
Reads external DS18B20 temperature sensors.
Logs temps to sqlite database.
Provides programmable alerts

Original Python 2.x logger that was mostly used for pushover alerts via cron

Hat tip to:
http://idledeveloper.com/monitoring-temperature-with-raspberry-pi/
http://raspberrywebserver.com/cgiscripting/rpi-temperature-logger/

Python 2.x

We're going to need an ini file for device labels and also for
alert levels.
"""
import os
import argparse
import ConfigParser
from pprint import pprint
try:
    from tinyfeedback.helper import send_once
except ImportError:
    print "Install tinyfeedback for logging."

def get_devices(debug):
    """Returns list of temperature sensors, just their serial numbers"""

    if not debug:
        print "Loading kernel modules."
        os.system('modprobe w1-gpio')
        os.system('modprobe w1-therm')
        loc = '/sys/bus/w1/devices/'
    else:
        loc = '/home/rburns/personal/'

    sensors = [x for x in os.listdir(loc) if x.startswith('28-')]
    # This is the list of temp sensor devices, which all have a unique
    # serial number.
    return sensors


def read_config_file(filename):
    """Reads ini file and returns structure containing the contained
    configuration"""
    config = {}
    sensors = {}

    def parse_sensor(cp, label):
        """Parses per-sensor section of config file.
        returns dict containing the configuration."""
        sens_dict = {}
        if cp.has_option(label, 'device'):
            sens_dict['device'] = cp.get(label,'device')
        else:
            return None
        if cp.has_option(label, 'high_alert'):
            sens_dict['high_alert'] = cp.getint(label,'high_alert')
        if cp.has_option(label, 'low_alert'):
            sens_dict['low_alert'] = cp.getint(label,'low_alert')
        return sens_dict

    def parse_main(cp):
        """Parses main section of config file.
        returns dict containing the configuration"""
        # Don't have any at the moment.
        if cp.has_option('Main','pushover'):
            pushstr = cp.get('Main','pushover')
            spl = pushstr.split(':')
            if (len(spl) != 2) or (len(spl[0]) != 30) or (len(spl[1]) != 30):
                print "Pushover entry in [Main] expects user_key:app_key, both 30 characters."
            else:
                config['pushover'] = {}
                config['pushover']['user'] = spl[0]
                config['pushover']['app'] = spl[1]
        if cp.has_option('Main','sqlite'):
            config['sqlite'] = cp.get('Main','sqlite')

        return config

    if filename is not '':
        cp = ConfigParser.RawConfigParser()
        cp.read(filename)
        for section in cp.sections():
            if section == 'Main':
                config = parse_main(cp)
            else:
                sens = parse_sensor(cp, section)
                if sens is not None:
                    sensors[section] = sens

    return config, sensors


def get_temp(device, sensors):
    """
    device is string with just the temperature serial number.

    returns: float temp in degrees F, or None if reading is invalid

    based on http://idledeveloper.com/monitoring-temperature-with-raspberry-pi/
    """
    device_path = '/sys/bus/w1/devices/' + device + '/w1_slave'

    #To read the sensor data, just open the w1_slave file
    with open(device_path, 'r') as f:
        data = f.readlines()

    deg_f = None
    if data[0].strip()[-3:] == 'YES':
        temp_thou_c = data[1][data[1].find('t=')+2:]
        # gets string with temp in thousandths of a degree
        # Celsius
        #If temp is 0 or not numeric an exception
        #will occur so lets handle it gracefully
        try:
            if float(temp_thou_c)==0:
                deg_f = 32.0
            else:
                deg_f = (float(temp_thou_c)/1000)*9/5+32
            deg_f = round(deg_f, 3)
        except:
            print "Error with t=", temp_thou_c
            pass

    found = False
    for label in sensors:
        print "get_temp label is",label
        if sensors[label]['device'] == device:
            sensors[label]['temp_f'] = deg_f
            found = True
    if not found:
        # Add to dict using device number as label
        sensors[device] = {}
        sensors[device]['device'] = device
        sensors[device]['temp_f'] = deg_f

    return deg_f


def send_pushover(config, msg):
    import httplib, urllib
    conn = httplib.HTTPSConnection("api.pushover.net:443")
    conn.request("POST", "/1/messages.json",
                 urllib.urlencode({
                     "token": config['pushover']['app'],
                     "user": config['pushover']['user'],
                     "message": msg,
                     }), { "Content-type": "application/x-www-form-urlencoded" })
    conn.getresponse()


def send_alerts(config, sensors):
    """Sends any alerts as appropriate.

    :param targets: List of target strings. Currently supported: Pushover.
    """
    def dispatch(config, msg):
        """This function dispatches the alert to any configured alert
        targets."""
        if 'pushover' in config:
            send_pushover(config, msg)

    for label in [x for x in sensors if sensors[x].has_key('temp_f')]:
        if 'high_alert' in sensors[label]:
            if sensors[label]['temp_f'] > sensors[label]['high_alert']:
                dispatch(config, "Over temp alert on " + label + \
                         " sensor: " + str(sensors[label]['temp_f']) + \
                         " F")
        if 'low_alert' in sensors[label]:
            if sensors[label]['temp_f'] < sensors[label]['low_alert']:
                dispatch(config, "Under temp alert on " + label + \
                        " sensor: " + str(sensors[label]['temp_f']) + \
                        " F")

def log_temperature(sensors):
    """Using tinyfeedback
    """
    print "Storing in tinyfeedback."
    for label in sensors:
        print 'debug',label,sensors[label]['temp_f']
        send_once('temps', {label: int(sensors[label]['temp_f'])})


def main(args):
    devices = get_devices(args.debug)
    config, sensors = read_config_file(args.config)

    # Reads temperature, and adds it to the 'sensors' dict.
    # Will add a new dictionary entry if the sensor is not
    # found in the list.
    # This lets you plug in a new sensor and see the output before
    # setting up the ini file.
    if devices is not None:
        for device in devices:
            t = get_temp(device, sensors)
    if args.alert:
        send_alerts(config, sensors)
    if args.tinyfeedback:
        log_temperature(sensors)
    pprint(config)
    pprint(sensors)


if __name__ == '__main__':
    parser = argparse.ArgumentParser(description=
                                     'Log temperatures from sensors.')
    parser.add_argument('-d', '--debug', action='store_true',
                        help=('Runs in debug mode for testing on '
                              'non-raspi system.')
                       )
    parser.add_argument('-f', '--config', default='',
                        help=('INI file containing configuration.')
                        )
    parser.add_argument('--alert', action='store_true',
                        help=('If present and configured in INI file, '
                              'will send temp alerts.')
                          )
    parser.add_argument('--tinyfeedback', action='store_true',
                        help=('If present, will send temp to tinyfeedback '
                              'instance for logging and display.')
                        )
    args = parser.parse_args()
    main(args)
