import os
import sys
import datetime
from datetime import datetime, timedelta
import prottcp as prot
from PyQt5.QtCore import *
from PyQt5.QtGui import *
from PyQt5.QtWidgets import *
import threading
import time
import serial
import serial.tools.list_ports
import coder_params as cpars
import coder_check as chk




# 批检仪赋码上位机


STR_RED="\033[1;31m"
STR_BLUE="\033[1;34m"
STR_END="\033[0m"



# 获取北京时间
def get_date():
    now_time = datetime.utcnow()
    utc_time = now_time + timedelta(hours=8)  # UTC只是比北京时间提前了8个小时
    utc_time = utc_time.strftime("%Y%m%d")
    return utc_time

# 获取北京时间
def get_time():
    now_time = datetime.utcnow()
    utc_time = now_time + timedelta(hours=8)  # UTC只是比北京时间提前了8个小时
    utc_time = utc_time.strftime("%Y-%m-%d %H:%M:%S")
    return utc_time




class coder(QObject):

    def __init__(self) -> None:
        QObject.__init__(self)
        self.app = QApplication(sys.argv)
        self.code_id=0
        self.run_times=0
        self.slave_num=10
        self.code_list_lock=[]
        self.save_file="code_list.csv"
        self.ser_is_open = False
        self.ser=prot.protu()
        self.widget = QWidget()
        self.widget.resize(1500, 800)
        self.widget.setWindowTitle("批检仪赋码工具")
        self.com_but_init()
        self.combsp_init()
        self.com_init()
        self.code_list_init()
        self.dot_fat_init()
        self.channel_init()
        self.sig_list_init()
        self.moterup_init()
        self.infotext_init()
        self.widget.destroyed.connect(self.quit)

    def quit(self):
        self.close_serial()
        print("app exit.")
        # 程序退出
        sys.exit(1)

    # 初始化
    def init(self,detonator_fat:bytearray,chip_fat:bytearray,signature_code:bytearray,slave_num:int):
        self.set_params(detonator_fat,chip_fat,signature_code,slave_num)
        date=get_date().encode('utf-8')
        self.date=date[-5:]
        self.year=date[:4]
        self.save_file=date.decode("utf-8")+".csv"
        if(os.path.exists(self.save_file)):
            with open(self.save_file) as f:
                lines=f.readlines()
                length=len(lines)
                self.code_id=length
                for i in lines:
                    self.code_list_add(i)
        print("code id start as:",self.code_id)
        self.code_list_to_bottom()

    # 设置雷管厂，芯片厂，特征码
    def set_params(self,detonator_fat:bytearray,chip_fat:bytearray,signature_code:bytearray,slave_num:int):
        self.detonator_fat=detonator_fat
        self.chip_fat=chip_fat
        self.slave_num=slave_num
        self.signature_code=signature_code
        info="已更换编码参数，现在的编码参数为："+"雷管厂:"+detonator_fat.decode("utf-8")+",芯片厂:"+chip_fat.decode("utf-8")
        info+=",特征码:"+signature_code.decode("utf-8")+",通道数:"+str(slave_num)
        self.set_infotext(info)

        # 选择波特率
    def combsp_init(self):
        self.combsp = QComboBox(self.widget)
        self.combsp.setObjectName(u"combsp")
        self.combsp.setGeometry(QRect(465, 10, 85, 25))
        self.combsp.setEditable(True)
        self.combsp.currentIndexChanged.connect(self.com_changed)
        for i in cpars.uartbsp_list():
            self.combsp.addItem(i)
        self.combsp_label = QLabel(self.widget)
        self.combsp_label.setObjectName(u"label")
        self.combsp_label.setGeometry(QRect(405, 16, 72, 15))
        self.combsp_label.setText("波特率:")

        # 选择雷管厂家
    def dot_fat_init(self):
        self.dot_fat = QComboBox(self.widget)
        self.dot_fat.setObjectName(u"dot_fat")
        self.dot_fat.setGeometry(QRect(645, 10, 400, 25))
        self.dot_fat.setEditable(False)
        self.dot_fat.currentIndexChanged.connect(self.params_changed)
        for i in cpars.dot_fat_list():
            self.dot_fat.addItem(i)
        self.dot_fat_label = QLabel(self.widget)
        self.dot_fat_label.setObjectName(u"label")
        self.dot_fat_label.setGeometry(QRect(570, 16, 72, 15))
        self.dot_fat_label.setText("雷管厂家:")

        # com口
    def com_init(self):
        self.com = QComboBox(self.widget)
        self.com.setObjectName(u"com")
        self.com.setGeometry(QRect(95, 10, 300, 25))
        self.com.setEditable(True)
        self.com.currentIndexChanged.connect(self.com_changed)
        # self.com.addItem("utcp:7777")
        ports_list = list(serial.tools.list_ports.comports())
        for comport in ports_list:
            # print(comport.name,comport.description)
            self.com.addItem(comport.name+":"+comport.description)
        self.com_label = QLabel(self.widget)
        self.com_label.setObjectName(u"label")
        self.com_label.setGeometry(QRect(30, 16, 72, 15))
        self.com_label.setText("COM口:")

        # 特征码
    def sig_list_init(self):
        self.sig_list = QComboBox(self.widget)
        self.sig_list.setObjectName(u"sig_list")
        self.sig_list.setGeometry(QRect(1285, 10, 85, 25))
        self.sig_list.setEditable(False)
        self.sig_list.currentIndexChanged.connect(self.params_changed)
        for i in cpars.code_signature_code_list():
            self.sig_list.addItem(i)
        self.sig_list_label = QLabel(self.widget)
        self.sig_list_label.setObjectName(u"label")
        self.sig_list_label.setGeometry(QRect(1220, 16, 72, 15))
        self.sig_list_label.setText("特征码:")

        # 通道数
    def channel_init(self):
        self.channel = QComboBox(self.widget)
        self.channel.setObjectName(u"channel")
        self.channel.setGeometry(QRect(1125, 10, 85, 25))
        self.channel.setEditable(True)
        self.channel.currentIndexChanged.connect(self.params_changed)
        for i in cpars.code_channel_list():
            self.channel.addItem(i)
        self.channel_label = QLabel(self.widget)
        self.channel_label.setObjectName(u"label")
        self.channel_label.setGeometry(QRect(1055, 16, 72, 15))
        self.channel_label.setText("通道数:")

        # 提示信息
    def  infotext_init(self):
        self.infotext=QLabel(self.widget)
        self.infotext.setGeometry(QRect(30, 765, 1200, 15))
        self.infotext.setText("请打开串口以开始赋码")
    def set_infotext(self,text:str):
        try:
            self.infotext.setText(text)
        except Exception as e:
            pass

        # 初始化打开端口按钮
    def com_but_init(self):
        self.com_but = QPushButton(self.widget)
        self.com_but.setObjectName(u"com_but")
        self.com_but.setGeometry(QRect(1250, 50, 93, 28))
        self.com_but.setText("打开端口")
        self.com_but.clicked.connect(self.com_but_clicked)

        # 电机上升
    def moterup_init(self):
        self.moterup=QPushButton(self.widget)
        self.moterup.setObjectName(u"moteerup")
        self.moterup.setGeometry(QRect(1250, 90, 93, 28))
        self.moterup.setText("电机上升")
        self.moterup.clicked.connect(self.cmd_moter_up)

        # 注码结果列表
    def code_list_init(self):
        self.code_list=QTableWidget(self.widget)
        self.code_list.setObjectName(u"code_list")
        self.code_list.setGeometry(QRect(30, 50, 1200, 700))
        self.code_list.setColumnCount(6)
        self.code_list.setHorizontalHeaderLabels(list(["创建时间","通道号","注码结果","管壳码","UID","密码"]))
        self.code_list.horizontalHeader().setSectionResizeMode(QHeaderView.ResizeMode.Stretch)

        # 添加数据
    def code_list_add(self,code_line:str):
        code_strs=code_line.split(",")
        errcode=int(code_strs[2])
        code_strs[2]=cpars.code_errinfo(errcode)
        RowCont=self.code_list.rowCount()
        self.code_list.insertRow(RowCont)
        for i in range(6):
            self.code_list.setItem(RowCont,i,QTableWidgetItem(code_strs[i]))
            if(errcode!=0):
                self.code_list.item(RowCont,i).setBackground(Qt.GlobalColor.red)
        # 滚动到底部
    def code_list_to_bottom(self):
        self.code_list.scrollToBottom()

    def com_but_clicked(self):
        print("com but clicked")
        if(self.ser_is_open==False):
            item=self.com.itemText(self.com.currentIndex())
            bsp=self.combsp.itemText(self.combsp.currentIndex())
            com=item.split(":")[0]
            if(com!="utcp"):
                item=com+":"+bsp
            self.open_serial(item)
        else:
            self.close_serial()
    def set_port_state(self,state:bool):
        self.ser_is_open=state
        if(state==True):
            self.com_but.setText("关闭端口")
        else:
            self.com_but.setText("打开端口")

    # 无效化编码参数设置项
    def params_active(self,power:bool):
        self.dot_fat.setEnabled(power)
        self.sig_list.setEnabled(power)
        self.channel.setEnabled(power)

    # 更换管壳码生成参数
    def params_changed(self,index:int):
        try:
            dot_fat=self.dot_fat.itemText(self.dot_fat.currentIndex()).split(":")[0].encode("utf-8")
            chip_fat=b"AD"
            sig_code=self.sig_list.itemText(self.sig_list.currentIndex()).encode("utf-8")
            slave_num=self.channel.itemText(self.channel.currentIndex())
            self.set_params(dot_fat,chip_fat,sig_code,int(slave_num))
        except Exception as e:
            pass

    # 更换端口时关闭之前的
    def com_changed(self,index:int):
        print("com changed")
        self.set_infotext("串口参数有变动，将自动关闭打开的串口")
        self.close_serial()
    # 打开通信接口
    def open_serial(self,com:str):
        if(self.ser.init(com)==True):
            self.ser.recv_signal.connect(self.recv_slot)
            self.ser.start_recv()
            self.set_port_state(True)
            print("open serial.")
            self.set_infotext("打开串口成功，开始监听请求赋码指令")
        else:
            self.set_infotext("打开串口失败，是否有其他程序占用了串口？")
            print("open serial failed.")
    def close_serial(self):
        try:
            self.ser.recv_signal.disconnect(self.recv_slot)
        except Exception as a:
            pass
        self.ser.close()
        self.set_infotext("串口已关闭")
        self.set_port_state(False)

    def recv_slot(self,cmd:int,data:bytearray,err:str):
        print("recv:","cmd:",hex(cmd),"data:",data.hex(' '))
        if(self.run_times<=0):
            print("run times end.")
            self.set_infotext("运行次数已结束，不再响应检测赋码指令")
            return
        self.run_times-=1
        ack=False
        if(cmd==0x81):
            self.set_infotext("已接收检测结果")
            ack=self.decode_check(data)
            if(ack==True):
                self.cmd_code(self.slave_num)
            else:
                print("check failed.")
                self.set_infotext("检测出现异常,未进行赋码，请排查异常后重试")
                self.cmd_moter_up()
                self.params_active(True)
        elif(cmd==0x82):
            self.set_infotext("已接收赋码结果")
            ack=self.decode_code(data)
            if(ack!=True):
                print("code failed.")
                self.set_infotext("赋码出现异常，本次赋码结果被丢弃，请排查异常后重试")
            self.cmd_moter_up()
            self.params_active(True)
        elif(cmd==0x37):
            print("key press.")
            self.params_active(False)
            self.set_infotext("已接收请求检测命令")
            self.cmd_moter_down()
            time.sleep(2)
            self.cmd_check(self.slave_num)


    def run(self,times:int):
        self.run_times=times*4
        self.widget.show()
        ret=self.app.exec()
        self.close_serial()
        sys.exit(ret)

    # 生成管壳码
    def calc_shell_code(self,id:int):
        shell=bytearray()
        shell+=self.detonator_fat
        shell+=self.date
        shell+=self.signature_code
        shell+="{d:05d}".format(d=id).encode("utf-8")
        return shell

    # 生成n通道的注码数据
    def creat_code_data(self,id:int,num:int):
        d=bytearray()
        d.append(num&0xff)
        d+=self.year
        for i in range(num):
            d.append(i&0xff)
            d+=self.calc_shell_code(id+i)
        return d
    # 发送检测命令
    def cmd_check(self,num:int):
        d=bytearray()
        d.append(num&0xff)
        d.append(0)
        self.ser.send(1,d)
        print("check start.")
    # 电机上升
    def cmd_moter_up(self):
        d=bytearray()
        d.append(0x02)
        try:
            self.ser.send(0x40,d)
        except Exception as e:
            self.set_infotext("发送命令失败，是否没有打开串口？")
        print("moter up.")
    # 电机下降
    def cmd_moter_down(self):
        d=bytearray()
        d.append(0x03)
        self.ser.send(0x40,d)
        print("moter down.")

    # 发送赋码指令
    def cmd_code(self,num:int):
        self.ser.send(2,self.creat_code_data(self.code_id,num))
        print("code start.")

    # 解析检测结果
    def decode_check(self,data:bytearray):
        num=data[0]
        ack=True
        acks_list=[]
        for i in range(num):
            ack_i=data[6*i+3]
            str_start=""
            if(ack_i!=0):
                ack=False
                str_start=STR_RED
            print(str_start+"addr:",int(data[6*i+2]),"ack:",ack_i,STR_END)
            acks_list.append(str(ack_i))
        try:
            self.ch_dlog.close()
            self.co_dlog.close()
        except Exception as e:
            pass
        if(ack!=True):
            self.ch_dlog=chk.check_dlog(self.widget,self.slave_num)
            self.ch_dlog.ack_list_init(acks_list)
            self.ch_dlog.show()
        return ack
    
    # 解析注码结果
    def decode_code(self,data:bytearray):
        num=data[0]
        ack=True
        code_list=[]
        # 生成注码结果数据
        for i in range(num):
            ack_i=data[39*i+2]
            str_start=""
            d=data[39*i+3:39*i+3+38]
            shell_code=d[:13].decode("utf-8")
            uid_code=d[13:13+16].decode("utf-8")
            psw_code=d[13+16:13+16+8].decode("utf-8")
            code_list.append(((i+1),ack_i,shell_code,uid_code,psw_code))

        # 统计错误信息
        length=len(code_list)
        if(len(self.code_list_lock)==0):
            # 第一次失败，保存失败数据
            self.code_list_lock=code_list
        else:
            # 不是第一次失败，比对失败数据，把成功过的统计出来
            if (len(self.code_list_lock)>length):
                self.code_list_lock=self.code_list_lock[:length]
            for i in range(length):
                # 长度不足的地方填充为本次的结果
                if(i>=len(self.code_list_lock)):
                    self.code_list_lock.append(code_list[i])
                if(self.code_list_lock[i][0]==code_list[i][0]):
                    if(code_list[i][1]==0):
                        self.code_list_lock[i]=code_list[i]
        # 校验注码结果
        acks_list=[]
        for i in self.code_list_lock:
            str_start=""
            ack_i=i[1]
            if(ack_i!=0):
                ack=False
                str_start=STR_RED
            print(str_start+"addr:",i[0],"ack:",ack_i,STR_END)
            print("\t","shell_code:",i[2])
            print("\t","uid_code:",i[3])
            print("\t","psw_code:",i[4])
            acks_list.append(str(ack_i))
        if(ack==True):
            s=""
            time=get_time()
            for i in self.code_list_lock:
                s=time+","+str(i[0])+','+str(i[1])+','+i[2]+','+i[3]+','+i[4]
                self.code_list_add(s)
                with open(self.save_file,"+a") as f:
                    f.write(s+'\n')
            self.code_list_to_bottom()
            print("save to file:",self.save_file)
            # 全部成功，清除错误记录
            self.code_list_lock.clear()
            self.code_id+=self.slave_num
        try:
            self.co_dlog.close()
        except Exception as e:
            pass
        if(ack!=True):
            self.co_dlog=chk.check_dlog(self.widget,self.slave_num,"注码结果查看")
            self.co_dlog.ack_list_init(acks_list)
            self.co_dlog.show()
        return ack

if __name__ == "__main__":
    c=coder()
    c.init(b'00',b'AD',b'A',10)
    c.run(5000)

