#!/usr/bin/python

# This script is a continuation of my series of temperature measurements
# usinjg the Dallas DS18B20 One-wire temperature sensor.
# See my blog for previous versions.
#
# This version adds graphing the results using RRDtool and PyRRD
# 
# MJL -- www.thepiandi.blogspot.com --  10/3/2013

import os
from datetime import datetime
import time
import subprocess
import sys
from Tkinter import Tk
from tkFileDialog import asksaveasfilename
from LCD_2X16 import *
from pyrrd.rrd import DataSource, RRA, RRD
from pyrrd.graph import DEF, CDEF, VDEF, LINE, AREA, GPRINT, COMMENT
from pyrrd.graph import ColorAttributes
from pyrrd.graph import Graph

def loadmodules():
    """ Checks to see if the 1-wire modules are loaded.  If not, loades them."""
    err = 'error'
    while err != '':
        modloaded = subprocess.Popen(['lsmod'], stdout = subprocess.PIPE, stderr = subprocess.PIPE)
        out, err = modloaded.communicate()
    if 'w1_therm' not in out or 'w1_gpio' not in out:
        print '1-Wire modules had to be loaded.  Waiting 10 seconds'
        print
        os.system('sudo modprobe w1-gpio')        
        os.system('sudo modprobe w1-therm')
        time.sleep(10)
        
def read_temp_raw():
    
    """ Uses Popen to open then read contents of w-slave. Returns the contents of the file as a two line list.
    If the file cannot be read the 1-wire modules are unloaded and loaded again. This is repeated
    three times until the file is read successfully.  After three times, the program
    is stopped."""
    
    global glitches
    trials = 3
    catdata = subprocess.Popen(['cat', device_file], stdout = subprocess.PIPE, stderr = subprocess.PIPE)
    out, err = catdata.communicate()
    while err != "" and trials:
        print "Got a glitch - trying to recover"        
        trials -= 1
        time.sleep(1)
        os.system('sudo modprobe -r w1-gpio')
        os.system('sudo modprobe -r w1-therm')
        time.sleep(1)
        os.system('sudo modprobe w1-gpio')
        os.system('sudo modprobe w1-therm')
        time.sleep(10)
        glitches += 1
        catdata = subprocess.Popen(['cat', device_file], stdout = subprocess.PIPE, stderr = subprocess.PIPE)
        out, err = catdata.communicate()
    if trials:
        out_decode = out.decode('utf-8')
        lines = out_decode.split('\n')
        return lines        
    else:
        raise(IOError)
        
def print2display(temperature, sen):
    """ Prints the temperature and time to the 16 character
    by two line LCD display.  Also prints to the stdio"""
    
    time.sleep(.01)
    if sen == '0':
        lcd_byte(LCD_LINE_1, LCD_CMD) # Print to top line
        string1 = "Brdbd: " + str(temperature) + " degF"
        time.sleep(.01)
        lcd_string(string1)
    if sen =='1':
        lcd_byte(LCD_LINE_2, LCD_CMD) # Print to bottom line
        string1 = "Cable: " + str(temperature) + " degF"
        time.sleep(.01)
        lcd_string(string1)

def check_switch():
    """Checks to see if the breadboard switch was pressed.  If so it generates a keyboard interrupt
    as if CTRL-C was pressed.  Switch must be held down for at least .2 seconds to be reconized"""
    input_switch = GPIO.input(SwitchInput) # Look for breadboard switch
    if not input_switch:
        time.sleep(.2)
        input_switch = GPIO.input(SwitchInput) # Look for breadboard switch again
        if not input_switch: # Must still be pressed
            print
            print "Switch Pressed"
            raise(KeyboardInterrupt)
    
def check_title(title):
    """Makes sure that all of the space characters are escaped with the backspace character"""
    cnt_spaces = title.count(' ')
    title_good = True
    start_pos = 0
    while cnt_spaces:
        index_space = title.find(' ', start_pos)
        if title[index_space -1] != '\\':
            title_good = False
        start_pos = index_space + 1
        cnt_spaces -= 1
    return title_good

