# the main imports
import pygame, random

# some important functions

# for creating pipe rectangles which will store the pipe images
def create_pipe():
    height = random.choice(pipe_height)
    bottom_pipe = pipe_surface.get_rect(midtop = (350, height))
    top_pipe = pipe_surface.get_rect(midbottom = (350, height-150))
    return bottom_pipe, top_pipe

# to move pipes from right to left of the screen
def move_pipe(pipes):
    for pipe in pipes:
        pipe.centerx -= 2.5
    return pipes

# drawing the pipes with rectanges
def draw_pipes(pipes):
    for pipe in pipes:
        # only the bottom pipe can have the bottom point going below the screen
        if pipe.bottom >= 512:
            screen.blit(pipe_surface, pipe)
        else:
            # flip the surface using inbuilt function
            # takes the surface and two bool values corresponding to the flip across x axis and flip through y axis
            flip_pipe = pygame.transform.flip(pipe_surface, False, True)
            screen.blit(flip_pipe, pipe)

# detecting collisions
def check_collision(pipes):
    for pipe in pipes:
        # if bird rectangle collides with any of the pipe rectangle
        if bird_rect.colliderect(pipe):
            collision_sound.play()
            return False
    #we also want collision when the bird is too high or too low
    if bird_rect.bottom >= 450 or bird_rect.top <=-50:
        return False
    return True

# bird movement animation
def rotate_bird(bird):
    # the first is the bird surface you want to rotate
    # the angle of the rotation - value is added so that is rotated in the right direction
    # to see the rotation is more visible it is multiplied by 3
    # the scale is not used and is kept the default i.e.1
    # the bird_movement will control where the angle of the rotation will be
    new_bird = pygame.transform.rotozoom(bird, -bird_movement*3, 1)
    return new_bird

# flap animation
def bird_animation():
    new_bird = bird_frames[bird_index]
    new_bird_rect = new_bird.get_rect(center = (50, bird_rect.centery))
    return new_bird, new_bird_rect

# base movement from right to left
def base_animation():
    # just one image was not doing the right thing, it was moving to the left and
    #going off the screen and then coming back to make up for that
    # we add another image at the end of the first one
    screen.blit(base_surface, (base_x_position,450))
    screen.blit(base_surface, (base_x_position+288 , 450))

# see the score
def display_score(game_state):
    # if game is running
    if game_state == 'current_game':
        # now we render the font in the screen
        # first the text to be displayed
        # anti aliasing - characters of the characters will be smooth edges if it is set to true
        # color of the text
        score_surface = game_font.render(str(int(score)), True, (255,255,255))
        score_rect = score_surface.get_rect(center = (144,50))
        screen.blit(score_surface, score_rect)

    # if game has ended
    if game_state == 'game_over':
        # normal score
        score_surface = game_font.render(f'Score:{int(score)}', True, (255,255,255))
        score_rect = score_surface.get_rect(center = (144,50))
        screen.blit(score_surface, score_rect)

        #highest score
        high_score_surface = game_font.render(f'High Score:{int(high_score)}', True, (255,255,255))
        high_score_rect = high_score_surface.get_rect(center = (144,425))
        screen.blit(high_score_surface, high_score_rect)

# updating the score
def update_score(score, high_score):
    if score> high_score:
        high_score = score
    return high_score

# initializations

# we initialize the mixer module to get rid of the delay in the processing of the sound
# when we want to play the sound
pygame.mixer.pre_init(frequency = 44100, size = 16, channels = 1, buffer = 512)

# initialize the module
pygame.init()

# variable initializations and main screen display

# the main display surface with its size
screen = pygame.display.set_mode((288, 512))
#module to make the frame rate even
clock = pygame.time.Clock()
# game font
game_font = pygame.font.Font('04B_19.TTF', 40)

#convert at the end makes the image a little
#more friendly for pygame to run and it will be faster

# the background image for the game
bg_surface = pygame.image.load('assets/background-day.png').convert()
# the base image for the background (the little part at the bottom)
base_surface = pygame.image.load('assets/base.png').convert()

# game over surface
game_over_surface = pygame.image.load('assets/gameover.png').convert_alpha()
game_over_rect = game_over_surface.get_rect(center = (144,256))

# for the birds with their different flap positions
bird_downflap = pygame.image.load('assets/bluebird-downflap.png').convert_alpha()
bird_midflap = pygame.image.load('assets/bluebird-midflap.png').convert_alpha()
bird_upflap = pygame.image.load('assets/bluebird-upflap.png').convert_alpha()
bird_frames = [bird_downflap, bird_midflap, bird_upflap]
bird_index = 0
bird_surface = bird_frames[bird_index]
bird_rect = bird_surface.get_rect(center=(50,256))

flap_sound = pygame.mixer.Sound('sound/sfx_wing.wav')
collision_sound = pygame.mixer.Sound('sound/sfx_hit.wav')
score_sound = pygame.mixer.Sound('sound/sfx_point.wav')
score_sound_countdown = 100

# if I wanted to make a bigger screen and fit the smaller image in it, I would have to
# rescale the image to be twice of what it is
# basically uncomment this if you make the display screen any bigger
# bg_surface = pygame.transform.scale2x(bg_surface)

BIRDFLAP = pygame.USEREVENT +1
pygame.time.set_timer(BIRDFLAP, 200)

# for pipes
pipe_surface = pygame.image.load('assets/pipe-green.png').convert()

# we are going to create a new pipe list which will contain a number of rectangles
# containing the pipes
pipe_list = []
# user event is not triggered by a button but is triggered by a timer
SPAWNPIPE = pygame.USEREVENT
# the timer
pygame.time.set_timer(SPAWNPIPE, 1200)
# for random height
pipe_height = [200,300,400,250,350]

run = True
# for the base image position, to make it move to the left
base_x_position = 0
# for the bird to fall down
gravity = 0.1
bird_movement = 0
# the game state
game_active = True
# for scores
score = 0
high_score = 0

# the main game loop

while run:
    # checks for any event happening and gets it from the event queue
    for event in pygame.event.get():
        # if the event is pressing the X button on top of the window
        if event.type == pygame.QUIT:
                #get out of the while loop
                run = False

        if event.type == pygame.KEYDOWN:
            if event.key == pygame.K_SPACE and game_active:
                bird_movement = 0
                bird_movement -= 4
                flap_sound.play()

            if event.key == pygame.K_SPACE and game_active==False:
                # get the game to start again
                game_active = True
                # clear the previous game pipes
                pipe_list.clear()
                # set the bird to the original default position
                bird_rect.center = (50, 256)
                # set the movement to be default too
                bird_movement = 0
                # set the score to be 0
                score = 0

        if event.type == SPAWNPIPE:
            # a function to make rectangles with pipes and put them into list
            pipe_list.extend(create_pipe())

        if event.type == BIRDFLAP:
            if bird_index<2:
                bird_index += 1
            else:
                bird_index = 0
            bird_surface, bird_rect = bird_animation()

    # set the frame rate
    clock.tick(120)

    # a function to put any image on the surface
    # takes the image, and the position on where to put the image on
    # 0,0 is the top left corner of the screen
    screen.blit(bg_surface, (0,0))

    # if game is running
    if game_active == True:
        # make the bird fall
        bird_movement += gravity
        # bird animation
        rotated_bird = rotate_bird(bird_surface)
        bird_rect.centery += bird_movement
        #place the bird 0n the surface
        screen.blit(rotated_bird, bird_rect)
        #check for collisions
        game_active = check_collision(pipe_list)
        # for pipe movement
        pipe_list = move_pipe(pipe_list)
        draw_pipes(pipe_list)
        # increase the score
        score += .01
        # sound for score
        score_sound_countdown -= 1
        # the main score sound
        if score_sound_countdown <= 0:
            score_sound.play()
            score_sound_countdown = 100
        display_score('current_game')

    # game has ended and the game over screen is shown
    else:
        screen.blit(game_over_surface, game_over_rect)
        high_score = update_score(score, high_score)
        display_score('game_over')

    # for the base animation to constantly move backwards we implement a funtion
    base_animation()
    # make the base_x_position to go -1 everytime the loop is called again
    base_x_position -= 1
    # if it goes out of the screen then reset the value
    if base_x_position<= -288:
        base_x_position = 0

    # update the display window and add anything that was done in the main game loop
    pygame.display.update()

# if the main loop is over then quit the pygame module
# without this the X button on top of the window will not work
pygame.quit()


__metaclass__ = type

__all__ = [
    'VPOTExport',
    ]

from zope.interface import implementer

from lp.translations.interfaces.vpotexport import IVPOTExport

# XXX JeroenVermeulen 2009-07-30 bug=406540: This should be an
# ITranslationMessageData.
@implementer(IVPOTExport)
class VPOTExport:
    """Present Rosetta POT files in a form suitable for exporting them
    efficiently.
    """

    def __init__(self, potemplate, tti, potmsgset, singular, plural):
        self.potemplate = potemplate
        self.sequence = tti.sequence
        self.potmsgset = potmsgset

        if singular:
            self.msgid_singular = singular.msgid
        else:
            self.msgid_singular = None

        if plural:
            self.msgid_plural = plural.msgid
        else:
            self.msgid_plural = None

        self.template_header = self.potemplate.header
        self.context = self.potmsgset.context
        self.comment = self.potmsgset.commenttext
        self.source_comment = self.potmsgset.sourcecomment
        self.file_references = self.potmsgset.filereferences
        self.flags_comment = self.potmsgset.flagscomment


"""
The ONNX to keras converter module
"""

from tensorflow import keras
import logging
import inspect
import collections
from onnx import numpy_helper

from .layers import AVAILABLE_CONVERTERS

def onnx_node_attributes_to_dict(args):
    """
    Parse ONNX attributes to Python dictionary
    :param args: ONNX attributes object
    :return: Python dictionary
    """
    def onnx_attribute_to_dict(onnx_attr):
        """
        Parse ONNX attribute
        :param onnx_attr: ONNX attribute
        :return: Python data type
        """
        if onnx_attr.HasField('t'):
            return numpy_helper.to_array(getattr(onnx_attr, 't'))

        for attr_type in ['f', 'i', 's']:
            if onnx_attr.HasField(attr_type):
                return getattr(onnx_attr, attr_type)

        for attr_type in ['floats', 'ints', 'strings']:
            if getattr(onnx_attr, attr_type):
                return list(getattr(onnx_attr, attr_type))
    return {arg.name: onnx_attribute_to_dict(arg) for arg in args}

