# -*- encoding=utf-8 -*-
from PyQt5.QtCore import *
from comPort import *
import re
import openpyxl
import datetime
import struct
import math
import binascii

gMeasureDataSet = {"Header": 0x80, "Length": 38}


class Thread_McuComToolHandle(QThread):
    signalComPort = pyqtSignal(str)  # Com port name
    signalComData = pyqtSignal(dict)  # Com data list
    signalComLog = pyqtSignal(str)  # Com port status

    def __init__(self):
        super(Thread_McuComToolHandle, self).__init__()
        self.excel_Init()
        self.comPort = COM()
        self.lastListPort = "None"
        self.comName = "None"
        self.baudRate = 115200
        self.dataTxRxEnFlg = 0  # Use to tell the thread, data is transmitting
        self.portOpenClickFlg = 0  # Use to tell the thread open port
        self.meterData = {"PacketGetFlg": 0,"MeterVersion": 0,"Vrms": 0, "Irms": 0, "Freq": 0, "Pactive": 0, "Preactive": 0, "Papparent": 0,
                          "Pfactor": 0, "Counter": 0}
        self.dataRxState = {"Mode": "PacketDetect"}
        self.signalSheld = {"OpenCOM": 0, "CloseCOM": 0}
        self.excelSaveFlg = 0
    ########################################### Main function #############################################################
    def run(self):
        while True:
            # Refresh the port information when new COM port insert
            if self.dataTxRxEnFlg == 0:
                # self.lgc_KeySheld(0, 1)
                listPort = self.comPort.list_port()  # list port names on port selection
                if self.lastListPort != listPort:
                    self.signalComPort.emit("None")  # clear the list and then update
                    if listPort != "None":  # Port can be find
                        for item in list(listPort):
                            self.signalComPort.emit(item[1])
                    if self.lastListPort != listPort:
                        self.lastListPort = listPort

            # When the Use click the open com port button
            if self.portOpenClickFlg == 1:
                try:
                    self.excel_Init()
                    self.comPort.open_port(self.comName, self.baudRate)
                    self.comPort.ser.write(struct.pack('B', 0x22)) #Ask the MCU to start send data
                    self.signalComLog.emit("Port " + self.comName + " open success!")
                    self.dataTxRxEnFlg = 1  # enable data receiving
                except:
                    self.signalComLog.emit("Port is occuped!")  # User reopen the COM Port in other software
                    self.dataTxRxEnFlg = 0  # disable data receiving
                self.portOpenClickFlg = 0  # Wait for next click on COM port open button

            # Save data from MCU into excel
            if self.dataTxRxEnFlg == 1:
                try:
                    self.Port_RxPacketDetect()
                    # if self.meterData["PacketGetFlg"] == 1:
                    #     self.signalComData.emit(self.meterData)

                except:
                    # avoid close com port when in packet detection,The corruption is caused by no data receive
                    if self.dataTxRxEnFlg == 1:
                        self.signalComLog.emit(
                            "Can't receive data from MSP!")  # User reopen the COM Port in other software



    def lgc_KeySheld(self, openCOM=0, closeCOM=0):
        self.signalSheld["OpenCOM"] = openCOM
        self.signalSheld["CloseCOM"] = closeCOM

    ########################################### Excel handling #############################################################
    def excel_Init(self):
        # init excel
        self.wb = openpyxl.Workbook()
        self.sheet = self.wb["Sheet"]
        self.sheet["A1"] = "Vrms(V)"
        self.sheet["B1"] = "Irms(A)"
        self.sheet["C1"] = "freq(Hz)"
        self.sheet["D1"] = "Pactive(W)"
        self.sheet["E1"] = "Preactive(var)"
        self.sheet["F1"] = "Papparent(VA)"
        self.sheet["G1"] = "Pfactor(C)"

        self.sheetTitle = datetime.datetime.now().strftime('%Y-%m-%d-%H-%M') + "-meterData"
        self.lineCount = 2

    def Excel_MeterDataHandle(self):
        if (self.excelSaveFlg):
            self.sheet["A" + str(self.lineCount)] = self.meterData["Vrms"]
            self.sheet["B" + str(self.lineCount)] = self.meterData["Irms"]
            self.sheet["C" + str(self.lineCount)] = self.meterData["Freq"]
            self.sheet["D" + str(self.lineCount)] = self.meterData["Pactive"]
            self.sheet["E" + str(self.lineCount)] = self.meterData["Preactive"]
            self.sheet["F" + str(self.lineCount)] = self.meterData["Papparent"]
            self.sheet["G" + str(self.lineCount)] = self.meterData["Pfactor"]
            self.lineCount += 1
            print(self.lineCount)

    ########################################### Human action  #############################################################
    def gui_OpenCom(self, comName):
        if self.signalSheld["OpenCOM"] == 0:
            # If there is port connection, close it first
            if self.dataTxRxEnFlg == 1:
                self.signalComData.emit({})  # clear the display
                self.dataTxRxEnFlg = 0  # Tell the while loop the port is closed

            comNumRegex = re.compile(r'COM\d\d\d|COM\d\d|COM\d')
            self.comName = comNumRegex.search(comName).group()
            self.portOpenClickFlg = 1
            pass
        else:
            self.signalComLog.emit("Close port first!")  # The device is removed from PC

    def gui_CoseCom(self):  # Make sure the main thread will not conflit between the slave thread
        if self.signalSheld["CloseCOM"] == 0:
            self.dataTxRxEnFlg = 0  # Tell the while loop, the COM port is closed
            if self.comName != "None":
                self.signalComData.emit({})  # clear the display
                self.comPort.close_port()  # Termate the Port communication to avoid blocked at the data receiving
                self.signalComLog.emit("Connection terminated and data saved!")  # The device is removed from PC
                self.comName = "None"
                if self.sheet["A2"].value != None:  # check whether there is any data saved
                    self.wb.save(self.sheetTitle + '.xlsx')

        else:
            self.signalComLog.emit("Open port first!")  # The device is removed from PC


    ########################################### Port control #############################################################
    def Port_RxPacketDetect(self):
        while True:
            if self.comPort.ser.in_waiting:
                # Analysis the header
                if self.dataRxState["Mode"] == "PacketDetect":
                    str = self.comPort.ser.read(39)
                    # data type 1
                    if int(str[0]) == gMeasureDataSet["Header"]:
                       # self.dataRxState["Mode"] = "AnlysPacket"

                       self.meterData["Vrms"] = self.StrToUintVRMS(str[1:5])
                       self.meterData["Irms"] = self.StrToUintIRMS(str[5:9])
                       self.meterData["Freq"] = self.StrToUintFR(str[9:11])
                       self.meterData["Pactive"] = self.StrToUintACTP(str[11:19])
                       self.meterData["Preactive"] = self.StrToUintREACTP(str[19:27])
                       self.meterData["Papparent"] = self.StrToUintAACTP(str[27:35])
                       self.meterData["Pfactor"] = self.StrToUintFACTOR(str[35:39])
                       self.meterData["Counter"] += 1
                       # crc = self.math_StrToUint(str[20:21])
                       datalist = [gMeasureDataSet["Header"]]
                       for i in range(len(str) - 1):
                           datalist.append(int(str[i]))

                       # crcCalcu = self.math_Crc8(datalist)
                       # if crc != crcCalcu:
                       #     self.meterData["PacketGetFlg"] = -1
                       # else:
                       # self.meterData["PacketGetFlg"] = 1
                       self.signalComData.emit(self.meterData)
                       if self.excelSaveFlg:
                           self.Excel_MeterDataHandle()
                       # self.dataRxState["Mode"] = "PacketDetect"
                       # self.signalComLog.emit("CRC is wrong! Noise may influence the communication speed.")
                    else:
                        # data Analysis wrong
                        self.dataRxState["Mode"] = "PacketDetect"

                # Analysis the data
                # elif self.dataRxState["Mode"] == "AnlysPacket":
                    # data type 1
                    # str = self.comPort.ser.read(gMeasureDataSet["Length"])



    def StrToUintVRMS(self, str):
        str = str[::-1]
        data = int(str.hex(),16)/16384
        data = round(data,3)
        return data
    def StrToUintIRMS(self, str):
        str = str[::-1]
        # data = str.hex()
        data = int(str.hex(),16)/16384
        data = round(data,3)

        return data
    def StrToUintFR(self, str):
        str = str[::-1]
        data = int(str.hex(),16)/100
        data = round(data,3)
        return data
    def StrToUintACTP(self, str):
        str = str[::-1]
        #print("a=" + str.hex())
        data = int(str.hex(), 16)
        if data>2**63-1:
            data = 2**64 - data
            data = 0-data
        #print(data)
        data = data/512
        data = round(data,2)
        return data
    def StrToUintREACTP(self, str):
        str = str[::-1]
        #print("c=" + str.hex())
        data = int(str.hex(), 16)
        #data = ~data
        if data>2**63-1:
            data = 2**64 - data
            data = 0-data
        #print(data)
        data = data/512
        data = round(data,3)
        return data
    def StrToUintAACTP(self, str):
        str = str[::-1]
        #print("b=" + str.hex())
        data = int(str.hex(), 16)
        if data>2**63-1:
            data = 2**64 - data
            data = 0-data
        #print(data)
        data = data/512
        data = round(data,3)
        return data
    def StrToUintFACTOR(self, str):
        str = str[::-1]
        #print(str.hex())
        # data = str.hex()
        data = int(str.hex(),16)/10000
        data = round(data,3)
        return data
    ########################################### Data handling ##############################################################
    def math_Crc8(self, datas):
        '''
        计算数据的CRC8校验码
        '''
        length = len(datas)
        crc = 0x00
        for i in range(length):
            if i == 0:
                crc = self.math_CalCrc(datas[0])  # 先计算第1个数据的CRC8
            else:
                crc = (crc ^ datas[i]) & 0xFF  # 其余的均将上次的CRC8结果与本次数据异或
                crc = self.math_CalCrc(crc)  # 再计算CRC8
        return crc & 0xFF

    def math_CalCrc(self, data):
        '''
        计算1字节数据的CRC8
        '''
        crc = data
        poly = 0x07  # 多项式x^8 + x^2 + x^1 + 1，即0x107，（根据原理）省略了最高位1而得0x07
        for i in range(8, 0, -1):
            if ((crc & 0x80) >> 7) == 1:  # 判断最高位是否为1，如果是需要异或，否则仅左移
                crc = (crc << 1) ^ poly
            else:
                crc = (crc << 1)
        return crc & 0xFF  # 计算后需要进行截取

    def math_StrToUint(self, str):
        if (len(str) == 1):
            data = struct.unpack('B', str)[0]
        elif (len(str) == 2):
            data = int(str[0]) * 256 + int(str[1])
        elif (len(str) == 4):
            data = int(str[0]) * 256 * 256 * 256 + int(str[1]) * 256 * 256 + int(str[2]) * 256 + int(str[3])
        elif (len(str) == 3):
            data = int(str[0]) * 256 * 256 + int(str[1]) * 256 + int(str[2])
        else:
            data = 0
        return data

    def math_StrToInt(self, str):
        datalist = struct.unpack('>h', str)
        return datalist[0]