# --------------------------------------------------------------------------------------------------

# Main Program

glitches = 0
device_id = ('28-00000400d39d' ,'28-000004986cbb')
prompt = 'Enter the measurement time interval in minutes (integer values only): '
prompt1 = 'Enter 0 for sensor on breadboard, 1 for cable sensor, 2 for both: '
prompt2 = 'Enter the maximum number of measurements: '
prompt3 = 'What are we measuring with the breadboard sensor? Default is Breadboard: '
prompt4 = 'What are we measuring with the cable sensor? Default is Cable: '
prompt5 = 'Enter comment if desired (Remember to escape punctuation): '
prompt6 = 'Enter a name for the file. No spaces, extension, or directory: '
prompt7 = 'Enter a title for the graph (Remember to escape spaces and punctuation): '
prompt8 = 'Satisfied with the inputs? Y or N: '
prompt9 = 'OK to proceed? If not, we quit. Y or N: '
prompt10 = 'Graph background color: Enter 0 for black, 1 for white, 2 for both: '
prompt11 = 'Choose height of graph in the range if 100 to 400 pixels: '

short_wait = .1

lcd_byte(0x01, LCD_CMD)  # Clear Display
loadmodules()   # Check to see if 1-Wire modules are loaded.

# Operator Inputs

good_inputs = False

while not good_inputs:
    
    # Choose which sensor or both
    print
    sensor = ''
    while sensor != '0' and sensor != '1' and sensor !='2': 
        sensor = raw_input(prompt1) 

    print
    if sensor == '0':
        print "You have choosen the breadboard sensor"
    elif sensor == '1':
        print "You have choosen the cable sensor"
    elif sensor == '2':
        print "You have choosen both sensors"

    # Legend Titles
    print
    if sensor == '0' or sensor == '2':
        measure_what0 = raw_input(prompt3)
        if measure_what0 == '':
            measure_what0 = 'Breadboard'

    if sensor == '1' or sensor == '2':
        measure_what1 = raw_input(prompt4)
        if measure_what1 == '':
            measure_what1 = 'Cable'

    # Graph Title
    print
    title_good = False
    while not title_good:
        title_it = ''
        while not title_it:
            title_it = raw_input(prompt7)
        title_good = check_title(title_it)
        if not title_good:
            print "\n\tYou forgot to escape all of the space characters, try again: "
            
    # Comment    
    print
    comment = raw_input(prompt5)
    cmt = COMMENT(comment)

    # Graph Colors
    print
    background = ''
    while background != '0' and background != '1' and background !='2': 
        background = raw_input(prompt10)

    # Height of Graph
    print
    how_high = 0
    while how_high < 100 or how_high > 400:
        try:
            how_high = abs(int(raw_input(prompt11)))
        except:
            pass

    # File names
    print
    directory = '/home/pi/Documents/PythonProjects/TempProbe/TempResults/'
    filename = raw_input(prompt6)
    graphfile_blk = directory + filename + '_black.png'
    graphfile_wht = directory + filename + '_white.png'   

    if sensor == '0' or sensor == '2':
        rrdfile0 = directory + filename + '_bread.rrd'
        
    if sensor == '1' or sensor == '2':
        rrdfile1 = directory + filename + '_cable.rrd'

    # Maximum number of measurements
    print
    max_measurements = 0
    while not max_measurements:
        try:
            max_measurements = abs(int(raw_input(prompt2)))
        except:
            pass

    # Measurement Interval
    print
    measurement_interval = 0
    while not measurement_interval:
        try:
            measurement_interval = abs(int(raw_input(prompt)))
        except:
            pass

    # Satisfied With The Inputs?
    print
    response = ""
    while response != 'y' and response != 'Y' and response != 'n' and response != 'N':
        response = raw_input(prompt8)
    if response == 'Y' or response == 'y':
        good_inputs = True

# Good to Proceed?
print
proceed = ''
while proceed != 'y' and proceed != 'Y' and proceed != 'n' and proceed != 'N':
    proceed = raw_input(prompt9)
  