def onnx_to_keras(onnx_model, input_names,
                  input_shapes=None, name_policy=None, verbose=True, change_ordering=False):
    """
    Convert ONNX graph to Keras model format
    :param onnx_model: loaded ONNX model
    :param input_names: list with input names
    :param input_shapes: override input shapes (experimental)
    :param name_policy: override layer names. None, "short" or "renumerate" (experimental)
    :param verbose: verbose output
    :param change_ordering: change ordering to HWC (experimental)
    :return: Keras model
    """
    # Use channels first format by default.
    keras_fmt = keras.backend.image_data_format()
    keras.backend.set_image_data_format('channels_first')

    if verbose:
        logging.basicConfig(level=logging.DEBUG)

    logger = logging.getLogger('onnx2keras')

    logger.info('Converter is called.')

    onnx_weights = onnx_model.graph.initializer
    onnx_inputs = onnx_model.graph.input
    onnx_outputs = [i.name for i in onnx_model.graph.output]
    onnx_nodes = onnx_model.graph.node

    logger.debug('List input shapes:')
    logger.debug(input_shapes)

    logger.debug('List inputs:')
    for i, input in enumerate(onnx_inputs):
        logger.debug('Input {0} -> {1}.'.format(i, input.name))

    logger.debug('List outputs:')
    for i, output in enumerate(onnx_outputs):
        logger.debug('Output {0} -> {1}.'.format(i, output))

    logger.debug('Gathering weights to dictionary.')
    weights = {}
    for onnx_w in onnx_weights:
        try:
            if len(onnx_w.ListFields()) < 4:
                onnx_extracted_weights_name = onnx_w.ListFields()[1][1]
            else:
                onnx_extracted_weights_name = onnx_w.ListFields()[2][1]
            weights[onnx_extracted_weights_name] = numpy_helper.to_array(onnx_w)
        except:
            onnx_extracted_weights_name = onnx_w.ListFields()[3][1]
            weights[onnx_extracted_weights_name] = numpy_helper.to_array(onnx_w)

        logger.debug('Found weight {0} with shape {1}.'.format(
                     onnx_extracted_weights_name,
                     weights[onnx_extracted_weights_name].shape))

    layers = dict()
    lambda_funcs = dict()
    keras_outputs = []
    keras_inputs = []

    for i, input_name in enumerate(input_names):
        for onnx_i in onnx_inputs:
            if onnx_i.name == input_name:
                if input_shapes:
                    input_shape = input_shapes[i]
                else:
                    input_shape = [i.dim_value for i in onnx_i.type.tensor_type.shape.dim][1:]

                layers[input_name] = keras.layers.InputLayer(
                    input_shape=input_shape, name=input_name
                ).output

                keras_inputs.append(layers[input_name])

                logger.debug('Found input {0} with shape {1}'.format(input_name, input_shape))

    # Convert every operation separable
    node_names = []
    for node_index, node in enumerate(onnx_nodes):
        node_type = node.op_type
        node_params = onnx_node_attributes_to_dict(node.attribute)

        # Add global converter info:
        node_params['change_ordering'] = change_ordering
        node_params['name_policy'] = name_policy

        node_name = str(node.output[0])
        keras_names = []
        for output_index, output in enumerate(node.output):
            if name_policy == 'short':
                keras_name = keras_name_i = str(output)[:8]
                suffix = 1
                while keras_name_i in node_names:
                    keras_name_i = keras_name + '_' + str(suffix)
                    suffix += 1
                keras_names.append(keras_name_i)
            elif name_policy == 'renumerate':
                postfix = node_index if len(node.output) == 1 else "%s_%s" % (node_index, output_index)
                keras_names.append('LAYER_%s' % postfix)
            else:
                keras_names.append(output)

        if len(node.output) != 1:
            logger.warning('Trying to convert multi-output node')
            node_params['_outputs'] = list(node.output)
            node_names.extend(keras_names)
        else:
            keras_names = keras_names[0]
            node_names.append(keras_names)

        logger.debug('######')
        logger.debug('...')
        logger.debug('Converting ONNX operation')
        logger.debug('type: %s', node_type)
        logger.debug('node_name: %s', node_name)
        logger.debug('node_params: %s', node_params)
        logger.debug('...')

        logger.debug('Check if all inputs are available:')
        if len(node.input) == 0 and node_type != 'Constant':
            raise AttributeError('Operation doesn\'t have an input. Aborting.')

        for i, node_input in enumerate(node.input):
            logger.debug('Check input %i (name %s).', i, node_input)
            if node_input not in layers:
                logger.debug('The input not found in layers / model inputs.')

                if node_input in weights:
                    logger.debug('Found in weights, add as a numpy constant.')
                    layers[node_input] = weights[node_input]
                else:
                    raise AttributeError('Current node is not in weights / model inputs / layers.')
        else:
            logger.debug('... found all, continue')

        keras.backend.set_image_data_format('channels_first')
        AVAILABLE_CONVERTERS[node_type](
            node,
            node_params,
            layers,
            lambda_funcs,
            node_name,
            keras_names
        )
        if isinstance(keras_names, list):
            keras_names = keras_names[0]

        try:
            logger.debug('Output TF Layer -> ' + str(layers[keras_names]))
        except KeyError:
            pass

    # Check for terminal nodes
    for layer in onnx_outputs:
        if layer in layers:
            keras_outputs.append(layers[layer])

    # Create model
    model = keras.models.Model(inputs=keras_inputs, outputs=keras_outputs)

    if change_ordering:
        change_ord_axes_map = {
            3: 2,
            1: 3,
            -1: 1
        }

        import numpy as np
        conf = model.get_config()

        for layer in conf['layers']:
            if layer['config'] and 'shared_axes' in layer['config']:
                # TODO: check axes first (if it's not 4D tensor)
                layer['config']['shared_axes'] = [1, 2]

            if layer['config'] and 'batch_input_shape' in layer['config']:
                layer['config']['batch_input_shape'] = \
                    tuple(np.reshape(np.array(
                        [
                            [None] +
                            list(layer['config']['batch_input_shape'][2:][:]) +
                            [layer['config']['batch_input_shape'][1]]
                        ]), -1
                    ))
            if layer['config'] and 'target_shape' in layer['config']:
                if len(list(layer['config']['target_shape'][1:][:])) > 0:
                    layer['config']['target_shape'] = \
                        tuple(np.reshape(np.array(
                                list(layer['config']['target_shape'][1:]) +
                                [layer['config']['target_shape'][0]]
                            ), -1),)

            if layer['config'] and 'data_format' in layer['config']:
                layer['config']['data_format'] = 'channels_last'
            if layer['config'] and 'axis' in layer['config']:
                axis = layer['config']['axis']
                # BatchNorm wrap axis with ListWrapper instead single INT value
                if isinstance(axis, (tuple, list)):
                    axis = axis[0]
                layer['config']['axis'] = change_ord_axes_map.get(axis, layer['config']['axis'])

        for layer in conf['layers']:
            if 'function' in layer['config'] and layer['config']['function'][1] is not None:
                kerasf = list(layer['config']['function'])
                dargs = list(kerasf[1])
                func = lambda_funcs.get(layer['name'])

                if func:
                    # ReduceSum operation has 'axis' param as array of ints. When onnx uses ReduceSum
                    # to reproduce SoftMax - dargs become something like [[1]] (list of lists)
                    # that why we handle collections.Iterable
                    if len(dargs) > 1 or isinstance(dargs[0], (tuple, list)):
                        params = inspect.signature(func).parameters
                        i = list(params.keys()).index('axes') if ('axes' in params) else -1

                        if i > 0:
                            i -= 1
                            axes = list(range(len(dargs[i].shape)))
                            axes = axes[0:1] + axes[2:] + axes[1:2]
                            dargs[i] = np.transpose(dargs[i], axes)

                        i = list(params.keys()).index('axis') if ('axis' in params) else -1

                        if i > 0:
                            i -= 1
                            axis = np.array(dargs[i])
                            axes_map = np.array([0, 3, 1, 2])
                            # to list because some tf operations check only for core python types (e.g tf.norm)
                            dargs[i] = axes_map[axis].tolist()
                    else:
                        # if map exits will change else will remain the same
                        dargs[0] = change_ord_axes_map.get(dargs[0], dargs[0])

                kerasf[1] = tuple(dargs)
                layer['config']['function'] = tuple(kerasf)

        keras.backend.set_image_data_format('channels_last')
        model_tf_ordering = keras.models.Model.from_config(conf)

        for dst_layer, src_layer, conf in zip(model_tf_ordering.layers, model.layers, conf['layers']):
            W = src_layer.get_weights()
            # TODO: check axes first (if it's not 4D tensor)
            if conf['config'] and 'shared_axes' in conf['config']:
                W[0] = W[0].transpose(1, 2, 0)
            dst_layer.set_weights(W)

        model = model_tf_ordering

    keras.backend.set_image_data_format(keras_fmt)

    return model


from typing import List, Optional
from dataclasses import dataclass
from enum import Enum, auto
import logging
import bisect
from statistics import mean
import time

from binhoHostAdapter import binhoHostAdapter
import binhoUtilities

logger = logging.getLogger(__name__)

