# coding=utf-8
from ctypes import *
import threading
import time
import signal
import sys
import platform
import os


class iTek_CANFD_DEVICE_INFO(Structure):
    _fields_ = [
        ('hw_Version', c_uint8 * 3),
        ('fw_Version', c_uint8 * 3),
        ('product_Version', c_uint8 * 3),
        ('can_Num', c_uint8),
        ('str_Serial_Num', c_uint8 * 20),
        ('str_hw_Type', c_uint8 * 40),
        ('reserved', c_uint16 * 6)
    ]


class FilterData(Structure):
    _fields_ = [
        ('frameType', c_uint8),
        ('filterType', c_uint8),
        ('ID1', c_uint32),
        ('ID2', c_uint32)
    ]


class FilterDataExtend(Structure):
    _fields_ = [
        ('num', c_int),
        ('filterDataExtend', FilterData * 64)
    ]


class FilterDataStandard(Structure):
    _fields_ = [
        ('num', c_int),
        ('filterDataStandard', FilterData * 128)
    ]


class iTek_CANFD_CHANNEL_NIT_CONFIG(Structure):
    _fields_ = [
        ('can_type', c_uint8),
        ('CANFDStandard', c_uint8),
        ('CANFDSpeedup', c_uint8),
        ('abit_timing', c_uint32),
        ('dbit_timing', c_uint32),
        ('workMode', c_uint8),
        ('res', c_uint32),
        ('Extend', FilterDataExtend),
        ('Standard', FilterDataStandard)
    ]


class canfd_frame(Structure):
    _fields_ = [
        ('can_id', c_uint32),
        ('len', c_uint8),
        ('flags', c_uint8),
        ('res', c_uint8),
        ('cantype', c_uint8),
        ('data', c_uint8 * 64)
    ]


class iTek_CANFD_Receive_Data(Structure):
    _fields_ = [
        ('frame', canfd_frame),
        ('timestamp', c_uint64),
    ]


class iTek_CANFD_Transmit_Data(Structure):
    _fields_ = [
        ('frame', canfd_frame),
        ('send_type', c_uint16),
    ]


breakflag = 0


def exit_can(flag):
    timet = 0
    if flag != 1:
        global breakflag
        breakflag = 1
        while True:
            if breakflag == 2 or breakflag == 0:
                break
            time.sleep(1)
            timet = timet + 1
            if timet > 5:
                break

        breakflag = 0
    if flag >= 0 :
        SendTh.join()
    so.iTek_ResetCAN(CanHandle1)
    so.iTek_ResetCAN(CanHandle2)
    so.iTek_CloseDevice(dev_handle)
    if platform.system().lower() == 'linux':
        so.iTek_UsbExit()
    sys.exit()


def handler(signum, frame):
    exit_can(0)


def send_thread():
    TransmitData2 = iTek_CANFD_Transmit_Data*2

    TransmitData = TransmitData2()
    SendData = c_uint8 * 64

    data = SendData(1, 1, 2, 3, 4, 5, 6, 7)

    canid = 0

    # 循环发送can数据,canid自增及数据增加,用于测试展示
    while True:
        global breakflag
        if breakflag > 0:
            breakflag = 2
            return
        time.sleep(1)

        canid += 1
        data[7] += 1
        TransmitData[0].send_type = 0
        TransmitData[0].frame = canfd_frame(can_id=canid, len=8, flags=0, res=0, cantype=0, data=data)
        canid += 1
        TransmitData[1].send_type = 0
        TransmitData[1].frame = canfd_frame(can_id=canid, len=8, flags=0, res=0, cantype=0, data=data)
        ret = so.iTek_Transmit(CanHandle1, pointer(TransmitData), 2)
        print("send result:{}".format(ret))