print

measurement_interval *= 60

start_time = int(time.time() / measurement_interval) * measurement_interval

next_meas_time= start_time + measurement_interval

# Breadboard RRD Setup

if sensor == '0' or sensor == '2':  
    dataSources = []
    roundRobinArchives = []
    dataSource = DataSource(dsName='Breadboard', dsType='GAUGE', heartbeat=int(1.5 * measurement_interval))
    dataSources.append(dataSource)

    roundRobinArchives.append(RRA(cf='LAST', xff=0.5, steps=1, rows=max_measurements))

    breadboard = RRD(rrdfile0, step=measurement_interval, ds=dataSources, rra=roundRobinArchives, start=start_time)
    breadboard.create(debug=False)

#   Cable RRD Setup

if sensor == '1' or sensor == '2':  
    dataSources = []
    roundRobinArchives = []
    dataSource = DataSource(dsName='Cable', dsType='GAUGE', heartbeat=int(1.5 * measurement_interval))
    dataSources.append(dataSource)

    roundRobinArchives.append(RRA(cf='LAST', xff=0.5, steps=1, rows=max_measurements))

    cable = RRD(rrdfile1, step=measurement_interval, ds=dataSources, rra=roundRobinArchives, start=start_time)
    cable.create(debug=False)

# Breadboard Graph Setup

if sensor == '0' or sensor == '2':
    bread_def = DEF(rrdfile=rrdfile0, vname='Bread_data', dsName='Breadboard', cdef='LAST')
    bread_line = LINE(defObj=bread_def, color='#00FF00', legend=measure_what0 + ' Temperature')
    bread_aver = VDEF(vname='Bread_aver', rpn='%s,AVERAGE' % bread_def.vname)
    bread_val = GPRINT(bread_aver, 'Average ' + measure_what0 + ' Temperature: %6.2lf Degrees F')

# Cable Graph Setup

if sensor == '1' or sensor == '2':
    cable_def = DEF(rrdfile=rrdfile1, vname='Cable_data', dsName='Cable', cdef='LAST')
    cable_line = LINE(defObj=cable_def, color='#FF0000', legend=measure_what1 + ' Temperature')
    cable_aver = VDEF(vname='Cable_aver', rpn='%s,AVERAGE' % cable_def.vname)
    cable_val = GPRINT(cable_aver, 'Average ' + measure_what1 + ' Temperature: %6.2lf Degrees F')

# Define Graph Colors
#    black background:
black_bkgnd = ColorAttributes()
black_bkgnd.back = '#000000'
black_bkgnd.canvas = '#333333'
black_bkgnd.shadea = '#000000'
black_bkgnd.shadeb = '#111111'
black_bkgnd.mgrid = '#CCCCCC'
black_bkgnd.axis = '#FFFFFF'
black_bkgnd.frame = '#0000AA'
black_bkgnd.font = '#FFFFFF'
black_bkgnd.arrow = '#FFFFFF'

#    white background:
white_bkgnd = ColorAttributes()
white_bkgnd.back = '#FFFFFF'
white_bkgnd.canvas = '#EEEEEE'
white_bkgnd.shadea = '#000000'
white_bkgnd.shadeb = '#111111'
white_bkgnd.mgrid = '#444444'
white_bkgnd.axis = '#000000'
white_bkgnd.frame = '#0000AA'
white_bkgnd.font = '#000000'   
white_bkgnd.arrow = '#000000'

