#!env python
# -*- coding: iso-8859-1 -*-
###########################################################################
# 
# File:            oneWire.py
#
# License:         Donationware, see attached LICENSE file for more 
#                  information
#
# Author:          Olli Lammi (olammi@iki.fi)
#
# Version:         1.5d
#
# Date:            02.12.2014
#
# Description:     Library to communicate with the 1-wire devices using OWFS
#                  and DigiTemp
#                  
# Requirements:    Python interpreter 2.4 or newer (www.python.org)
#                  (tested with 2.4.3)
#
# Version history: ** 24.02.2009 v0.9a (Olli Lammi) **
#                  First beta version for testers. 
#
#                  ** 25.02.2009 v0.9c (Olli Lammi) **
#                  Logging and import changes 
#
#                  ** 06.04.2009 v1.0a (Olli Lammi) **
#                  Added DigiTemp support. 
#
#                  ** 06.04.2009 v1.0b (Olli Lammi) **
#                  Corrected DigiTemp support. 
#                  
#                  ** 17.08.2011 v1.0c (Olli Lammi) **
#                  Corrected OWFS "nan" value handling. 
#
#                  ** 01.01.2013 v1.5b (Olli Lammi) **
#                  Support for EDS OWSERVER-ENET-2.
#                  Added workaround for file reading (USE_EXTERNAL_PROCESS). 
#
#                  ** 24.03.2013 v1.5c (Olli Lammi) **
#                  Removed float value rounding to 2 decimals.
#
#                  ** 02.12.2014 v1.5d (Olli Lammi) **
#                  Removed unnecessary imports. Fixed Windows support.
#
###########################################################################

# Imports

import sys, os
import string
import re, urllib2
import xml.etree.ElementTree
import time
import os.path

from modules.core import log
from modules.utils import utils


###########################################################################

# Constants

OWTYPE_FLOAT = 1
OWTYPE_INT = 2


###########################################################################

# Classes

class OneWireFS(log.Logging):
    def __init__(self):
        log.Logging.__init__(self, 'OneWireFS')
        self.OWFS_DEVICES = {}
        self.timeout = 0
        self.USE_EXTERNAL_PROCESS = False
        
    def setTimeout(self, tout):
        self.timeout = tout

    def setUseExternalProcess(self, uep):
        self.USE_EXTERNAL_PROCESS = uep

    def setDevices(self, devmap):
        self.OWFS_DEVICES = devmap
        
    def runQueryCommand(self, cmd):
        try:
            item = self.OWFS_DEVICES[cmd]
        except:
            self.Log("ERROR: Invalid one wire key: " + cmd)
            return ""

        type = item[0]
        fname = item[1]

        if len(fname) <= 0:
            self.Log("ERROR: Empty sensor file for key: " + cmd)
            return ""

        self.Debug("Running oneWire query from OWFS file " + fname)
        data = ""
        try:
            if self.USE_EXTERNAL_PROCESS:
                if os.name == 'posix':
                    catcmd = 'cat ' + os.path.realpath(fname)
                elif os.name == 'nt':
                    catcmd = 'type ' + os.path.realpath(fname)
                self.Debug("Reading data from OWFS using command: " + catcmd)

                tocmd = utils.TimeoutableShellCommand()
                tocmd.setTimeout(self.timeout)
                (status, outstdout, outstderr) = tocmd.execute(catcmd)

                if status != 0:
                    temps = "ERROR: Error reading from OWFS\n"
                    temps = temps + "  OWFS read command: " + catcmd + "\n"
                    temps = temps + "  Command output: " + outstdout + outstderr
                    self.Log(temps)                
                    return ""
                else:
                    data = string.strip(outstdout)                                
            else:            
                infile = open(fname, 'r')
                data = infile.read()
                infile.close()
        except:
            self.Log("ERROR: Error reading sensor file: " + fname)
            return ""
        self.Debug("Received oneWire data: " + repr(data))

        tempOutput = data.find('t=')
        if tempOutput != -1:
            tempString = data.strip()[tempOutput+2:]
            data = str(float(tempString) / 1000.0)

        data = handleOWData(data, type)
        return data


