#===============================================================================
# CERN (BE-CO-HT)
# ELMA EI2C class
#===============================================================================
# author: Theodor Stana (t.stana@cern.ch)
#
# date of creation: 2013-08-13
#
# version: 1.0
#
# description:
#     This module defines the EI2C class, which defines an ELMA I2C object
#     corresponding to one ELMA crate. The methods of the object can be used
#     for opening a Telnet connection to the crate and communicating to boards
#     connected to the two serial lines (SERCLK and SERDAT) on the VME P1
#     connector. The communication is done via writereg, writemregs and readreg
#     commands.
#
#===============================================================================
# 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:
#    2013-08-13    Theodor Stana     t.stana@cern.ch     File created
#    2014-10-29    Theodor Stana     t.stana@cern.ch     Accounting for Telnet
#                                                        echoes
#===============================================================================
#  TODO: - 
#===============================================================================

import sys
import socket
from ei2cexcept import *
import ei2cdefine
import binascii

class EI2C:

  """Access a VME crate through Telnet."""

  def __init__(self, ip, user, pwd):

    """Initialize EI2C class.

    The arguments to init the class are mandatory:
    ip   -- IP address of VME crate
    user -- username to login to VME crate
    pwd  -- password to login to VME crate
    """

    self.ip   = ip
    self.user = user
    self.pwd  = pwd
    self.write_cnt = 0
    self.read_cnt  = 0

    if (ei2cdefine.DUMP):
        self.f = open("dump.txt", 'w')

  def open(self):
    s = socket.socket(socket.AF_INET, socket.SOCK_STREAM);
    try:
      s.connect((self.ip, 23))
      s.recv(30)
    except (socket.gaierror, socket.error) as e:
      raise BadHostnameError
    s.send(self.user + "\r\n")
    if "password" not in s.recv(30):
      raise BadUsernameError
    s.send(self.pwd + "\r\n")
    if "Wrong password" in s.recv(30):
      raise BadPasswordError
    self.handle = s

    # get crate firmware version, to apply proper address in readreg/writereg
    self.handle.send("version\r\n")
    ver = self.handle.recv(256)
    pos = ver.find("Software version")
    if (pos == -1):
      print("Unexpected response from \"version\" command, exiting...")
      self.close()
      sys.exit(2)
    ver = float(ver[pos+17:pos+21])
    self.ver = ver

    # Print connection information
    print("Successfully connected to " + self.ip)
    print("SysMon firmware v%1.2f" % self.ver)

  def close(self):
    self.handle.close()
    if (ei2cdefine.DUMP):
        self.f.close()

  def write(self, slot, addr, val):
    self.write_cnt += 1
    cmd = "writereg %d %x %x\r\n" % (slot, addr, val)
    if (self.ver < 2.27):
      addr = addr/4 + 1
      cmd = "writereg %d %d %x\r\n" % (slot, addr, val)
    if (ei2cdefine.DUMP):
      self.f.write(cmd);
    self.handle.send(cmd)
    resp = "ERROR"
    while ("ERROR" in resp):
      resp = self._strip_resp(self.handle.recv(30))

  def read(self, slot, addr):
    self.read_cnt += 1
    cmd = "readreg %d %x\r\n" % (slot, addr)
    if (self.ver < 2.27):
      addr = addr/4 + 1
      cmd = "readreg %d %d\r\n" % (slot, addr)
    if (ei2cdefine.DUMP):
      self.f.write(cmd);
    self.handle.send(cmd)
    resp = "ERROR"
    while ("ERROR" in resp):
      resp = self._strip_resp(self.handle.recv(30))
    return int(resp, 16)

  def writemregs(self, slot, addr, val):
    self.write_cnt += 1
    cmd = "writemregs %d %x %s\r\n" % (slot, addr, ' '.join(format(b,'x') for b in val))
    if (self.ver < 2.27):
      addr = addr/4 + 1
      cmd = "writemregs %d %d %s\r\n" % (slot, addr, ' '.join(format(b,'x') for b in val))
    if (ei2cdefine.DUMP):
      self.f.write(cmd);
    #print cmd
    self.handle.send(cmd)
    resp = "ERROR"
    while ("ERROR" in resp):
      resp = self._strip_resp(self.handle.recv(30))

  def readmregs(self, slot, addr, nrregs):
    self.read_cnt += 1
    cmd = "readmregs %d %x %d\r\n" % (slot, addr, nrregs)
    if (self.ver < 2.27):
      addr = addr/4 + 1
      cmd = "readmregs %d %d %d\r\n" % (slot, addr, nrregs)
    if (ei2cdefine.DUMP):
      self.f.write(cmd);
    self.handle.send(cmd)
    resp = "ERROR"
    while ("ERROR" in resp):
      resp = self._strip_resp(self.handle.recv(30))
    return resp

  def get_ver(self):
    return self.ver

  def print_bid(self, slot):
    bid = self.read(slot, 0x000);
    bid = binascii.unhexlify("%s" % "{0:x}".format(bid))
    if (bid == "TBLO"):
      print("CONV-TTL-BLO found in slot %d" % slot)
    elif (bid == "T485"):
      print("CONV-TTL-RS485 found in slot %d" % slot)

  def _strip_resp(self, msg):

    """Strip useful message from SysMon response.

    The response string from the SysMon is stripped of unnecessary characters
    and a "useful" string is returned. In case one of the error messages appear,
    the function raises one of the EI2C exceptions."""

    while (1):
      try:
        #print msg.splitlines()
        msg = msg.splitlines()[1]
        if (ei2cdefine.DUMP):
          self.f.write(msg + "\n")
        break
      except IndexError:
        if (ei2cdefine.DUMP):
          self.f.write("ERROR\n")
        return "ERROR"

    if "Not Ack" in msg:
      raise NAckError

    if "Invalid register number!" in msg:
      raise RegNumberError

    if "Bad command!" in msg:
      raise InvalidCmdError

    if "I2C Timer expired" in msg:
      raise TimerExpiredError

    if "Invalid slot number!" in msg:
      raise SlotError

    if "Read Data:" in msg:
      msg = msg.split(" ")[3]

    return msg