# Let's make some measurements and graph them
try:
    if proceed == 'N' or proceed == 'n':
        raise(KeyboardInterrupt)

    print 'First Measurement Will Be Made At: ' + time.asctime(time.localtime(next_meas_time))
    print
    while max_measurements:
        time_now = time.time()
        while time_now < next_meas_time:
            check_switch()
            time.sleep(0.5)
            time_now = time.time()           
                      
        if sensor == '0' or sensor == '2':
            device_file = '/sys/bus/w1/devices/' + device_id[0] + '/w1_slave'
            measurement_good = False
            while measurement_good == False:
                lines = read_temp_raw()
                if 'YES' in lines[0]:
                    equals_pos = lines[1].find('t=')
                    if equals_pos != -1:
                        measurement_good = True
                        temp_string = lines[1][equals_pos +2:]
                        Deg_C = float(temp_string)/1000.0
                        Deg_F = round(Deg_C * 1.8 + 32.0, 1)                    
                        timenow = datetime.now()
                        print timenow.strftime("%A, %B %d, %I:%M:%S %p") + (", the Breadboard sensor temperature is %3.1f") %(Deg_F) + u"\xB0" +"F"
                        print2display(Deg_F, '0')
                if measurement_good == False:
                    time.sleep(short_wait)
                    
            breadboard.bufferValue(next_meas_time, str(Deg_F))
            breadboard.update(debug = False)
            
        if sensor == '1' or sensor == '2':
            device_file = '/sys/bus/w1/devices/' + device_id[1] + '/w1_slave'
            measurement_good = False
            while measurement_good == False:
                lines = read_temp_raw()
                if 'YES' in lines[0]:
                    equals_pos = lines[1].find('t=')
                    if equals_pos != -1:
                        measurement_good = True
                        temp_string = lines[1][equals_pos +2:]
                        Deg_C = float(temp_string)/1000.0
                        Deg_F = round(Deg_C * 1.8 + 32.0, 1)                    
                        timenow = datetime.now()
                        print timenow.strftime("%A, %B %d, %I:%M:%S %p") + (", the Cable sensor temperature is %3.1f") %(Deg_F) + u"\xB0" +"F"
                        print2display(Deg_F, '1')
                if measurement_good == False:
                    time.sleep(short_wait)
                    
            cable.bufferValue(next_meas_time, str(Deg_F))
            cable.update(debug = False)

        next_meas_time += measurement_interval  
        max_measurements -= 1
            
        gb = Graph(graphfile_blk, start = start_time, end = next_meas_time - measurement_interval, color = black_bkgnd, vertical_label='Degrees\ F', width=600, height=how_high, title=title_it)
        gw = Graph(graphfile_wht, start = start_time, end = next_meas_time - measurement_interval, color = white_bkgnd, vertical_label='Degrees\ F', width=600, height=how_high, title=title_it)

        if sensor == '0':
            gb.data.extend([bread_def, bread_line, bread_aver, bread_val])
            gw.data.extend([bread_def, bread_line, bread_aver, bread_val])
        if sensor == '1':
            gb.data.extend([cable_def, cable_line, cable_aver, cable_val])
            gw.data.extend([cable_def, cable_line, cable_aver, cable_val])           
        if sensor == '2':
            gb.data.extend([bread_def, bread_line, bread_aver])
            gb.data.extend([cable_def, cable_line, cable_aver])
            gb.data.extend([bread_val, cable_val])
            gw.data.extend([bread_def, bread_line, bread_aver])
            gw.data.extend([cable_def, cable_line, cable_aver])
            gw.data.extend([bread_val, cable_val])
        if comment:
            gb.data.extend([cmt])
            gw.data.extend([cmt])

        if background == '0' or background == '2':          
            gb.write()
        if background == '1' or background == '2':          
            gw.write()
        
except(KeyboardInterrupt):
    print

except(IOError):
    print
    print 'Three tries and we are out of here'    

except:
    print
    print "Unexpected Error: ", sys.exc_info()[1]
        
print
print "See you later, Glitches = %d" % glitches

lcd_byte(0x01, LCD_CMD)  # Clear Display
GPIO.cleanup()

print           
print "start time: ", start_time
print "last measurement: ", next_meas_time

run_time = next_meas_time - start_time - measurement_interval
run_days = run_time / 86400
run_hours = run_time % 86400 / 3600
run_minutes = run_time % 86400 % 3600 / 60
total_measurements = run_time / measurement_interval

print
print 'Total Run Time: %2d days, %2d hours, %2d minutes' %(run_days, run_hours, run_minutes)
print 'Total Number of Measurements Per Device: ' + str(total_measurements)
print