#!/usr/bin/python
#===============================================================================
# CERN (BE-CO-HT)
# Read the CONV-TTL-BLO board ID and current temperature from DS18B20 chip
#===============================================================================
# author: Theodor Stana (t.stana@cern.ch)
#
# date of creation:
#
# version: 1.0
#
# description:
#   The script implements some classes to communicate to the OpenCores One-Wire
#   master core implemented inside the CONV-TTL-BLO and uses these classes to
#   retrieve the board ID and the current temperature on-board the CONV-TTL-BLO.
#
#===============================================================================
# GNU LESSER GENERAL PUBLIC LICENSE
#===============================================================================
# This source file is free software; you can redistribute it and/or modify it
# under the terms of the GNU Lesser General Public License as published by the
# Free Software Foundation; either version 2.1 of the License, or (at your
# option) any later version. This source is distributed in the hope that it
# will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty
# of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
# See the GNU Lesser General Public License for more details. You should have
# received a copy of the GNU Lesser General Public License along with this
# source; if not, download it from http://www.gnu.org/licenses/lgpl-2.1.html
#===============================================================================
# last changes:
#===============================================================================
#  TODO: - 
#===============================================================================
# Import system modules
import sys
import time
import os

# Import common modules
sys.path.append("../ei2c")
from ei2c import *
import ei2cdefine

##------------------------------------------------------------------------------
## OneWire class
##------------------------------------------------------------------------------
class COpenCoresOneWire:

    R_CSR = 0x0
    R_CDR = 0x4

    CSR_DAT_MSK   = (1<<0)
    CSR_RST_MSK   = (1<<1)
    CSR_OVD_MSK   = (1<<2)
    CSR_CYC_MSK   = (1<<3)
    CSR_PWR_MSK   = (1<<4)
    CSR_IRQ_MSK   = (1<<6)
    CSR_IEN_MSK   = (1<<7)
    CSR_SEL_OFS   = 8
    CSR_SEL_MSK   = (0xF<<8)
    CSR_POWER_OFS = 16
    CSR_POWER_MSK = (0xFFFF<<16)
    CDR_NOR_MSK   = (0xFFFF<<0)
    CDR_OVD_OFS   = 16
    CDR_OVD_MSK   = (0XFFFF<<16)

    def wr_reg(self, addr, val):
        self.bus.write(self.cslot, self.base + addr,val)

    def rd_reg(self,addr):
        return self.bus.read(self.cslot, self.base + addr)

    def __init__(self, bus, cslot, base, clk_div_nor, clk_div_ovd):
        self.bus = bus
        self.base = base
        self.cslot = cslot
        data = (((clk_div_nor & self.CDR_NOR_MSK) |
               ((clk_div_ovd<<self.CDR_OVD_OFS) & self.CDR_OVD_MSK)))
        self.wr_reg(self.R_CDR, data)

    def reset(self, port):
        data = (((port<<self.CSR_SEL_OFS) & self.CSR_SEL_MSK) |
               self.CSR_CYC_MSK |
               self.CSR_RST_MSK)
        self.wr_reg(self.R_CSR, data)
        tmo = 100
        while(self.rd_reg(self.R_CSR) & self.CSR_CYC_MSK):
            tmo = tmo -1
            if tmo <= 0:
                msg = "ERROR: TempID IC20: Not responding"
                print(msg)

        reg = self.rd_reg(self.R_CSR)
        return ~reg & self.CSR_DAT_MSK

    def slot(self, port, bit):
        data = (((port<<self.CSR_SEL_OFS) & self.CSR_SEL_MSK) |
               self.CSR_CYC_MSK |
               (bit & self.CSR_DAT_MSK))
        self.wr_reg(self.R_CSR, data)
        tmo = 100
        while(self.rd_reg(self.R_CSR) & self.CSR_CYC_MSK):
            tmo = tmo -1
            if tmo <= 0:
                msg = "ERROR: TempID IC20: Not responding"
                print(msg)

        reg = self.rd_reg(self.R_CSR)
        return reg & self.CSR_DAT_MSK

    def read_bit(self, port):
        return self.slot(port, 0x1)

    def write_bit(self, port, bit):
        return self.slot(port, bit)

    def read_byte(self, port):
        data = 0
        for i in range(8):
            data |= self.read_bit(port) << i
        return data

    def write_byte(self, port, byte):
        data = 0
        byte_old = byte
        for i in range(8):
            data |= self.write_bit(port, (byte & 0x1)) << i
            byte >>= 1
        if(byte_old == data):
            return 0
        else:
            return -1

    def write_block(self, port, block):
        if(160 < len(block)):
            return -1
        data = []
        for i in range(len(block)):
            data.append(self.write_byte(port, block[i]))
        return data

    def read_block(self, port, length):
        if(160 < length):
            return -1
        data = []
        for i in range(length):
            data.append(self.read_byte(port))
        return data