# noinspection PyCallByClass
class AD7745:
    I2C_INDEX = 0

    WRITE_ADDR = 0x90
    READ_ADDR = 0x91

    STATUS_ADDR = 0x00
    STATUS_EXCERR = 1 << 3
    STATUS_RDY = 1 << 2
    STATUS_RDYVT = 1 << 1
    STATUS_RDYCAP = 1 << 0

    CAP_DATA_H_ADDR = 0x01
    CAP_DATA_M_ADDR = 0x02
    CAP_DATA_L_ADDR = 0x03

    VT_DATA_H_ADDR = 0x04
    VT_DATA_M_ADDR = 0x05
    VT_DATA_L_ADDR = 0x06

    CAP_SETUP_ADDR = 0x07
    CAP_SETUP_CAPEN = 1 << 7
    CAP_SETUP_CIN2 = 1 << 6
    CAP_SETUP_CAPDIFF = 1 << 5
    CAP_SETUP_CAPCHOP = 1

    VT_SETUP_ADDR = 0x08
    VT_SETUP_VTEN = 1 << 7
    VT_SETUP_VTMD1 = 1 << 6
    VT_SETUP_VTMD0 = 1 << 5
    VT_SETUP_EXTREF = 1 << 4
    VT_SETUP_VTSHORT = 1 << 1
    VT_SETUP_VTCHOP = 1

    EXC_SETUP_ADDR = 0x09
    EXC_SETUP_CLKCTRL = 1 << 7
    EXC_SETUP_EXCON = 1 << 6
    EXC_SETUP_EXCB = 1 << 5
    EXC_SETUP_UEXCB = 1 << 4
    EXC_SETUP_EXCA = 1 << 3
    EXC_SETUP_UEXCA = 1 << 2
    EXC_SETUP_EXCLVL1 = 1 << 1
    EXC_SETUP_EXCLVL0 = 1

    CONFIG_ADDR = 0x0A
    CONFIG_VTFS1 = 1 << 7
    CONFIG_VTFS0 = 1 << 6
    CONFIG_CAPFS2 = 1 << 5
    CONFIG_CAPFS1 = 1 << 4
    CONFIG_CAPFS0 = 1 << 3
    CONFIG_MD2 = 1 << 2
    CONFIG_MD1 = 1 << 1
    CONFIG_MD0 = 1

    CAP_DAC_A_ADDR = 0x0B
    CAP_DAC_A_DACAENA = 1 << 7
    CAP_DAC_A_DACA7bit = 0x7F

    CAP_DAC_B_ADDR = 0x0C
    CAP_DAC_B_DACBENB = 1 << 7
    CAP_DAC_B_DACB7bit = 0x7F

    CAP_OFFSET_H_ADDR = 0x0D
    CAP_OFFSET_L_ADDR = 0x0E

    CAP_GRAIN_H_ADDR = 0x0F
    CAP_GRAIN_L_ADDR = 0x10

    VOLT_GRAIN_H_ADDR = 0x11
    VOLT_GRAIN_L_ADDR = 0x12

    class CapInput(Enum):
        CAP1_EXC1 = auto()
        CAP2_EXC2 = auto()
        CAP_DIFF_CIN1 = auto()
        CAP_DIFF_CIN2 = auto()

    class CapFilter(Enum):
        # Note frequencies listed as -3dB point
        CONV87Hz = auto()
        CONV79Hz = auto()
        CONV44Hz = auto()
        CONV22Hz = auto()
        CONV14Hz = auto()
        CONV11Hz = auto()
        CONV9Hz = auto()
        CONV8Hz = auto()

    @dataclass
    class vt_setup_reg:
        VTEN: bool = False
        VTMD1: bool = False
        VTMD0: bool = False
        EXTREF: bool = False
        VTSHORT: bool = False
        VTCHOP: bool = False

    @dataclass
    class cap_setup_reg:
        CAPEN: bool = False
        CIN2: bool = False
        CAPDIFF: bool = False
        CAPCHOP: bool = False

    @dataclass
    class exc_setup_reg:
        CLKCTRL: bool = False
        EXCON: bool = False
        EXCB: bool = False
        UEXCB: bool = False
        EXCA: bool = False
        UEXCA: bool = False
        EXCLVL1: bool = False
        EXCLVL0: bool = False

    @dataclass
    class configuration_reg:
        VTFS1: bool = False
        VTFS0: bool = False
        CAPFS2: bool = False
        CAPFS1: bool = False
        CAPFS0: bool = False
        MD2: bool = False
        MD1: bool = False
        MD0: bool = False

    @dataclass
    class cap_daca_reg:
        DACAENA: bool = False
        DACA7bit: int = 0

    @dataclass
    class cap_dacb_reg:
        DACBENB: bool = False
        DACB7bit: int = 0

    @dataclass
    class status_reg:
        EXCERR: bool = False
        RDY: bool = False
        RDYVT: bool = False
        RDYCAP: bool = False

    def __init__(self, binho: binhoHostAdapter.binhoHostAdapter) -> None:
        self.i2cbus = binho
        self.i2cbus.setOperationMode(AD7745.I2C_INDEX, "I2C")
        self.i2cbus.setPullUpStateI2C(0, "EN")
        self.set_clock(400000)
        self.cont_conv = True

        self._cap_dac_lsb: Optional[float] = None

        # Disable voltage channel on startup
        self.vt_setup = AD7745.vt_setup_reg(VTEN=False)
        # Configure for idle, slow read time
        self.configuration = AD7745.configuration_reg(
            VTFS1=True,
            VTFS0=True,
            CAPFS0=True,
            CAPFS1=True,
            CAPFS2=True,
            MD0=False,
            MD1=False,
            MD2=False,
        )

    @staticmethod
    def _check_device_success(ret_str: str) -> None:
        """
        Check whether the operation is successful or not.
        '-OK' -> success
        '-NG' -> fail

        :param ret_str: Binho return string
        :return: None
        """
        if ret_str == "-OK":
            return
        elif ret_str == "-NG":
            raise RuntimeError("Binho returned NG!")
        else:
            raise RuntimeError("Binho returned Unknown command!")

    def write_byte(self, register: int, value: int) -> None:
        """
        Write byte to AD7745

        :param register: Register address to write to
        :param value: Value to write to register
        :return: None
        """
        self._check_device_success(
            self.i2cbus.startI2C(AD7745.I2C_INDEX, AD7745.WRITE_ADDR)
        )
        self._check_device_success(self.i2cbus.writeByteI2C(AD7745.I2C_INDEX, register))
        self._check_device_success(self.i2cbus.writeByteI2C(AD7745.I2C_INDEX, value))
        self._check_device_success(self.i2cbus.endI2C(0, False))

    def set_clock(self, freq: int) -> None:
        """
        Set Binho I2C frequency

        :param freq: I2C Frequency, 0 to 400kHz
        :return: None
        """
        if not 0 <= freq <= 400000:
            raise ValueError(f"Invalid Frequency {freq}, allowed range 0-400khz")
        self._check_device_success(self.i2cbus.setClockI2C(AD7745.I2C_INDEX, freq))

    def get_clock(self) -> int:
        """
        Get the Binho I2C frequency

        :return: current Binho I2C frequency
        """
        read_string = self.i2cbus.getCLockI2C(AD7745.I2C_INDEX)
        if read_string[1:3] == "NG":
            raise RuntimeError("Binho returned NG!")
        return int(read_string[12:], 16)

    def read_byte(self, addr: int, byte_len: int = 1) -> List[int]:
        """
        Read byte from register on Binho

        :param addr: Register address to read from
        :param byte_len: bytes to read, from sequential address
        :return:
        """
        self._check_device_success(
            self.i2cbus.startI2C(AD7745.I2C_INDEX, AD7745.WRITE_ADDR)
        )
        self._check_device_success(self.i2cbus.writeByteI2C(AD7745.I2C_INDEX, addr))
        self._check_device_success(self.i2cbus.endI2C(AD7745.I2C_INDEX, True))

        read_string = self.i2cbus.readBytesI2C(
            AD7745.I2C_INDEX, AD7745.READ_ADDR, byte_len
        )
        if read_string[1:3] == "NG":
            raise RuntimeError("Binho returned NG!")
        return [int(i, 0) for i in read_string[10:].split(" ")]

    @property
    def status(self) -> "AD7745.status_reg":
        """
        STATUS REGISTER: Address Pointer 0x00, Read Only, Default Value 0x07
            EXCERR: EXCERR = 1 indicates that the excitation output cannot be driven properly.
                The possible reason can be a short circuit or too high capacitance between the
                excitation pin and ground.
            RDY: RDY = 0 indicates that conversion on the enabled channel(s) has been finished
                and new unread data is available. If both capacitive and voltage/temperature
                channels are enabled, the RDY bit is changed to 0 after conversion on both
                channels is finished. The RDY bit returns to 1 either when data is read or
                prior to finishing the next conversion. If, for example, only the capacitive
                channel is enabled, then the RDY bit reflects the RDYCAP bit.
            RDYVT: RDYVT = 0 indicates that a conversion on the voltage/temperature channel has
                been finished and new unread data is available
            RDYCAP: RDYCAP = 0 indicates that a conversion on the capacitive channel has been
                finished and new unread data is available.

        :return: AD7745.status_reg
        """
        read_string = self.read_byte(AD7745.STATUS_ADDR, byte_len=1)[0]

        return self.status_reg(
            EXCERR=bool(read_string & AD7745.STATUS_EXCERR),
            RDY=bool(read_string & AD7745.STATUS_RDY),
            RDYVT=bool(read_string & AD7745.STATUS_RDYVT),
            RDYCAP=bool(read_string & AD7745.STATUS_RDYCAP),
        )

    @property
    def cap_data(self) -> int:
        """
        CAP DATA REGISTER: 24 Bits, Address Pointer 0x01, 0x02, 0x03, Read-Only, Default Value 0x000000

        :return: 24-Bit Cap Data value
        """
        read_data = self.read_byte(AD7745.CAP_DATA_H_ADDR, byte_len=3)
        cap_h = read_data[0]
        cap_m = read_data[1]
        cap_l = read_data[2]

        return (cap_h << 16) + (cap_m << 8) + cap_l

    @property
    def vt_data(self) -> int:
        """
        VT DATA REGISTER: 24 Bits, Address Pointer 0x04, 0x05, 0x06, Read-Only, Default Value 0x000000

        :return: 24-Bit Voltage Data value
        """
        read_data = self.read_byte(AD7745.CAP_DATA_H_ADDR, byte_len=3)
        vt_h = read_data[0]
        vt_m = read_data[1]
        vt_l = read_data[2]
        return (vt_h << 16) + (vt_m << 8) + vt_l

    @property
    def cap_setup(self) -> "AD7745.cap_setup_reg":
        """
        CAP SET-UP REGISTER : Address Pointer 0x07, Default Value 0x00
            CAPEN: CAPEN = 1 enables capacitive channel for single conversion, continuous conversion, or calibration.
            CIN2: CIN2 = 1 switches the internal multiplexer to the second capacitive input on the AD7746.
            CAPDIFF: CAPDIFF = 1 sets differential mode on the selected capacitive input.
            CAPCHOP: CAPCHOP: The CAPCHOP bit should be set to 0 for the specified capacitive channel performance.
                CAPCHOP = 1 approximately doubles the capacitive channel conversion times and slightly improves the
                capacitive channel noise performance for the longest conversion times.

        :return: AD7745.cap_setup_reg
        """
        read_data = self.read_byte(AD7745.CAP_SETUP_ADDR, byte_len=1)[0]
        return self.cap_setup_reg(
            CAPEN=bool(read_data & AD7745.CAP_SETUP_CAPEN),
            CIN2=bool(read_data & AD7745.CAP_SETUP_CIN2),
            CAPDIFF=bool(read_data & AD7745.CAP_SETUP_CAPDIFF),
            CAPCHOP=bool(read_data & AD7745.CAP_SETUP_CAPCHOP),
        )

    @cap_setup.setter
    def cap_setup(self, val: "AD7745.cap_setup_reg") -> None:
        """
        CAP SET-UP REGISTER : Address Pointer 0x07, Default Value 0x00
            CAPEN: CAPEN = 1 enables capacitive channel for single conversion, continuous conversion, or calibration.
            CIN2: CIN2 = 1 switches the internal multiplexer to the second capacitive input on the AD7746.
            CAPDIFF: CAPDIFF = 1 sets differential mode on the selected capacitive input.
            CAPCHOP: CAPCHOP: The CAPCHOP bit should be set to 0 for the specified capacitive channel performance.
                CAPCHOP = 1 approximately doubles the capacitive channel conversion times and slightly improves the
                capacitive channel noise performance for the longest conversion times.

        :param val: AD7745.cap_setup_reg to write
        :return: None
        """
        value = 0
        if val.CAPEN:
            value += AD7745.CAP_SETUP_CAPEN
        if val.CIN2:
            value += AD7745.CAP_SETUP_CIN2
        if val.CAPDIFF:
            value += AD7745.CAP_SETUP_CAPDIFF
        if val.CAPCHOP:
            value += AD7745.CAP_SETUP_CAPCHOP

        self.write_byte(AD7745.CAP_SETUP_ADDR, value)

    @property
    def vt_setup(self) -> "AD7745.vt_setup_reg":
        """
        VT SET-UP REGISTER: Address Pointer 0x08, Default Value 0x00
            VTEN: VTEN = 1 enables voltage/temperature channel for single conversion, continuous conversion,
                or calibration.
            VTMD1, VTDM0: Voltage/temperature channel input configuration.

                    ====== ====== ====================================
                    VTMD1  VTMD0               Channel Input
                    ====== ====== ====================================
                    0       0       Internal temperature sensor
                    0       1       External temperature sensor diode
                    1       0       VDD monitor
                    1       1       External voltage input (VIN)
                    ====== ====== ====================================

            EXTREF: EXTREF = 1 selects an external reference voltage connected to REFIN(+), REFIN(–) for the voltage
                input or the VDD monitor.
            EXTREF: EXTREF = 0 selects the on-chip internal reference. The internal reference must be used with the
                internal temperature sensor for proper operation.
            VTSHORT: VTSHORT = 1 internally shorts the voltage/temperature channel input for test purposes.
            VTCHOP: VTCHOP = 1 sets internal chopping on the voltage/temperature channel. The VTCHOP bit must be set to
                1 for the specified voltage/temperature channel performance.

        :return: AD7745.vt_setup_reg
        """
        read_string = self.read_byte(AD7745.VT_SETUP_ADDR, byte_len=1)[0]
        return AD7745.vt_setup_reg(
            VTEN=bool(read_string & AD7745.VT_SETUP_VTEN),
            VTMD1=bool(read_string & AD7745.VT_SETUP_VTMD1),
            VTMD0=bool(read_string & AD7745.VT_SETUP_VTMD0),
            EXTREF=bool(read_string & AD7745.VT_SETUP_EXTREF),
            VTSHORT=bool(read_string & AD7745.VT_SETUP_VTSHORT),
            VTCHOP=bool(read_string & AD7745.VT_SETUP_VTCHOP),
        )

    @vt_setup.setter
    def vt_setup(self, val: "AD7745.vt_setup_reg") -> None:
        """
        VT SET-UP REGISTER: Address Pointer 0x08, Default Value 0x00
        VTEN: VTEN = 1 enables voltage/temperature channel for single conversion, continuous conversion, or calibration.
        VTMD1, VTDM0: Voltage/temperature channel input configuration.

                    ====== ====== ====================================
                    VTMD1  VTMD0     Channel Input
                    ====== ====== ====================================
                    0      0       Internal temperature sensor
                    0      1       External temperature sensor diode
                    1      0       VDD monitor
                    1      1       External voltage input (VIN)
                    ====== ====== ====================================

        EXTREF: EXTREF = 1 selects an external reference voltage connected to REFIN(+), REFIN(–) for the voltage input
            or the VDD monitor.
        EXTREF: EXTREF = 0 selects the on-chip internal reference. The internal reference must be used with the internal
            temperature sensor for proper operation.
        VTSHORT: VTSHORT = 1 internally shorts the voltage/temperature channel input for test purposes.
        VTCHOP: VTCHOP = 1 sets internal chopping on the voltage/temperature channel. The VTCHOP bit must be set to 1
            for the specified voltage/temperature channel performance.

        :param val: AD7745.vt_setup_reg to set
        :return: None
        """
        reg_value = 0
        if val.VTEN:
            reg_value += AD7745.VT_SETUP_VTEN
        if val.VTMD1:
            reg_value += AD7745.VT_SETUP_VTMD1
        if val.VTMD0:
            reg_value += AD7745.VT_SETUP_VTMD0
        if val.EXTREF:
            reg_value += AD7745.VT_SETUP_EXTREF
        if val.VTSHORT:
            reg_value += AD7745.VT_SETUP_VTSHORT
        if val.VTCHOP:
            reg_value += AD7745.VT_SETUP_VTCHOP
        self.write_byte(AD7745.VT_SETUP_ADDR, reg_value)

    @property
    def exc_setup(self) -> "AD7745.exc_setup_reg":
        """
        EXC SET-UP REGISTER: Address Pointer 0x09, Default Value 0x03
        CLKCTRL: The CLKCTRL bit should be set to 0 for the specified AD7745/AD7746 performance. CLKCTRL = 1 decreases
            the excitation signal frequency and the modulator clock frequency by factor of 2. This also increases the
             conversion time on all channels (capacitive, voltage, and temperature) by a factor of 2.
        EXCON: When EXCON = 0, the excitation signal is present on the output only during capacitance channel
            conversion. When EXCON = 1, the excitation signal is present on the output during both capacitance and
            voltage/temperature conversion.
        EXCB: EXCB = 1 enables EXCB pin as the excitation output.
        UEXCB: UEXCB = 1 enables EXCB pin as the inverted excitation output. Only one of the EXCB or the EXCB bits
            should be set for proper operation.
        EXCA: EXCA = 1 enables EXCA pin as the excitation output.
        UEXCA: EXCA = 1 enables EXCA pin as the inverted excitation output. Only one of the EXCA or the EXCA bits should
            be set for proper operation.
        EXCLVL1, EXCLVL0 : Excitation Voltage Level.

            ======= ======= ============== ================= ==================
            EXCLVL1 EXCLVL0 Voltage on Cap EXC Pin Low Level EXC Pin High Level
            ======= ======= ============== ================= ==================
               0    0          ±VDD/8          VDD × 3/8           VDD × 5/8
               0    1          ±VDD/4          VDD × 1/4           VDD × 3/4
               1    0          ±VDD × 3/8      VDD × 1/8           VDD × 7/8
               1    1          ±VDD/2          0                   VDD
            ======= ======= ============== ================= ==================

        :param: None
        :return: AD7745.exc_setup_reg
        """
        read_string = self.read_byte(AD7745.EXC_SETUP_ADDR, byte_len=1)[0]
        return AD7745.exc_setup_reg(
            CLKCTRL=bool(read_string & AD7745.EXC_SETUP_CLKCTRL),
            EXCON=bool(read_string & AD7745.EXC_SETUP_EXCON),
            EXCB=bool(read_string & AD7745.EXC_SETUP_EXCB),
            UEXCB=bool(read_string & AD7745.EXC_SETUP_UEXCB),
            EXCA=bool(read_string & AD7745.EXC_SETUP_EXCA),
            UEXCA=bool(read_string & AD7745.EXC_SETUP_UEXCA),
            EXCLVL1=bool(read_string & AD7745.EXC_SETUP_EXCLVL1),
            EXCLVL0=bool(read_string & AD7745.EXC_SETUP_EXCLVL0),
        )

    @exc_setup.setter
    def exc_setup(self, val: "AD7745.exc_setup_reg") -> None:
        """
        EXC SET-UP REGISTER: Address Pointer 0x09, Default Value 0x03
        CLKCTRL: The CLKCTRL bit should be set to 0 for the specified AD7745/AD7746 performance. CLKCTRL = 1 decreases
            the excitation signal frequency and the modulator clock frequency by factor of 2. This also increases the
             conversion time on all channels (capacitive, voltage, and temperature) by a factor of 2.
        EXCON: When EXCON = 0, the excitation signal is present on the output only during capacitance channel
            conversion. When EXCON = 1, the excitation signal is present on the output during both capacitance and
            voltage/temperature conversion.
        EXCB: EXCB = 1 enables EXCB pin as the excitation output.
        UEXCB: UEXCB = 1 enables EXCB pin as the inverted excitation output. Only one of the EXCB or the EXCB bits
            should be set for proper operation.
        EXCA: EXCA = 1 enables EXCA pin as the excitation output.
        UEXCA: EXCA = 1 enables EXCA pin as the inverted excitation output. Only one of the EXCA or the EXCA bits should
            be set for proper operation.
        EXCLVL1, EXCLVL0 : Excitation Voltage Level.

            ======= ======= ============== ================= ==================
            EXCLVL1 EXCLVL0 Voltage on Cap EXC Pin Low Level EXC Pin High Level
            ======= ======= ============== ================= ==================
               0    0          ±VDD/8          VDD × 3/8           VDD × 5/8
               0    1          ±VDD/4          VDD × 1/4           VDD × 3/4
               1    0          ±VDD × 3/8      VDD × 1/8           VDD × 7/8
               1    1          ±VDD/2          0                   VDD
            ======= ======= ============== ================= ==================

        :param val: AD7745.exc_setup_reg to set
        :return: None
        """
        value = 0
        if val.CLKCTRL:
            value += AD7745.EXC_SETUP_CLKCTRL
        if val.EXCON:
            value += AD7745.EXC_SETUP_EXCON
        if val.EXCB:
            value += AD7745.EXC_SETUP_EXCB
        if val.UEXCB:
            value += AD7745.EXC_SETUP_UEXCB
        if val.EXCA:
            value += AD7745.EXC_SETUP_EXCA
        if val.UEXCA:
            value += AD7745.EXC_SETUP_UEXCA
        if val.EXCLVL1:
            value += AD7745.EXC_SETUP_EXCLVL1
        if val.EXCLVL0:
            value += AD7745.EXC_SETUP_EXCLVL0
        self.write_byte(AD7745.EXC_SETUP_ADDR, value)

    @property
    def configuration(self) -> "AD7745.configuration_reg":
        """
        CONFIGURATION REGISTER: Address Pointer 0x0A, Default Value 0xA0
        VTF1, VTF0: Voltage/temperature channel digital filter setup—conversion time/update rate setup. The conversion
            times in this table are valid for the CLKCTRL = 0 in the EXC SETUP register. The conversion times are
            longer by a factor of two for the CLKCTRL = 1.

        +-----+------+---------------------+------------------+----------------------+
        |     |      |                      VTCHOP = 1                               |
        +-----+------+---------------------+------------------+----------------------+
        |VTF1 | VTF0 |Conversion Time (ms) | Update Rate (Hz) |  –3 dB Frequency (Hz)|
        +=====+======+=====================+==================+======================+
        |0    |  0   |   20.1              |     49.8         |      26.4            |
        +-----+------+---------------------+------------------+----------------------+
        |0    |  1   |   32.1              |     31.2         |      15.9            |
        +-----+------+---------------------+------------------+----------------------+
        |1    |  0   |   62.1              |     16.1         |      8.0             |
        +-----+------+---------------------+------------------+----------------------+
        |1    |  1   |   122.1             |     8.2          |      4.0             |
        +-----+------+---------------------+------------------+----------------------+

        CAPF2, CAPF1, CAPF0: Capacitive channel digital filter setup—conversion time/update rate setup. The conversion
            times in this table are valid for the CLKCTRL = 0 in the EXC SETUP register. The conversion times are longer
            by factor of two for the CLKCTRL = 1.

        ====== ======= ======= ======================  ================ ======================
                                                     CAP CHOP = 0
        ------ ------- ------- ---------------------------------------------------------------
        CAPF2   CAPF1   CAPF0   Conversion Time (ms)    Update Rate     –3 dB Frequency (Hz)
        ====== ======= ======= ======================  ================ ======================
        0       0       0       11.0                    90.9            87.2
        0       0       1       11.9                    83.8            79.0
        0       1       0       20.0                    50.0            43.6
        0       1       1       38.0                    26.3            21.8
        1       0       0       62.0                    16.1            13.1
        1       0       1       77.0                    13.0            10.5
        1       1       0       92.0                    10.9            8.9
        1       1       1       109.6                   9.1             8.0
        ====== ======= ======= ======================  ================ ======================

        MD2, MD1, MD0: Converter mode of operation setup.

        === === === ===============================================
        MD2 MD1 MD0 Mode
        === === === ===============================================
        0   0   0   Idle
        0   0   1   Continuous conversion
        0   1   0   Single conversion
        0   1   1   Power-Down
        1   0   0   \-
        1   0   1   Capacitance system offset calibration
        1   1   0   Capacitance or voltage system gain calibration
        1   1   1
        === === === ===============================================

        :param: None
        :return: AD7745.exc_setup_reg
        """
        read_string = self.read_byte(AD7745.CONFIG_ADDR, byte_len=3)[0]
        return AD7745.configuration_reg(
            VTFS1=bool(read_string & AD7745.CONFIG_VTFS1),
            VTFS0=bool(read_string & AD7745.CONFIG_VTFS0),
            CAPFS2=bool(read_string & AD7745.CONFIG_CAPFS2),
            CAPFS1=bool(read_string & AD7745.CONFIG_CAPFS1),
            CAPFS0=bool(read_string & AD7745.CONFIG_CAPFS0),
            MD2=bool(read_string & AD7745.CONFIG_MD2),
            MD1=bool(read_string & AD7745.CONFIG_MD1),
            MD0=bool(read_string & AD7745.CONFIG_MD0),
        )

    @configuration.setter
    def configuration(self, val: "AD7745.configuration_reg") -> None:
        """
        CONFIGURATION REGISTER: Address Pointer 0x0A, Default Value 0xA0
        VTF1, VTF0: Voltage/temperature channel digital filter setup—conversion time/update rate setup. The conversion
            times in this table are valid for the CLKCTRL = 0 in the EXC SETUP register. The conversion times are
            longer by a factor of two for the CLKCTRL = 1.

                                                VTCHOP = 1
            VTF1    VTF0    Conversion Time (ms)    Update Rate (Hz)    –3 dB Frequency (Hz)
             0       0       20.1                    49.8                26.4
             0       1       32.1                    31.2                15.9
             1       0       62.1                    16.1                8.0
             1       1       122.1                   8.2                 4.0

        CAPF2, CAPF1, CAPF0: Capacitive channel digital filter setup—conversion time/update rate setup. The conversion
            times in this table are valid for the CLKCTRL = 0 in the EXC SETUP register. The conversion times are
            longer by factor of two for the CLKCTRL = 1.

                                                CAP CHOP = 0
            CAPF2   CAPF1   CAPF0   Conversion Time (ms)    Update Rate     –3 dB Frequency (Hz)
              0       0       0       11.0                    90.9            87.2
              0       0       1       11.9                    83.8            79.0
              0       1       0       20.0                    50.0            43.6
              0       1       1       38.0                    26.3            21.8
              1       0       0       62.0                    16.1            13.1
              1       0       1       77.0                    13.0            10.5
              1       1       0       92.0                    10.9            8.9
              1       1       1       109.6                   9.1             8.0

        MD2, MD1, MD0: Converter mode of operation setup.

            MD2 MD1 MD0 Mode
             0   0   0   Idle
             0   0   1   Continuous conversion
             0   1   0   Single conversion
             0   1   1   Power-Down
             1   0   0   -
             1   0   1   Capacitance system offset calibration
             1   1   0   Capacitance or voltage system gain calibration
             1   1   1

        :param val: AD7745.exc_setup_reg to set
        :return: None
        """
        value = 0
        if val.VTFS1:
            value += AD7745.CONFIG_VTFS1
        if val.VTFS0:
            value += AD7745.CONFIG_VTFS0
        if val.CAPFS2:
            value += AD7745.CONFIG_CAPFS2
        if val.CAPFS1:
            value += AD7745.CONFIG_CAPFS1
        if val.CAPFS0:
            value += AD7745.CONFIG_CAPFS0
        if val.MD2:
            value += AD7745.CONFIG_MD2
        if val.MD1:
            value += AD7745.CONFIG_MD1
        if val.MD0:
            value += AD7745.CONFIG_MD0
        self.write_byte(AD7745.CONFIG_ADDR, value)

    @property
    def cap_daca(self) -> "AD7745.cap_daca_reg":
        """
        CAP DAC A REGISTER: Address Pointer 0x0B, Default Value 0x00
        DACAENA: DACAENA = 1 connects capacitive DACA to the positive capacitance input.
        DACA: DACA value, Code 0x00 ≈ 0 pF, Code 0x7F ≈ full range.

        :param: None
        :return: AD7745.cap_daca_reg
        """
        read_string = self.read_byte(AD7745.CAP_DAC_A_ADDR, byte_len=1)[0]
        return AD7745.cap_daca_reg(
            DACAENA=bool(read_string & AD7745.CAP_DAC_A_DACAENA),
            DACA7bit=int(read_string & AD7745.CAP_DAC_A_DACA7bit),
        )

    @cap_daca.setter
    def cap_daca(self, val: "AD7745.cap_daca_reg") -> None:
        """
        CAP DAC A REGISTER: Address Pointer 0x0B, Default Value 0x00
        DACAENA: DACAENA = 1 connects capacitive DACA to the positive capacitance input.
        DACA: DACA value, Code 0x00 ≈ 0 pF, Code 0x7F ≈ full range.

        :param val: AD7745.cap_daca_reg to set
        :return: None
        """
        if val.DACA7bit > 127 or val.DACA7bit < 0:
            raise ValueError(
                f"Provided DACA7bit ({val.DACA7bit}) is out of bounds, must be less than 127!"
            )
        if val.DACAENA:
            self.write_byte(AD7745.CAP_DAC_A_ADDR, (1 << 7) | val.DACA7bit)
        else:
            self.write_byte(AD7745.CAP_DAC_A_ADDR, val.DACA7bit)

    @property
    def cap_dacb(self) -> "AD7745.cap_dacb_reg":
        """
        CAP DAC B REGISTER: Address Pointer 0x0C, Default Value 0x00
        DACBENB: DACBENB = 1 connects capacitive DACB to the negative capacitance input.
        DACB: DACB value, Code 0x00 ≈ 0 pF, Code 0x7F ≈ full range.

        :param: None
        :return: AD7745.cap_dacb_reg
        """
        read_string = self.read_byte(self.CAP_DAC_B_ADDR, byte_len=1)[0]
        return AD7745.cap_dacb_reg(
            DACBENB=bool(read_string & AD7745.CAP_DAC_B_DACBENB),
            DACB7bit=int(read_string & AD7745.CAP_DAC_B_DACB7bit),
        )

    @cap_dacb.setter
    def cap_dacb(self, val: "AD7745.cap_dacb_reg") -> None:
        """
        CAP DAC B REGISTER: Address Pointer 0x0C, Default Value 0x00
        DACBENB: DACBENB = 1 connects capacitive DACB to the negative capacitance input.
        DACB: DACB value, Code 0x00 ≈ 0 pF, Code 0x7F ≈ full range.

        :param val: AD7745.cap_dacb_reg to set
        :return: None
        """
        if val.DACB7bit > 177 or val.DACB7bit < 0:
            raise ValueError(
                f"Provided DACA7bit ({val.DACB7bit}) is out of bounds, buts be less than 177!"
            )
        if val.DACBENB:
            self.write_byte(AD7745.CAP_DAC_B_ADDR, (1 << 7) | val.DACB7bit)
        else:
            self.write_byte(AD7745.CAP_DAC_B_ADDR, val.DACB7bit)

    @property
    def cap_offset(self) -> int:
        """
        CAP OFFSET CALIBRATION REGISTER: 16 Bits, Address Pointer 0x0D, 0x0E, Default Value 0x8000

        :param: None
        :return: (off_h << 8) + off_l, 16 bits int value
        """
        reg_val = self.read_byte(AD7745.CAP_OFFSET_H_ADDR, byte_len=2)
        off_h = reg_val[0]
        off_l = reg_val[1]

        return (off_h << 8) + off_l

    @cap_offset.setter
    def cap_offset(self, val: int) -> None:
        """
        CAP OFFSET CALIBRATION REGISTER: 16 Bits, Address Pointer 0x0D, 0x0E, Default Value 0x8000

        :param val: 16 bits int value to set
        :return: None
        """
        off_h = val >> 8
        off_l = val & 0xFF

        self.write_byte(AD7745.CAP_OFFSET_H_ADDR, off_h)
        self.write_byte(AD7745.CAP_OFFSET_L_ADDR, off_l)

    @property
    def cap_gain(self) -> int:
        """
        CAP GAIN CALIBRATION REGISTER: 16 Bits, Address Pointer 0x0F, 0x10, Default Value 0xXXXX

        :param: None
        :return: (grain_h << 8) + grain_l, 16 bits int value
        """
        reg_val = self.read_byte(AD7745.CAP_OFFSET_H_ADDR, byte_len=2)
        grain_h = reg_val[0]
        grain_l = reg_val[1]

        return (grain_h << 8) + grain_l

    @cap_gain.setter
    def cap_gain(self, val: int) -> None:
        """
        CAP GAIN CALIBRATION REGISTER: 16 Bits, Address Pointer 0x0F, 0x10, Default Value 0xXXXX

        :param val: 16 bits int value to set
        :return: None
        """
        grain_h = val >> 8
        grain_l = val & 0xFF

        self.write_byte(AD7745.CAP_GRAIN_H_ADDR, grain_h)
        self.write_byte(AD7745.CAP_GRAIN_L_ADDR, grain_l)

    @property
    def volt_gain(self) -> int:
        """
        VOLT GAIN CALIBRATION REGISTER: 16 Bits, Address Pointer 0x11,0x12, Default Value 0xXXXX

        :param: None
        :return: 16 bit int gain val
        """
        reg_val = self.read_byte(AD7745.VOLT_GRAIN_H_ADDR, byte_len=2)
        grain_h = reg_val[0]
        grain_l = reg_val[1]

        return (grain_h << 8) + grain_l

    @volt_gain.setter
    def volt_gain(self, val: int) -> None:
        """
        VOLT GAIN CALIBRATION REGISTER: 16 Bits, Address Pointer 0x11,0x12, Default Value 0xXXXX

        :param val: 16 bits int value to set
        :return: None
        """
        grain_h = val >> 8
        grain_l = val & 0xFF

        self.write_byte(AD7745.VOLT_GRAIN_H_ADDR, grain_h)
        self.write_byte(AD7745.VOLT_GRAIN_L_ADDR, grain_l)

    def __del__(self) -> None:
        """
        Close binho on del.

        :return: None
        """
        self.close()

    def close(self) -> None:
        """
        Close the binho adaptor.

        :return: None
        :rtype: None
        """
        self.i2cbus.close()

    @staticmethod
    def cap_val_to_pf(cap_val: int) -> float:
        """Convert full scale dac value to 0-8.1920pF val"""
        return cap_val * (4.096 / 0x800000)

    def get_cap_val(self, timeout: float = 10) -> float:
        """
        Waits until the sensor has fresh data then reads it

        Raises an exception if the sensor's EXCERR bit is set or it takes longer than the timeout

        :param timeout: Timeout in sec
        :return: Capacitance float
        """
        return AD7745.cap_val_to_pf(self.get_raw_cap_val(timeout=timeout))

    def get_raw_cap_val(self, timeout: float = 10) -> int:
        """
        Waits until the sensor has fresh data then reads it

        Raises an exception if the sensor's EXCERR bit is set or it takes longer than the timeout

        :param timeout: Timeout in sec
        :return: Raw Capacitance value
        """
        if not self.cont_conv:
            conf = self.configuration
            conf.MD0 = False
            conf.MD1 = True
            conf.MD2 = False
            self.configuration = conf
        start_time = time.time()
        while True:
            stat_reg = self.status
            if stat_reg.RDYCAP is False:
                return self.cap_data
            if stat_reg.EXCERR:
                self.i2cbus.setLEDRGB(255, 0, 0)
                raise IOError(
                    "Excitation output cannot be driven properly!\n"
                    "A possible reason is a short circuit or too high capacitance between the "
                    "excitation pin and ground "
                )
            if time.time() - start_time > timeout:
                self.i2cbus.setLEDRGB(255, 0, 0)
                raise IOError("Sensor was not ready in time!")

    def cap_input(self, input_setting: "AD7745.CapInput") -> None:
        """
        Configure the cap input setting, nominally CIN1/CIN2

        :param AD7745.CapInput input_setting: Input settin to use
        :return: None
        :raises ValueError: If the valus is invalid
        """
        # Read config and change only the specified values:
        conf_val = self.configuration

        if input_setting == AD7745.CapInput.CAP1_EXC1:
            self.cap_setup = AD7745.cap_setup_reg(
                CAPEN=True,  # Enable cap channels
                CIN2=False,  # Set input to CIN1
                CAPDIFF=False,  # Set single ended input
                CAPCHOP=False,  # Required for specified performance
            )
            self.exc_setup = AD7745.exc_setup_reg(
                CLKCTRL=False,  # Required for specified performance
                EXCON=False,  # Signal is present for cap/vin conversion
                EXCB=False,  # Disable EXCB
                UEXCB=False,  # Disable EXCB
                EXCA=True,  # Enable EXCA
                UEXCA=False,  # Normal not inverted operation
                EXCLVL0=True,  # Excitation Voltage Level set to hi
                EXCLVL1=True,  # Excitation Voltage Level
            )
            # Configure for Continuous conversion
            conf_val.MD0 = True
            conf_val.MD1 = False
            conf_val.MD2 = False
            self.configuration = conf_val
        elif input_setting == AD7745.CapInput.CAP2_EXC2:
            self.cap_setup = AD7745.cap_setup_reg(
                CAPEN=True,  # Enable cap channels
                CIN2=True,  # Set input to CIN2
                CAPDIFF=False,  # Set single ended input
                CAPCHOP=False,  # Required for specified performance
            )
            self.exc_setup = AD7745.exc_setup_reg(
                CLKCTRL=False,  # Required for specified performance
                EXCON=False,  # Signal is present for cap/vin conversion
                EXCB=True,  # Enable EXCB
                UEXCB=False,  # Normal not inverted operation
                EXCA=False,  # Enable EXCA
                UEXCA=False,  # Normal not inverted operation
                EXCLVL0=True,  # Excitation Voltage Level set to hi
                EXCLVL1=True,  # Excitation Voltage Level
            )
            # Configure for Continuous conversion
            conf_val.MD0 = True
            conf_val.MD1 = False
            conf_val.MD2 = False
            self.configuration = conf_val
        elif input_setting == AD7745.CapInput.CAP_DIFF_CIN1:
            self.cap_setup = AD7745.cap_setup_reg(
                CAPEN=True,  # Enable cap channels
                CIN2=False,  # Set input to CIN1
                CAPDIFF=False,  # Set single ended input
                CAPCHOP=False,  # Required for specified performance
            )
            self.exc_setup = AD7745.exc_setup_reg(
                CLKCTRL=False,  # Required for specified performance
                EXCON=False,  # Signal is present for cap/vin conversion
                EXCB=False,  # Disable B output
                UEXCB=True,  # Inverted EXCB operation
                EXCA=True,  # Enable EXCA
                UEXCA=False,  # Normal not inverted operation
                EXCLVL0=True,  # Excitation Voltage Level set to hi
                EXCLVL1=True,  # Excitation Voltage Level
            )
            # Configure for Continuous conversion
            conf_val.MD0 = True
            conf_val.MD1 = False
            conf_val.MD2 = False
            self.configuration = conf_val
        elif input_setting == AD7745.CapInput.CAP_DIFF_CIN2:
            self.cap_setup = AD7745.cap_setup_reg(
                CAPEN=True,  # Enable cap channels
                CIN2=True,  # Set input to CIN2
                CAPDIFF=False,  # Set single ended input
                CAPCHOP=False,  # Required for specified performance
            )
            self.exc_setup = AD7745.exc_setup_reg(
                CLKCTRL=False,  # Required for specified performance
                EXCON=False,  # Signal is present for cap/vin conversion
                EXCB=False,  # Disable B output
                UEXCB=True,  # Inverted EXCB operation
                EXCA=True,  # Enable EXCA
                UEXCA=False,  # Normal not inverted operation
                EXCLVL0=True,  # Excitation Voltage Level set to hi
                EXCLVL1=True,  # Excitation Voltage Level
            )
            if self.cont_conv:
                # Configure for Continuous conversion
                conf_val.MD0 = True
                conf_val.MD1 = False
                conf_val.MD2 = False
            else:
                # Configure for single shot conversion
                conf_val.MD0 = False
                conf_val.MD1 = True
                conf_val.MD2 = False
            self.configuration = conf_val
        else:
            raise ValueError(
                f"The provided input_setting {input_setting} is not a valid AD7745.CapInput value!"
            )

    def cap_filter(self, filter_setting: "AD7745.CapFilter") -> None:
        """
        Set the input filter setting, preserving the config reg.

        :param AD7745.CapFilter filter_setting: Filter setting, specified in readout Hz
        :return: None
        :raises ValueError: If the valus is invalid
        """
        # Read config and change only the specified values:
        conf_val = self.configuration
        if filter_setting == AD7745.CapFilter.CONV87Hz:
            conf_val.CAPFS2, conf_val.CAPFS1, conf_val.CAPFS0 = False, False, False
        elif filter_setting == AD7745.CapFilter.CONV79Hz:
            conf_val.CAPFS2, conf_val.CAPFS1, conf_val.CAPFS0 = False, False, True
        elif filter_setting == AD7745.CapFilter.CONV44Hz:
            conf_val.CAPFS2, conf_val.CAPFS1, conf_val.CAPFS0 = False, True, False
        elif filter_setting == AD7745.CapFilter.CONV22Hz:
            conf_val.CAPFS2, conf_val.CAPFS1, conf_val.CAPFS0 = False, True, True
        elif filter_setting == AD7745.CapFilter.CONV14Hz:
            conf_val.CAPFS2, conf_val.CAPFS1, conf_val.CAPFS0 = True, False, False
        elif filter_setting == AD7745.CapFilter.CONV11Hz:
            conf_val.CAPFS2, conf_val.CAPFS1, conf_val.CAPFS0 = True, False, True
        elif filter_setting == AD7745.CapFilter.CONV9Hz:
            conf_val.CAPFS2, conf_val.CAPFS1, conf_val.CAPFS0 = True, True, False
        elif filter_setting == AD7745.CapFilter.CONV8Hz:
            conf_val.CAPFS2, conf_val.CAPFS1, conf_val.CAPFS0 = True, True, True
        else:
            raise ValueError(
                f"The provided filter_setting {filter_setting} is not a valid AD7745.CapFilter value!"
            )
        self.configuration = conf_val

    def cal_low_val(self, st_val=0) -> int:
        """
        Function that sets the DAC value so the sensor has as much headroom as possible.
        This function returns the value that the capdac was set to.

        :return: Identified cap dat val
        """
        # Reset cap reg
        self.cap_daca_reg(DACAENA=True, DACA7bit=0)
        # Flush stale cap data values
        self.get_raw_cap_val()
        self.get_raw_cap_val()
        # Binary search
        start, end = st_val, 127
        while start <= 127:
            print(f"Cal {start}")
            self.cap_daca = self.cap_daca_reg(DACAENA=True, DACA7bit=start)
            self.get_raw_cap_val()
            # Check if consecutive readings are 0, and not full scale
            if self.get_raw_cap_val() > 0:
                start += 1
                continue
            else:
                break
        self.cap_daca = self.cap_daca_reg(DACAENA=True, DACA7bit=start - 1)
        self.get_raw_cap_val()
        if self.get_raw_cap_val() != 0 and self.get_raw_cap_val() != 0xFFFFFF:
            return start
        self.i2cbus.setLEDRGB(255, 0, 0)
        raise IOError(
            f"Failed to find a valid CDAC value!, last attempt was {start} @ {hex(self.get_raw_cap_val())}"
        )

    @property
    def cap_dac_lsb(self) -> float:
        """
        Calculates and returns the size of the capdac LSB based on AN-1585.

        This can range from 0.134pF to 0.173pF. (technically this is the min to typ variation,
        however the datasheet indicates that this parameter typically varies low)

        :return: LSB size of the CAPDAC in pF
        """
        if self._cap_dac_lsb is None:
            f_gain_cal = (2**16 + self.cap_gain) / 2**16
            c_ref = 4.096 * f_gain_cal
            c_dac_range = c_ref * 3.2
            c_dac_lsb = c_dac_range / 127
            self._cap_dac_lsb = c_dac_lsb
        return self._cap_dac_lsb

