##_______________________________________________________________________________________________
##
##                                     CONV-TTL-RS485 PTS
##
##                                         CERN,BE/CO-HT
##_______________________________________________________________________________________________
##
##------------------------------------------------------------------------------------------------
##
##                                  CONV-TTL-RS485 TEMPID test
##
##------------------------------------------------------------------------------------------------
##
## Description  Testing of the DS18B20 thermometer chip (IC22) on the CONV-TTL-RS-485 . The
##              gateware loaded to the FPGA implements the interface for the communication between
##              the SKT bus and the one wire of the DS18B20; this interface a one wire Wishbone
##              master, running on a 20 MHz system clock.
##
##              The family code of the DS18B20 is 0x28, predefined in the part number.
##
##              The test reads the unique ID and the temperature and checks if the received
##              values are within reasonable limits.
##
## Authors      Julian Lewis (Julian.Lewis@cern.ch)
##              Theodor Stana (t.stana@cern.ch)
## Website      http://www.ohwr.org/projects/conv-ttl-rs485
## Date         31/10/2014
##------------------------------------------------------------------------------------------------
##
##------------------------------------------------------------------------------------------------
##                               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
##-------------------------------------------------------------------------------------------------

##-------------------------------------------------------------------------------------------------
##                                            Import
##-------------------------------------------------------------------------------------------------

# Import system modules

import sys
sys.path.append("pyts")
import time
import os

# Import common modules

from ctypes import *
from ptsexcept import *
from vv_pts import *

from ptsdefine import *

##-------------------------------------------------------------------------------------------------
##                                         One Wire 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.vv_write(self.base + addr,val)

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

    def __init__(self, bus, base, clk_div_nor, clk_div_ovd):
        self.bus = bus
        self.base = base
        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 IC22: Not responding"
                raise PtsError(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 IC22: Not responding"
                raise PtsError(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 IC22: Not responding"
            raise PtsError(msg)
        else:
            err = self.onewire.write_byte(self.port, self.ROM_READ)
            if err != 0:
                msg = "ERROR: TempID IC22: Write failed"
                raise PtsError(msg)

            serial_number = 0
            for i in range(8):
                serial_number |= self.onewire.read_byte(self.port) << (i*8)
            if (self.crc(serial_number)):
                msg = "ERROR: TempID IC22: Checksum calculation failed"
                raise PtsError(msg)

        return serial_number

    def access(self, serial_number):
        if (1 != self.onewire.reset(self.port)):
            msg = "ERROR: TempID IC22: Not responding"
            raise PtsError(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

    def crc(self, val):
        ret = 0
        for i in range(64):
            if ((ret & 0x01) ^ (val & 0x01)):
                ret >>= 1
                ret ^= 0x8c
            else:
                ret >>= 1
            val >>= 1
        return ret

##-------------------------------------------------------------------------------------------------
##                                             main                                              --
##-------------------------------------------------------------------------------------------------

def main(bus,tname,inf,log):

    """
    tests : Thermometer IC22 and unique ID
    uses  : pts.bit and therm_id.py
    """

    FAMILY_CODE = 0x28

    pel = PTS_ERROR_LOGGER(inf,log)

    try:
        # Create bus objects
        onewire = COpenCoresOneWire(bus, TEMP_1WIRE_BASE, 99, 19)
        ds18b20 = CDS18B20(onewire, 0)

        # Reading of unique ID
        unique_id = ds18b20.read_serial_number()
        family_code = unique_id & 0xff
        if (unique_id == -1):
            msg = "ERROR: TempID IC22: Unable to read 1-wire thermometer"
            pel.set(msg)
        else:
            inf.write("Unique ID: %016X\n" % (unique_id))

        # Reading of temperature
        temp = ds18b20.read_temp(unique_id)
        inf.write("Current temperature: %3.3f\n" % temp)

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

        if (family_code != FAMILY_CODE):
            msg = "ERROR: TempID IC22: Invalid family code (0x%02X)\n" % (family_code, FAMILY_CODE)
            pel.set(msg)

        return pel.get()

    except BusException, e:
        raise PtsError("SKT Exception: %s" % (e))

    except BusWarning, e:
        raise PtsError("SKT Warning: %s" % (e))

    #finally:
    #    return pel.get()