if __name__ == '__main__':
    if platform.system().lower() == 'windows':
        # USBCAND.dll文件 需要放置在python二进制目录下
        so = CDLL("./iTekCANFD.dll")
    elif platform.system().lower() == 'linux':
        so = CDLL("./libiTekCANFD.so")
        print("iTek_UsbInit:", so.iTek_UsbInit())

    dev_handle = so.iTek_OpenDevice(2, 0, 0)
    if dev_handle <= 0:
        print("打开设备失败")
        exit (0)

    DevInfo = iTek_CANFD_DEVICE_INFO()
    # 读取设备信息
    so.iTek_GetDeviceInfo(dev_handle, pointer(DevInfo))
    print("硬件版本:V{}.{}.{}".format(DevInfo.hw_Version[0], DevInfo.hw_Version[1], DevInfo.hw_Version[2]))
    print("固件版本:V{}.{}.{}".format(DevInfo.fw_Version[0], DevInfo.fw_Version[1], DevInfo.fw_Version[2]))
    print("通道数量:{}".format(DevInfo.can_Num))
    print("出厂序列号:{}".format(bytes(DevInfo.str_Serial_Num).decode('utf-8')))
    print("设备名称描述字符:{}".format(bytes(DevInfo.str_hw_Type).decode('utf-8')))

    # can通道参数设置
    Can1Standard = FilterDataStandard()
    Can1Standard.num = 1
    Can1Standard.filterDataStandard[0] = FilterData(frameType=0, filterType=0, ID1=0x00, ID2=0x7ff)

    Can1Extend = FilterDataExtend()
    Can1Extend.num = 1
    Can1Extend.filterDataExtend[0] = FilterData(frameType=1, filterType=0, ID1=0x00, ID2=0x1FFFFFFF)

    can1 = iTek_CANFD_CHANNEL_NIT_CONFIG()
    can1.can_type = 1
    can1.CANFDStandard = 0
    can1.CANFDSpeedup = 0
    can1.workMode = 0
    can1.abit_timing = 0x00181302
    can1.dbit_timing = 0x001F0A20
    can1.Standard = Can1Standard
    can1.Extend = Can1Extend

    Can2Standard = FilterDataStandard()
    Can2Standard.num = 1
    Can2Standard.filterDataStandard[0] = FilterData(frameType=0, filterType=0, ID1=0x00, ID2=0x7ff)

    Can2Extend = FilterDataExtend()
    Can2Extend.num = 1
    Can2Extend.filterDataExtend[0] = FilterData(frameType=1, filterType=0, ID1=0x00, ID2=0x1FFFFFFF)

    can2 = iTek_CANFD_CHANNEL_NIT_CONFIG()
    can2.can_type = 1
    can2.CANFDStandard = 0
    can2.CANFDSpeedup = 0
    can2.workMode = 0
    can2.abit_timing = 0x00181302
    can2.dbit_timing = 0x001F0A20
    can2.Standard = Can2Standard
    can2.Extend = Can2Extend

    # 初始化can通道，linux window定义的接口函数不一致，这里区分
    if platform.system().lower() == 'windows':
        CanHandle1 = so.iTek_InitCAN(dev_handle, 0, pointer(can1))
    elif platform.system().lower() == 'linux':
        CanHandle1 = so.iTek_InitCan(dev_handle, 0, pointer(can1))

    if CanHandle1 == 0:
        print("初始化can0失败")
        exit_can(0)

    if platform.system().lower() == 'windows':
        CanHandle2 = so.iTek_InitCAN(dev_handle, 1, pointer(can2))
    elif platform.system().lower() == 'linux':
        CanHandle2 = so.iTek_InitCan(dev_handle, 1, pointer(can2))

    if CanHandle2 == 0:
        print("初始化can1失败")
        exit_can(0)

    # 启动can通道
    if so.iTek_StartCAN(CanHandle1) == 0:
        print("启动can0失败")
        exit_can(0)

    if so.iTek_StartCAN(CanHandle2) == 0:
        print("启动can1失败")
        exit_can(0)

    # 信号捕获操作，用于ctrl+c退出时也能让can设备正常退出
    signal.signal(signal.SIGINT, handler)

    # 创建发送线程用于测试发送功能
    SendTh = threading.Thread(target=send_thread)
    SendTh.start()

    RECVDATA = iTek_CANFD_Receive_Data * 5
    recvdata = RECVDATA()
    # 循环接收并打印接收到的信息
    while True:
        recvnum = so.iTek_Receive(CanHandle2, recvdata, len(recvdata), 1000)
        for i in range (0, recvnum):
            print ("[%d]recvtime:[%lu] canid:[%d] cantype:[%d] datalen:[%d] data:[" %(i, recvdata[i].timestamp, recvdata[i].frame.can_id, recvdata[i].frame.cantype, recvdata[i].frame.len) , end='')
            for j in range (0, recvdata[i].frame.len) :
                print ("%02x " %(recvdata[i].frame.data[j]), end='')
            print ("]")
    exit_can(0)