if __name__ == "__main__":
    adaptor = binhoHostAdapter.binhoHostAdapter("COM1")
    sensor = AD7745(adaptor)

    sensor.cap_input(AD7745.CapInput.CAP1_EXC1)

    print(sensor.get_cap_val())

    sensor.close()


# Import required libraries
import pandas as pd
from sklearn.model_selection import train_test_split
from sklearn.ensemble import RandomForestClassifier
from sklearn.metrics import accuracy_score, classification_report

# Load the Titanic dataset
url = "https://web.stanford.edu/class/archive/cs/cs109/cs109.1166/stuff/titanic.csv"
data = pd.read_csv(url)

# Print the first few rows of the dataset
print(data.head())
# Print information about the dataset, including columns and data types
print(data.info())

# Preprocessing: Handle missing values and convert categorical variables to numeric
data["Age"].fillna(data["Age"].median(), inplace=True)
data["Sex"] = data["Sex"].map({"female": 0, "male": 1})

# Separate features and target variable
X = data.drop(["Survived", "Name"], axis=1)
y = data["Survived"]

# Split the dataset into training and testing sets
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)

# Create a Random Forest classifier
random_forest_classifier = RandomForestClassifier(n_estimators=100, random_state=42)

# Train the Random Forest classifier on the training data
random_forest_classifier.fit(X_train, y_train)