class OneWireDigiTemp(log.Logging):
    def __init__(self, dtbin, dtconf, dtport):
        log.Logging.__init__(self, 'OneWireDigiTemp')
        self.DT_BINARY = dtbin
        self.DT_RC = dtconf
        self.DT_PORT = dtport
        self.timeout = 0
        
    def setTimeout(self, tout):
        self.timeout = tout

    def runQueryCommand(self, cmd):
        temp = string.split(cmd, '#', 1)
        cmdid = temp[0]
        cmdrow = 1
        if len(temp) > 1:
            try:
                cmdrow = int(temp[1])
            except:
                self.Log("ERROR: Invalid Digitemp value row indicator: " + cmd)
                return ""
                
        digitempcmd = self.DT_BINARY + ' -q -c ' + self.DT_RC + ' -s ' + self.DT_PORT + ' -t ' + cmdid
        digitempcmd = digitempcmd + ' -o "%.2C" -O "%C" -H "%h"'
        self.Debug("Reading data from DigiTemp using command: " + digitempcmd)

        tocmd = utils.TimeoutableShellCommand()
        tocmd.setTimeout(self.timeout)
        (stat, outstdout, outstderr) = tocmd.execute(digitempcmd)

        if stat != 0:
            temps = "ERROR: Error reading from DigiTemp\n"
            temps = temps + "  DigiTemp command: " + digitempcmd + "\n"
            temps = temps + "  DigiTemp output: " + outstdout + outstderr
            self.Log(temps)                
            data = ""
        else:
            data = string.strip(outstdout)
        self.Debug("Received oneWire data: " + repr(data))

        if len(data) > 0:
            rivit = string.split(data, '\n')
            data = ''
            rowid = 1
            for rivi in rivit:
                rivi = string.strip(rivi)                
                if len(rivi) > 0:
                    try:    
                        data = "%.2f" % float(rivi)
                        if rowid != cmdrow:
                            data = ''
                            rowid = rowid + 1
                            continue
                        return data
                    except:
                        continue
                    
        return data


class OneWireEDSOWSERVER(log.Logging):
    def __init__(self):
        log.Logging.__init__(self, 'OneWireEDSOWSERVER')
        self.URL = ''
        self.DATAPOINTS = {}

    def setDatapoints(self, pointmap):
        self.DATAPOINTS = pointmap

    def runQueryCommand(self, cmds):
        dataresult = {}

        data = ''
        self.Debug("Retrieving OWSERVER details.xml from URL: " + self.URL)
        
        try:
            f = urllib2.urlopen(self.URL)
            data = f.read()
            f.close()
        except:
            data = ''

        self.Debug("Received details.xml with %d bytes." % (len(data), ))

        if len(data) <= 0:
            self.Log("Cannot get data from URL: " + self.URL)
            return {}
        
        result = handleOWServerDetailsXML(data)
        if len(result.keys()) <= 0:
            self.Log("ERROR: No One wire device data in the details.xml data or data invalid.")
            return {}
        
        for cmd in cmds:
            if not self.DATAPOINTS.has_key(cmd):
                self.Log("ERROR: Undefined data point: " + cmd)
                continue
            item = self.DATAPOINTS[cmd]
            
            romid = item[0]
            type = item[1]
            valname = item[2]

            if len(valname) <= 0:
                self.Log("ERROR: Invalid or missing value name for data point: " + cmd)
                continue
            
            if not result.has_key(romid):
                self.Log("ERROR: One wire device ROM ID not found in the data: " + romid)
                continue
            
            resitem = result[romid]
            if not resitem.has_key(valname):
                self.Log("ERROR: One wire data key " + valname + " not found for ROM ID " + romid)
                continue
            
            dataresult[cmd] = handleOWData(resitem[valname], type)
                
        return dataresult
        

###########################################################################

# Functions

def handleOWData(data, type):
    if type == OWTYPE_FLOAT:
        data = string.strip(data)            
        if len(data) > 0:
            if string.lower(data) == 'nan':
                data = ""
            else:
                data = "%f" % float(data)
    elif type == OWTYPE_INT:
        data = string.strip(data)
        if len(data) > 0:
            if string.lower(data) == 'nan':
                data = ""
            else:
                data = "%d" % int(data)
 
    return data



RE_STRIP = re.compile('^\{[^\{]*\}(.*)$')
def stripNamespace(tag):
    match = RE_STRIP.match(tag)
    if match != None:
        return match.group(1)
    return tag

def handleOwDevice(deviceitem):
    result = ['', {}]
    id = ''
    for item in deviceitem:
        stag = string.upper(stripNamespace(item.tag))
        if stag == 'ROMID':
            id = string.upper(item.text)
        else:
            result[1][stag] = item.text
    if len(id) > 0:
        result[0] = id
        return result
    return None

def handleOWServerDetailsXML(data):
    devices = {}

    try:
        root = xml.etree.ElementTree.fromstring(data)
    except:
        return {}

    for item in root:
        stag = string.upper(stripNamespace(item.tag))
        if stag.startswith('OWD_'):
            device = handleOwDevice(item)

            if device != None:
                devices[device[0]] = device[1]

    return devices


