#!/usr/bin/python

# Copyright CERN, 2011
# Author: Matthieu Cattin (CERN)
# Author (modifications): Richard Carrillo <rcarrillo(AT)sevensols.com>
# Licence: GPL v2 or later.
# Website: http://www.ohwr.org
# Website: http://www.sevensols.com
# Last modifications: 1/4/2012

# Import standard modules
import sys
import time

# Import specific modules
from onewire import *

# Class to access the DS18B20 (temperature sensor & unique ID) chip.
# It uses the onewire class and supports only one device on the onewire bus.

class DS18B20OperationError(Exception):
    def __init__(self, msg):
        self.msg = msg
    def __str__(self):
        return ("DS18B20: %s" %(self.msg))

class CDS18B20:

    # ROM commands
    ROM_SEARCH = 0xF0
    ROM_READ = 0x33
    ROM_MATCH = 0x55
    ROM_SKIP = 0xCC
    ROM_ALARM_SEARCH = 0xEC

    # DS18B20 functions commands
    CONVERT_TEMP = 0x44
    WRITE_SCRATCHPAD = 0x4E
    READ_SCRATCHPAD = 0xBE
    COPY_SCRATCHPAD = 0x48
    RECALL_EEPROM = 0xB8
    READ_POWER_SUPPLY = 0xB4

    FAMILY_CODE = 0x28
    MIN_TEMPER = -55
    MAX_TEMPER = 125

    # Thermometer resolution configuration
    RES = {'9-bit':0x0, '10-bit':0x1, '11-bit':0x2, '12-bit':0x3}

    def __init__(self, onewire, port):
        self.onewire = onewire
        self.port = port

    def read_serial_number(self):
        try:
            #print '[DS18B20] Reading serial number'
            self.onewire.reset(self.port)
            #print '[DS18B20] Write ROM command %.2X' % self.ROM_READ
            self.onewire.write_byte(self.port, self.ROM_READ)
            family_code = self.onewire.read_byte(self.port)
            if family_code != self.FAMILY_CODE:
                raise DS18B20OperationError("Invalid family code reported:0x02X (expected:0x02X). Wrong chip mounted?" % (family_code, self.FAMILY_CODE))
            serial_number = 0
            for i in range(6):
                serial_number |= self.onewire.read_byte(self.port) << (i*8)
            crc = self.onewire.read_byte(self.port)
            #print '[DS18B20] Family code  : %.2X' % family_code
            #print '[DS18B20] Serial number: %.12X' % serial_number
            #print '[DS18B20] CRC          : %.2X' % crc
            return ((crc<<56) | (serial_number<<8) | family_code)
        except OneWireDeviceOperationError as e:
            raise DS18B20OperationError(e)

    def access(self, serial_number):
        try:
            #print '[DS18B20] Accessing device'
            self.onewire.reset(self.port)
            #print '[DS18B20] Write ROM command %.2X' % self.ROM_MATCH
            self.onewire.write_byte(self.port, self.ROM_MATCH)
            #print '[DS18B20] Serial number: 0x%16X' % serial_number
            block = []
            for i in range(8):
                block.append(serial_number & 0xFF)
                serial_number >>= 8
                #print '[DS18B20] block[%d]:0x%02X' % (i, block[-1])
            self.onewire.write_block(self.port, block)
        except OneWireDeviceOperationError as e:
            raise DS18B20OperationError(e)

    def read_temp(self, serial_number):
        try:
            #print '[DS18B20] Reading temperature'
            self.access(serial_number)
            #print '[DS18B20] Write function command %.2X' % self.CONVERT_TEMP
            self.onewire.write_byte(self.port, self.CONVERT_TEMP)
            time.sleep(0.9)
            self.access(serial_number)
            #print '[DS18B20] Write function command %.2X' % self.READ_SCRATCHPAD
            self.onewire.write_byte(self.port, self.READ_SCRATCHPAD)
            data = self.onewire.read_block(self.port, 9)
            #for i in range(9):
            #    print '[DS18B20] Scratchpad data[%1d]: %.2X' % (i, data[i])
            temp = (data[1] << 8) | (data[0])
            if temp & 0x1000:
                temp = -0x10000 + temp
            temp = temp/16.0
            if temp < self.MIN_TEMPER or temp > self.MAX_TEMPER:
                raise DS18B20OperationError("Sensor reported an invalid temperature.")
            return temp
        except OneWireDeviceOperationError as e:
            raise DS18B20OperationError(e)

    # Set temperature thresholds
    # TODO

    # Configure thermometer resolution
    # TODO