# Make predictions on the test data
y_pred = random_forest_classifier.predict(X_test)

# Evaluate the classifier
accuracy = accuracy_score(y_test, y_pred)
print(f"Accuracy: {accuracy:.2f}")

# Print the classification report
print("Classification Report:")
print(classification_report(y_test, y_pred))

# Print the predicted class labels along with the true class labels
print("\nPredicted Class Labels | True Class Labels")
for pred, true in zip(y_pred, y_test):
    pred_label = "Survived" if pred == 1 else "Not Survived"
    true_label = "Survived" if true == 1 else "Not Survived"
    print(f"{pred_label} | {true_label}")


from django.shortcuts import render,redirect
from django.contrib.auth.models import User
from django.contrib.auth import authenticate,login,logout
from .models import *
import datetime

def Home(request):
    data2=Add_Car.objects.all()
    da=""
    data1=Categary.objects.all()
    error=False
    if request.method=='POST':
        b=request.POST['brand']
        m=request.POST['carmodel']
        t=request.POST['cartype']
        car = Categary.objects.get(brand=b)
        da=Add_Car.objects.filter(brand=car,carmodel=m,cartype=t)
        error=True

    d={'data':da,'data2':data2,'error':error}
    return render(request, 'user_home.html',d)
def Logout(request):
    if not request.user.is_authenticated:
        return redirect('login')
    logout(request)
    return redirect('home')