##------------------------------------------------------------------------------
## DS18B20 class
##------------------------------------------------------------------------------
class CDS18B20:

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

    CONVERT_TEMP      = 0x44
    WRITE_SCRATCHPAD  = 0x4E
    READ_SCRATCHPAD   = 0xBE
    COPY_SCRATCHPAD   = 0x48
    RECALL_EEPROM     = 0xB8
    READ_POWER_SUPPLY = 0xB4

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

    def read_serial_number(self):
        if(1 != self.onewire.reset(self.port)):
            msg = "ERROR: TempID IC20: Not responding"
            print(msg)
        else:
            err = self.onewire.write_byte(self.port, self.ROM_READ)
            if err != 0:
                msg = "ERROR: TempID IC20: Write failed"
                print(msg)

            family_code = self.onewire.read_byte(self.port)
            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)

        return ((crc<<56) | (serial_number<<8) | family_code)

    def access(self, serial_number):
        if(1 != self.onewire.reset(self.port)):
            msg = "ERROR: TempID IC20: Not responding"
            print(msg)
        else:
            err = self.onewire.write_byte(self.port, self.ROM_MATCH)
            block = []
            for i in range(8):
                block.append(serial_number & 0xFF)
                serial_number >>= 8
            self.onewire.write_block(self.port, block)
            return 0

    def read_temp(self, serial_number):
        err = self.access(serial_number)
        err = self.onewire.write_byte(self.port, self.CONVERT_TEMP)
        time.sleep(0.8)
        err = self.access(serial_number)
        err = self.onewire.write_byte(self.port, self.READ_SCRATCHPAD)
        data = self.onewire.read_block(self.port, 9)
        temp = (data[1] << 8) | (data[0])
        if(temp & 0x1000):
            temp = -0x10000 + temp
        temp = temp/16.0
        return temp


##------------------------------------------------------------------------------
## main
##------------------------------------------------------------------------------
if __name__ == "__main__":

    FAMILY_CODE = 0x28

    # Get the IP, user and password for the ELMA crate from either user input,
    # or ei2cdefine.py
    if (ei2cdefine.ENABLED):
        ip   = ei2cdefine.HNAME
        user = ei2cdefine.USER
        pwd  = ei2cdefine.PWD
    else:
        ip   = raw_input("ELMA crate IP or hostname : ")
        user = raw_input("ELMA crate user name      : ")
        pwd  = raw_input("ELMA crate password       : ")
    elma = EI2C(ip, user, pwd)
    elma.open()

    # Ask for slot number
    while 1:
        try:
            slot = raw_input("Slot no.: ")
            slot = int(slot)
            break
        except TypeError as e:
            print("Please input a decimal slot number.")
        except SlotError as e:
            print(e.strerror)
        except KeyboardInterrupt:
            sys.exit();
        except:
            print("Unexpected error: ", sys.exc_info()[0])

    # Print which board is detected in the selected slot
    bid = elma.get_bid(slot)

    # CONV-TTL-BLO gateware changed memory map in v3.0 and golden v0.2
    gwvers = elma.read(slot, 0x04) & 0xff
    baseaddr = 0x200
    if (bid == "TBLO"):
        if ((gwvers >= 0x10) and (gwvers <= 0x22)):
            baseaddr = 0x080

    # Create onewire and thermometer objects
    onewire = COpenCoresOneWire(elma, slot, baseaddr, 99, 19)
    ds18b20 = CDS18B20(onewire, 0)

    # Reading of unique ID
    unique_id = ds18b20.read_serial_number()
    if(unique_id == -1):
        msg = "ERROR: TempID IC20: Unable to read 1-wire thermometer"
        print(msg)
    else:
        print("Unique ID: %.12X" % (unique_id))

    # Reading of temperature
    temp = ds18b20.read_temp(unique_id)
    print("Current temperature: %3.3f" % temp)

    # Cheking if received values are reasonable
    if temp < 10.0 or temp > 50.0:
        msg = "ERROR: TempID IC20: Temperature: %d out of range[10 .. 50oC]" % (temp)
        print(msg)

    if((unique_id & 0xFF) != FAMILY_CODE):
        family_code = unique_id & 0xFF
        msg = "ERROR: TempID IC20: Invalid family code: 0x%.8X" % (family_code)
        print(msg)

    elma.close()