def about(request):
    data = 0
    try:
        data=About.objects.get(id=1)
    except:
        pass
    error = False
    if request.method=='POST':
        t=request.POST['title']
        d=request.POST['des']
        i1 = request.FILES['img1']
        i2 = request.FILES['img2']
        if data:
            data.title=t
            data.des = d
            data.img1 = i1
            data.img2 = i2
            data.save()
            error=True
        else:
            About.objects.create(title=t,des=d,img1=i1,img2=i2)
            error=True
    d = {'error':error}
    return render(request,'about.html',d)

def contact_update(request):
    data = 0
    try:
        data=Contact.objects.get(id=1)
    except:
        pass
    error = False
    if request.method=='POST':
        t=request.POST['email']
        d=request.POST['time1']
        i1 = request.POST['contact']
        i2 = request.POST['add']
        if data:
            data.email=t
            data.office_time = d
            data.contact = i1
            data.address = i2
            data.save()
            error=True
        else:
            Contact.objects.create(office_time=d,contact=i1,address=i2,email=t)
            error=True
    d = {'error':error}
    return render(request,'update_contact.html',d)

def view_about(request):
    data=About.objects.get(id=1)
    d={'data':data}
    return render(request,'view_about.html',d)

def Contact1(request):
    data = Contact.objects.get(id=1)
    d = {'data': data}
    return render(request, 'contact.html',d)
def Admin_Login(request):
    error = ""
    if request.method == "POST":
        u = request.POST['uname']
        p = request.POST['pwd']
        user = authenticate(username=u, password=p)
        try:
            if user.is_staff:
                login(request, user)
                error = "yes"
                return redirect('admin_home')
            else:
                error = "not"
        except:
            error="not"
    d = {'error': error}
    return render(request,'login.html',d)
def Admin_Home(request):
    if not request.user.is_staff:
        return redirect('login')
    book=Add_Car.objects.all()
    book1=Categary.objects.all()
    book2=Customer.objects.all()
    b=0
    for i in book:
        b+=1
    b1=0
    for i in book1:
        b1+=1
    b2=0
    for i in book2:
        b2+=1

    d={'b':b,'b1':b1,'b2':b2}
    return render(request, 'admin_home.html',d)

def Add_car(request):
    if not request.user.is_authenticated:
        return redirect('login')
    error = False
    data=Categary.objects.all()
    if request.method == "POST":
        cn = request.POST['carname']
        t = request.POST['cartype']
        bd = request.POST['bodycover']
        cm = request.POST['carmodel']
        bt = request.POST['bodytype']
        b= request.POST['brand']
        p = request.POST['carprice']
        num = request.POST['carnumber']
        l= request.POST['carlength']
        w = request.POST['carwidth']
        h = request.POST['carheight']
        s= request.POST['seatingcapacity']
        fu = request.POST['fueltype']
        de= request.POST['displacement']
        m = request.POST['maxpower']
        mt= request.POST['maxtorque']
        ml = request.POST['milage']
        tr = request.POST['transmissiontype']
        n= request.POST['noofgear']
        ac= request.POST['aircondition']
        cp= request.POST['carpowerwindow']
        ct= request.POST['carcenterlocking']
        ca= request.POST['carabs']
        ab= request.POST['airbags']
        ft= request.POST['frontype']
        rt= request.POST['reartype']
        cd= request.POST['cardescription']
        fc= request.POST['fuelcapacity']
        bs= request.POST['bootspace']
        fl= request.POST['foglamps']
        e= request.POST['enginedisplay']
        cl= request.POST['centrallocking']
        ld= request.POST['lastupdationdate']
        i1= request.FILES['img1']
        i2= request.FILES['img2']
        i3= request.FILES['img3']
        i4= request.FILES['img4']
        br=Categary.objects.filter(brand=b).first()
        Add_Car.objects.create(brand=br,carname=cn,cartype=t,bodycover=bd,carmodel=cm,bodytype=bt,carprice=p,carnumber=num,carlength=l,carwidth=w,carheight=h,seatingcapacity=s,fueltype=fu,displacement=de,maxpower=m,maxtorque=mt,milage=ml,transmissiontype=tr,noofgear=n,aircondition=ac,carpowerwindow=cp,carcenterlocking=ct,carabs=ca,airbags=ab,frontype=ft,reartype=rt,cardescription=cd,fuelcapacity=fc,bootspace=bs,foglamps=fl,enginedisplay=e,centrallocking=cl,lastupdationdate=ld,img1=i1,img2=i2,img3=i3,img4=i4)
        error = True
    d={'data':data,'error':error}
    return render(request,'add_car.html',d)
def Add_company(request):
    if not request.user.is_authenticated:
        return redirect('login')
    error = False
    if request.method=="POST":
        b=request.POST['brand']
        i= request.FILES['image']
        Categary.objects.create(brand=b,image=i)
        error=True
    d ={'error':error}
    return render(request,'add_company.html',d)

def View_Company(request):
     if not request.user.is_authenticated:
        return redirect('login')
     data=Categary.objects.all()
     d={'data':data}
     return render(request,'view_company.html',d)
def View_Car(request):
     if not request.user.is_authenticated:
        return redirect('login')
     data=Add_Car.objects.all()
     d={'data':data}
     return render(request,'view_car.html',d)

def edit_car(request,pid):
    if not request.user.is_authenticated:
        return redirect('login')
    data=Categary.objects.all()
    error=False
    detail=Add_Car.objects.get(id=pid)
    if request.method == "POST":
        cn = request.POST['carname']
        t = request.POST['cartype']
        bd = request.POST['bodycover']
        cm = request.POST['carmodel']
        bt = request.POST['bodytype']
        b= request.POST['brand']
        p = request.POST['carprice']
        num = request.POST['carnumber']
        l= request.POST['carlength']
        w = request.POST['carwidth']
        h = request.POST['carheight']
        s= request.POST['seatingcapacity']
        fu = request.POST['fueltype']
        de= request.POST['displacement']
        m = request.POST['maxpower']
        mt= request.POST['maxtorque']
        ml = request.POST['milage']
        tr = request.POST['transmissiontype']
        n= request.POST['noofgear']
        ac= request.POST['aircondition']
        cp= request.POST['carpowerwindow']
        ct= request.POST['carcenterlocking']
        ca= request.POST['carabs']
        ab= request.POST['airbags']
        ft= request.POST['frontype']
        rt= request.POST['reartype']
        cd= request.POST['cardescription']
        fc= request.POST['fuelcapacity']
        bs= request.POST['bootspace']
        fl= request.POST['foglamps']
        e= request.POST['enginedisplay']
        cl= request.POST['centrallocking']
        ld= request.POST['lastupdationdate']
        try:
            i1= request.FILES['img1']
            detail.img1=i1
            detail.save()
        except:
            pass
        try:
            i2= request.FILES['img2']
            detail.img2=i2
            detail.save()
        except:
            pass
        try:
            i3= request.FILES['img3']
            detail.img1=i3
            detail.save()
        except:
            pass
        try:
            i4= request.FILES['img4']
            detail.img1=i4
            detail.save()
        except:
            pass
        detail.carname=cn
        detail.cartype=t
        detail.bodycover=bd
        detail.carmodel=cm
        detail.bodytype=bt
        detail.brand.brand=b
        detail.carprice=p
        detail.carnumber=num
        detail.carlength=l
        detail.carwidth=w
        detail.carheight=h
        detail.seatingcapacity=s
        detail.fueltype=fu
        detail.displacement=de
        detail.maxpower=m
        detail.maxtorque=mt
        detail.milage=ml
        detail.transmissiontype=tr
        detail.noofgear=n
        detail.aircondition=ac
        detail.carpowerwindow=cp
        detail.carcenterlocking=ct
        detail.carabs=ca
        detail.airbags=ab
        detail.frontype=ft
        detail.reartype=rt
        detail.cardescription=cd
        detail.fuelcapacity=fc
        detail.bootspace=bs
        detail.enginedisplay=e
        detail.foglamps=fl
        detail.centrallocking=cl
        detail.lastupdationdate=ld
        detail.save()
        error=True
    d={'detail':detail,'data':data,'error':error}
    return render(request,'edit_car.html',d)

def edit_company(request,pid):
    if not request.user.is_authenticated:
        return redirect('login')
    data=Categary.objects.get(id=pid)
    error=False
    if request.method=="POST":
        b=request.POST['brand']
        try:
            i= request.FILES['image']
            data.image=i
            data.save()
        except:
            pass
        data.brand=b
        data.save()
        error=True
    d={'data':data,'error':error}
    return render(request,'edit_company.html',d)

def car_list(request):
    error=False
    data3=Categary.objects.all()
    data2=Add_Car.objects.all()
    data1=Add_Car.objects.all()
    if request.method=="POST":
        s =request.POST['carname']
        m =request.POST['carmodel']
        f =request.POST['fueltype']
        data = Add_Car.objects.filter(carname=s,carmodel=m,fueltype=f)
        if data:
            data1 = data
            error=True
    d={'data2':data2,'data1':data1,'error':error,'data3':data3,}
    return render(request,'car_list.html',d)

def car_detail(request,pid):
    error = False
    data=Add_Car.objects.get(id=pid)
    rt = datetime.datetime.now()
    rd = datetime.date.today()
    rt1 = str(rt).split(":")
    rt2 = "".join(rt1)
    order_id1 = rt2.split("-")
    order_id2 = "".join(order_id1)
    order_id3 = order_id2.split(" ")
    order_id4 = "".join(order_id3)
    order_id = order_id4.replace("2020","")
    if request.method=="POST":
        n=request.POST['name']
        e=request.POST['email']
        c=request.POST['con']
        m=request.POST['msg']
        user=User.objects.create_user(username=n,email=e)
        customer=Customer.objects.create(user=user,contact=c,msg=m,enquery=order_id,Name=data,enquery_date=rd,remark='null',remark_date='null',status='unseen')
        error = True

    d={'data':data,'error':error}
    return render(request,'car_detail.html',d)
def company_list(request):
    data=Categary.objects.all()
    data2 = Add_Car.objects.all()
    d={'data':data,"data2":data2}
    return render(request,'company_list.html',d)
def company_detail(request,pid):
    data=Categary.objects.get(id=pid)
    data1=Add_Car.objects.filter(brand=data)
    d={'data1':data1,'data':data}
    return render(request,'company_detail.html',d)
def view_enquery(request):
    data=Customer.objects.all()
    d={'data':data}
    return render(request,'view_enquery.html',d)
def enquery_detail(request,pid):
    data=Customer.objects.get(id=pid)
    date=datetime.date.today()
    if request.method=='POST':
        n=request.POST['name']
        e=request.POST['email']
        c=request.POST['con']
        m=request.POST['msg']
        r=request.POST['remark']
        r1=request.POST['enquery_date']
        data.name=n
        data.email=e
        data.contact=c
        data.msg=m
        data.remark=r
        data.enquery_date=r1
        data.status="seen"
        data.remark_date=date
        data.save()
        return redirect('view_enquery')
    d={'data':data}
    return render(request,'enquery_detail.html',d)

def view_seenenquery(request):
    data=Customer.objects.all()
    d={'data':data}
    return render(request,'view_seenenquery.html',d)
def seen_enquery(request,pid):
    data=Customer.objects.get(id=pid)
    d={'data':data}
    return render(request,'seen_enquery.html',d)
def search_enquery(request):
    data=Customer.objects.all()
    error=""
    if request.method=='POST':
        n=request.POST['enquery']
        da=Customer.objects.filter(enquery=n)
        if da:
            data=da
            error=True
    d={'data':data,'error':error}
    return render(request,'search_enquery.html',d)
def delete_company(request,pid):
    data=Categary.objects.get(id=pid)
    data.delete()
    return redirect('view_company')

def delete_car(request,pid):
    data=Add_Car.objects.get(id=pid)
    data.delete()
    return redirect('view_car')


#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
File: source/utils/engine.py
"""

import os
import time
import shutil
import numpy as np
import torch

from collections import defaultdict
from source.inputter.batcher import create_turn_batch, create_kb_batch

class MetricsManager(object):
    """
    MetricsManager
    """
    def __init__(self):
        self.metrics_val = defaultdict(float)
        self.metrics_cum = defaultdict(float)
        self.num_samples = 0

    def update(self, metrics_list):
        """
        update
        """
        for i, metrics in enumerate(metrics_list):
            num_samples = metrics.pop("num_samples", 1)
            self.num_samples += num_samples
            for key, val in metrics.items():
                if val is not None:
                    key_turn = str(key) + "-turn-{}".format(str(i+1))
                    self.metrics_val[key_turn] = val

                    if isinstance(val, torch.Tensor):
                        val = val.item()
                        self.metrics_cum[key] += val * num_samples
                    elif isinstance(val, tuple):
                        assert len(val) == 2
                        val, num_words = val[0].item(), val[1]
                        self.metrics_cum[key] += np.array([val * num_samples, num_words])
                    else:
                        self.metrics_cum[key] += val * num_samples

    def clear(self):
        """
        clear
        """
        self.metrics_val = defaultdict(float)
        self.metrics_cum = defaultdict(float)
        self.num_samples = 0

    def get(self, name):
        """
        get
        """
        val = self.metrics_cum.get(name)
        if not isinstance(val, float):
            val = val[0]
        return val / self.num_samples

    def report_val(self):
        """
        report_val
        """
        metric_strs = []
        for key, val in self.metrics_val.items():
            metric_str = "{}={:.3f}".format(key.upper(), val)
            metric_strs.append(metric_str)
        metric_strs = "   ".join(metric_strs)
        return metric_strs

    def report_cum(self):
        """
        report_cum
        """
        metric_strs = []
        for key, val in self.metrics_cum.items():
            if isinstance(val, float):
                val, num_words = val, None
            else:
                val, num_words = val

            metric_str = "{}={:.3f}".format(key.upper(), val / self.num_samples)
            metric_strs.append(metric_str)

            if num_words is not None:
                ppl = np.exp(min(val / num_words, 100))
                metric_str = "{}-PPL={:.3f}".format(key.upper(), ppl)
                metric_strs.append(metric_str)

        metric_strs = "   ".join(metric_strs)
        return metric_strs

class Trainer(object):
    """
    Trainer
    """
    def __init__(self,
                 model,
                 optimizer,
                 train_iter,
                 valid_iter,
                 logger,
                 valid_metric_name="-loss",
                 num_epochs=1,
                 pre_epochs=1,
                 save_dir=None,
                 log_steps=None,
                 valid_steps=None,
                 grad_clip=None,
                 lr_scheduler=None,
                 entity_dir=None):
        self.model = model
        self.optimizer = optimizer
        self.train_iter = train_iter
        self.valid_iter = valid_iter
        self.logger = logger

        self.is_decreased_valid_metric = valid_metric_name[0] == "-"
        self.valid_metric_name = valid_metric_name[1:]
        self.num_epochs = num_epochs
        self.pre_epochs = pre_epochs
        self.save_dir = save_dir
        self.log_steps = log_steps
        self.valid_steps = valid_steps
        self.grad_clip = grad_clip
        self.lr_scheduler = lr_scheduler
        self.entity_dir = entity_dir

        self.best_valid_metric = float(
            "inf") if self.is_decreased_valid_metric else -float("inf")
        self.epoch = 0
        self.batch_num = 0
        self.use_rl = False

        self.train_start_message = "\n".join(["",
                                              "=" * 85,
                                              "=" * 34 + " Model Training " + "=" * 35,
                                              "=" * 85,
                                              ""])
        self.valid_start_message = "\n" + "-" * 33 + " Model Evaulation " + "-" * 33

    def train(self):
        """
        train
        """
        for epoch in range(self.epoch, self.pre_epochs):
            self.train_epoch()

        self.use_rl = True
        self.valid_metric_name = "bleu_score"      # "bleu_score" or "f1_score"
        self.best_valid_metric = -float("inf")
        self.is_decreased_valid_metric = False
        optimizer = self.optimizer
        #optimizer = getattr(torch.optim, "Adam")(self.model.parameters(), lr=6.25e-5)
        lr_decay = self.lr_scheduler.factor
        patience = self.lr_scheduler.patience
        self.lr_scheduler = torch.optim.lr_scheduler.ReduceLROnPlateau(
            optimizer=optimizer, mode='max', factor=lr_decay,
            patience=patience, verbose=True, min_lr=1e-6)

        self.logger.info("RL-training starts ...")
        for epoch in range(self.epoch, self.num_epochs):
            self.train_epoch()

    def train_epoch(self):
        """
        train_epoch
        """
        self.epoch += 1
        train_mm = MetricsManager()
        num_batches = self.train_iter.n_batch
        self.train_iter.prepare_epoch()
        self.logger.info(self.train_start_message)

        for batch_idx in range(num_batches):
            self.model.train()
            start_time = time.time()

            local_data = self.train_iter.get_batch(batch_idx)
            turn_inputs = create_turn_batch(local_data['inputs'])
            kb_inputs = create_kb_batch(local_data['kbs'])
            assert len(turn_inputs) == local_data['max_turn']

            metrics_list = self.model.iterate(turn_inputs, kb_inputs,
                                              optimizer=self.optimizer,
                                              grad_clip=self.grad_clip,
                                              use_rl=self.use_rl,
                                              entity_dir=self.entity_dir,
                                              is_training=True)

            elapsed = time.time() - start_time
            train_mm.update(metrics_list)
            self.batch_num += 1

            if (batch_idx + 1) % self.log_steps == 0:
                message_prefix = "[Train][{:2d}][{}/{}]".format(self.epoch, batch_idx + 1, num_batches)
                metrics_message = train_mm.report_val()
                message_posfix = "TIME={:.2f}s".format(elapsed)
                self.logger.info("   ".join(
                    [message_prefix, metrics_message, message_posfix]))

            if (batch_idx + 1) % self.valid_steps == 0:
                self.logger.info(self.valid_start_message)
                valid_mm = self.evaluate(self.model, self.valid_iter, use_rl=self.use_rl, entity_dir=self.entity_dir)

                message_prefix = "[Valid][{:2d}][{}/{}]".format(self.epoch, batch_idx + 1, num_batches)
                metrics_message = valid_mm.report_cum()
                self.logger.info("   ".join([message_prefix, metrics_message]))

                cur_valid_metric = valid_mm.get(self.valid_metric_name)
                if self.is_decreased_valid_metric:
                    is_best = cur_valid_metric < self.best_valid_metric
                else:
                    is_best = cur_valid_metric > self.best_valid_metric
                if is_best:
                    self.best_valid_metric = cur_valid_metric
                self.save(is_best, is_rl=self.use_rl)
                if self.lr_scheduler is not None:
                    self.lr_scheduler.step(cur_valid_metric)
                self.logger.info("-" * 85 + "\n")

        self.save()
        self.logger.info('')

    @staticmethod
    def evaluate(model, data_iter, use_rl=False, entity_dir=None):
        """
        evaluate
        """
        model.eval()
        mm = MetricsManager()
        num_batches = data_iter.n_batch
        with torch.no_grad():
            for batch_idx in range(num_batches):
                local_data = data_iter.get_batch(batch_idx)
                turn_inputs = create_turn_batch(local_data['inputs'])
                kb_inputs = create_kb_batch(local_data['kbs'])
                assert len(turn_inputs) == local_data['max_turn']

                metrics_list = model.iterate(turn_inputs, kb_inputs,
                                             use_rl=use_rl, entity_dir=entity_dir, is_training=False)
                mm.update(metrics_list)
        return mm

    def save(self, is_best=False, is_rl=False):
        """
        save
        """
        model_file = os.path.join(self.save_dir, "state_epoch_{}.model".format(self.epoch))
        torch.save(self.model.state_dict(), model_file)
        self.logger.info("Saved model state to '{}'".format(model_file))

        train_file = os.path.join(self.save_dir, "state_epoch_{}.train".format(self.epoch))
        train_state = {"epoch": self.epoch,
                       "batch_num": self.batch_num,
                       "best_valid_metric": self.best_valid_metric,
                       "optimizer": self.optimizer.state_dict()}
        if self.lr_scheduler is not None:
            train_state["lr_scheduler"] = self.lr_scheduler.state_dict()
        torch.save(train_state, train_file)
        self.logger.info("Saved train state to '{}'".format(train_file))

        if is_best:
            if is_rl:
                best_model_file = os.path.join(self.save_dir, "best_rl.model")
                best_train_file = os.path.join(self.save_dir, "best_rl.train")
            else:
                best_model_file = os.path.join(self.save_dir, "best.model")
                best_train_file = os.path.join(self.save_dir, "best.train")
            shutil.copy(model_file, best_model_file)
            shutil.copy(train_file, best_train_file)
            self.logger.info(
                "Saved best model state to '{}' with new best valid metric {}={:.3f}".format(
                    best_model_file, self.valid_metric_name.upper(), self.best_valid_metric))

    def load(self, file_ckpt):
        """
        load
        """
        if os.path.isfile(os.path.join(self.save_dir, file_ckpt)):
            file_prefix = file_ckpt.split('.')[0]
            model_file = "{}/{}.model".format(self.save_dir, file_prefix)
            train_file = "{}/{}.train".format(self.save_dir, file_prefix)

            model_state_dict = torch.load(model_file, map_location=lambda storage, loc: storage)
            self.model.load_state_dict(model_state_dict)
            self.logger.info("Loaded model state from '{}'".format(model_file))

            train_state_dict = torch.load(train_file, map_location=lambda storage, loc: storage)
            self.epoch = train_state_dict["epoch"]
            self.best_valid_metric = train_state_dict["best_valid_metric"]
            self.batch_num = train_state_dict["batch_num"]
            self.optimizer.load_state_dict(train_state_dict["optimizer"])
            if self.lr_scheduler is not None and "lr_scheduler" in train_state_dict:
                self.lr_scheduler.load_state_dict(train_state_dict["lr_scheduler"])
            self.logger.info(
                "Loaded train state from '{}' with (epoch-{} best_valid_metric={:.3f})".format(
                    train_file, self.epoch, self.best_valid_metric))