Browse Source

TV串口类、CA410串口类、gamma算法类、bat安装py第三库。

JeffWang 2 years ago
parent
commit
99c7a6c04c
9 changed files with 1608 additions and 0 deletions
  1. 337 0
      Python/autoGamma.py
  2. 145 0
      Python/baseSerail.py
  3. 182 0
      Python/caSerail.py
  4. 260 0
      Python/cfg.txt
  5. 307 0
      Python/gamma-tools.py
  6. 303 0
      Python/mokaSerail.py
  7. 40 0
      Python/pip_install.bat
  8. 33 0
      Python/pip_rinstall.bat
  9. 1 0
      Python/pyinstall.bat

+ 337 - 0
Python/autoGamma.py

@@ -0,0 +1,337 @@
+"""
+author: chaohui.yang
+date: 2022-09-07
+"""
+import math
+from abc import abstractmethod, ABCMeta
+from datetime import datetime
+import numpy as np
+from matplotlib.ticker import MultipleLocator
+from scipy import interpolate
+import xlrd
+import json
+import jsbeautifier
+import matplotlib.pyplot as plt
+
+np.set_printoptions(precision=5)
+np.set_printoptions(suppress=True)
+
+
+def readDataFromExcel(filePath: str):
+    """
+    从EXCEL中读取目标x,y以及gamma值,以及测量的XYZ值
+    :param filePath: ecxel路径
+    :return: 转换关系矩阵RGB_2_XYZ, 包含11组测量值的矩阵XYZ_11, 包含11个测量值索引的数组XYZ11_index,target_x, target_y, target_gamma
+    """
+    workbook = xlrd.open_workbook(filePath)
+    sheet = workbook.sheets()[0]
+    # 使用表格中定义的target_x,target_y,以及target_gamma值
+    target_x = sheet.cell_value(8, 1)
+    target_y = sheet.cell_value(9, 1)
+    target_gamma = sheet.cell_value(10, 1)
+    print(f"从表格读取到:\ntarget_x = {target_x}\ntarget_y = {target_y}\ntarget_gamma = {target_gamma}\n")
+    XYZ11_index = sheet.row_values(1, 1)[3:14]
+    rows_X = sheet.row_values(2, 1)[0:14]  # 获取第X行内容
+    rows_Y = sheet.row_values(3, 1)[0:14]  # 获取第Y行内容
+    rows_Z = sheet.row_values(4, 1)[0:14]  # 获取第Z行内容
+    XYZ_all = [[rows_X[i], rows_Y[i], rows_Z[i]] for i in range(len(rows_X))]
+    XYZ_11 = np.array(XYZ_all[3:14]).T
+    RGB_2_XYZ = np.array(XYZ_all[0:3]).T  # np.array([XYZ[0], XYZ[1], XYZ[2]])
+    return RGB_2_XYZ, XYZ_11, XYZ11_index, target_x, target_y, target_gamma
+
+
+def cal_target_XYZ11(XYZ_2_RGB, target_x, target_y, target_gamma):
+    """
+    根据“转换关系”矩阵XYZ_2_RGB,计算出11个目标XYZ值
+    :param XYZ_2_RGB: 表示“转换关系”的矩阵,也就是测量而得的R/G/B对应的XYZ值所合成的矩阵(3X3)
+    :param target_x:
+    :param target_y:
+    :param target_gamma:
+    :return: targetXYZ11:包含11个目标XYZ值的矩阵(3X11)
+    """
+    print("以下求最大可行目标亮度值....")
+    targetY_list = []
+    for i in range(0, 3):
+        targetY = 1 / (XYZ_2_RGB[i][0] * target_x / target_y + XYZ_2_RGB[i][1] + XYZ_2_RGB[i][2] * (
+                1 - target_x - target_y) / target_y)
+        targetY_list.append(targetY)
+        print(i + 1, "式子的解:targetY = ", targetY)
+    targetY_max = round(min(targetY_list), 5)
+    print("最大可行目标亮度值: targetY_max = ", targetY_max)
+    targetXYZ11 = np.zeros((3, 11))
+    # print("targetXYZ11: \n", targetXYZ11)
+    # 算出11个target_XYZ
+    for i in range(0, 11):
+        targetXYZ11[1][i] = targetY_max * ((1 - 0.1 * i) ** target_gamma)
+        targetXYZ11[0][i] = (targetXYZ11[1][i] * target_x) / target_y
+        targetXYZ11[2][i] = targetXYZ11[1][i] * (1 - target_x - target_y) / target_y
+    # print("11个目标target_XYZ值: \n", targetXYZ11)
+    return targetXYZ11
+
+
+# 插值方法
+def interpolate_to_256(ori_matrix, ori_matrix_index):
+    """
+    插值算法。根据输入数组和对应元素序列,插值使得最终为256个元素
+    :param ori_matrix: 输入矩阵(n x m),通常每行分别对应R/G/B值或X/Y/Z值
+    :param ori_matrix_index: 对应每列矩阵元素的索引值序列(1 x m)
+    :return: output_matrix: 输出插值后的矩阵(nx256)
+    """
+    # print(f"待扩充矩阵序列间隔为ori_index :\n{ori_matrix_index}")
+    # print("待扩充矩阵为: \n", ori_matrix)
+    new_index = np.arange(0, 256, 1)
+    # 插值方式kind = linear|slinear|quadratic|cubic
+    f = interpolate.interp1d(ori_matrix_index, ori_matrix, kind="linear")
+    output_matrix = f(new_index)
+    # print("扩充后的矩阵为  :\n", output_matrix)
+    return output_matrix
+
+
+def cal_RGB_by_XYZ(transMatrix, matrix_XYZ):
+    """
+    根据逆矩阵和XYZ计算RGB
+    :param transMatrix: RGB TO XYZ的转换矩阵逆矩阵(3X3)
+    :param matrix_XYZ: XYZ矩阵(3X3)
+    :return: RGB矩阵(3X3)
+    """
+    matrix_RGB = np.dot(transMatrix, matrix_XYZ)
+    return matrix_RGB
+
+
+def calActualRGB11(targetRGB11, targetXYZ11, XYZ256):
+    """
+    根据11个目标RGB值、目标XYZ值以及测量再插值而得的256个XYZ,算出实际需要的11个RGB值
+    :param targetRGB11: 包含11个目标RGB值的3x11矩阵
+    :param targetXYZ11: 包含11个目标XYZ值的3x11矩阵
+    :param XYZ256: 包含256个由测量的XYZ值的3x256矩阵
+    :return: actualRGB11:包含11个实际需要的RGB值的3x11矩阵
+    """
+    print(f"以下开始计算实际需要的11组RGB值...\n")
+    actualRGB11 = np.zeros([3, 11])
+    targetRGB11 = targetRGB11.T[::-1].T
+    print("翻转的targetRGB11 is: ", targetRGB11)
+    targetXYZ11 = targetXYZ11.T[::-1].T
+    print("翻转的targetXYZ11 is: ", targetXYZ11)
+    for n in range(0, 3):
+        for m in range(1, 11):
+            distances = []
+            for index in range(0, 256):
+                distance = abs(XYZ256[n, index] - targetXYZ11[n, m])
+                distances.append(distance)
+            # print(f"targetXYZ11[{n}][{m}]对应的XYZ256的距离为:\n{distances}")
+            min_index = distances.index(min(distances))
+            print(f"目标点targetXYZ11[{n}][{m}] , 对应XYZ256中最短距离的元素值index为: {min_index}")
+            # 当min_index刚好为首、尾index时无需参与gamma计算,直接取对应的target值
+            if min_index == 0 or min_index == 255:
+                actualRGB11[n, m] = targetRGB11[n, m]
+                print(f"该点不计算panel gamma值\n")
+            else:
+                panelGamma = round(math.log((XYZ256[n, min_index]) / XYZ256[n, 255]) / math.log((min_index) / 255), 5)
+                print(f"对应targetXYZ11[{n}][{m}]最短距离的XYZ256[{n}][{min_index}]的panel gamma值为:{panelGamma}\n")
+                # 当panelGamma算出为0时,无法数学计算直接取对应的target值
+                if panelGamma == 0:
+                    actualRGB11[n, m] = targetRGB11[n, m]
+                else:
+                    actualRGB11[n, m] = (targetRGB11[n, m]) ** (1 / panelGamma)
+        actualRGB11[n] = actualRGB11[n][::-1]
+    # print("实际需要的11组RGB值为 : \n", actualRGB11)
+    return actualRGB11
+
+
+def showRGBcurve(RGB255):
+    """
+    根据计RGB255点绘出R/G/B三条曲线
+    :param RGB255: 最终计算而得的RGB255数组(3X256),每行分别对应R/G/B的256个值
+    """
+    x = np.arange(0, 256)
+    plt.title("RGB255")
+    # x轴的刻度间隔
+    x_major_locator = MultipleLocator(15)
+    # y轴的刻度间隔
+    y_major_locator = MultipleLocator(10)
+    # ax为两条坐标轴的实例
+    ax = plt.gca()
+    ax.xaxis.set_major_locator(x_major_locator)
+    ax.yaxis.set_major_locator(y_major_locator)
+    plt.plot(x, RGB255[0], label="R", color='red')
+    plt.plot(x, RGB255[1], label="G", color='green')
+    plt.plot(x, RGB255[2], label="B", color='blue')
+    plt.legend()
+    plt.show()
+
+
+class RGBcalculator(metaclass=ABCMeta):
+    @abstractmethod
+    def calRGB255(self, filePath: str):
+        pass
+
+
+class Firetv_RGBcalculator(RGBcalculator):
+    target_x = 0
+    target_y = 0
+    target_gamma = 2.2
+    RGB_2_XYZ = np.zeros((3, 3))
+    XYZ_11 = np.zeros((3, 11))
+    XYZ11_index = [255, 229, 204, 178, 153, 127, 102, 76, 51, 26, 0]
+
+    def calRGB255(self, FilePath: str):
+        # 从excel中读取“转换矩阵”,“11组测量的XYZ”,以及“11组XYZ对应的坐标索引”
+        self.RGB_2_XYZ, self.XYZ_11, selfXYZ11_index, self.target_x, \
+        self.target_y, self.target_gamma = readDataFromExcel(FilePath)
+        print("转换矩阵RGB_to_XYZ为:\n", self.RGB_2_XYZ)
+        XYZ_2_RGB = np.linalg.pinv(self.RGB_2_XYZ)
+        print("转换逆矩阵XYZ_2_RGB为:\n", XYZ_2_RGB)
+        targetXYZ11 = cal_target_XYZ11(XYZ_2_RGB, self.target_x, self.target_y, self.target_gamma)
+        print("11个目标target_XYZ值: \n", targetXYZ11)
+        # 算出11组目标RGB
+        targetRGB11 = cal_RGB_by_XYZ(XYZ_2_RGB, targetXYZ11)
+        print("11个目标targetRGB11值: \n", targetRGB11)
+        # 对测量出来的XYZ11做插值运算
+        XYZ256 = interpolate_to_256(self.XYZ_11, self.XYZ11_index)
+        # XYZ256 = interpolate3d_to_256(XYZ_11, XYZ11_index)
+        print(f"测量的11个XYZ11为:\n {self.XYZ_11}")
+        print(f"对XYZ11插值扩展后为XYZ256:\n {XYZ256}")
+        ActualRGB11 = calActualRGB11(targetRGB11, targetXYZ11, XYZ256)
+        print("实际需要的11个RGB值: \n", ActualRGB11)
+        # 对实际需要的RGB11做插值运算得到最终的RGB256
+        ActualRGB256 = interpolate_to_256(ActualRGB11, self.XYZ11_index)
+        ActualRGB256 = np.multiply(ActualRGB256, 255)  # 去归一化,以RGB-255显示
+        ActualRGB256 = np.around(ActualRGB256, 4)  # 小数点保留4位
+        print(f"最终需要的256组RGB值: \n{ActualRGB256}")
+        return ActualRGB256
+
+    def calRGB255_for_Debug(self, new_XYZ_of_RGB, new_XYZ_11, new_XYZ11_index, new_target_x,
+                            new_target_y, new_target_gamma):
+        """
+
+        :param new_XYZ_of_RGB: 3X3矩阵
+        :param new_XYZ_11:  3X11矩阵
+        :param new_XYZ11_index: 1X11数组
+        :param new_target_x:
+        :param new_target_y:
+        :param new_target_gamma:
+        :return:
+        """
+        self.target_x = new_target_x
+        self.target_y = new_target_y
+        self.target_gamma = new_target_gamma
+        # “转换矩阵”,“11组测量的XYZ”,以及“11组XYZ对应的`Z坐标索引”
+        self.RGB_2_XYZ = new_XYZ_of_RGB
+        self.XYZ_11 = new_XYZ_11
+        self.XYZ11_index = new_XYZ11_index
+        print(f"获取到:\ntarget_x = {self.target_x}\ntarget_y = {self.target_y}\ntarget_gamma = {self.target_gamma}\n")
+        print("转换矩阵RGB_to_XYZ为:\n", self.RGB_2_XYZ)
+        XYZ_2_RGB = np.linalg.pinv(self.RGB_2_XYZ)
+        print("转换逆矩阵XYZ_2_RGB为:\n", XYZ_2_RGB)
+        targetXYZ11 = cal_target_XYZ11(XYZ_2_RGB, self.target_x, self.target_y, self.target_gamma)
+        print("11个目标target_XYZ值: \n", targetXYZ11)
+        # 算出11组目标RGB
+        targetRGB11 = cal_RGB_by_XYZ(XYZ_2_RGB, targetXYZ11)
+        print("11个目标targetRGB11值: \n", targetRGB11)
+        # 对测量出来的XYZ11做插值运算
+        XYZ256 = interpolate_to_256(self.XYZ_11, self.XYZ11_index)
+        # XYZ256 = interpolate3d_to_256(XYZ_11, XYZ11_index)
+        print(f"测量的11个XYZ11为:\n {self.XYZ_11}")
+        print(f"对XYZ11插值扩展后为XYZ256:\n {XYZ256}")
+        ActualRGB11 = calActualRGB11(targetRGB11, targetXYZ11, XYZ256)
+        print("实际需要的11个RGB值: \n", ActualRGB11)
+
+        ActualXYZ11 = np.dot(XYZ_2_RGB, ActualRGB11)
+        print(f"实际需要的11组XYZ值: \n{ActualXYZ11}")
+        # 对实际需要的RGB11做插值运算得到最终的RGB256
+        ActualRGB256 = interpolate_to_256(ActualRGB11, self.XYZ11_index)
+        # showRGBcurve(np.multiply(ActualRGB256, 255))
+        ActualRGB256 = np.multiply(ActualRGB256, 1023)  # 去归一化,以RGB-255显示
+        ActualRGB256 = np.around(ActualRGB256, 0).astype(int)  # 小数点保留4位
+
+        ActualXYZ256 = np.dot(XYZ_2_RGB, ActualRGB256)
+        print(f"最终需要的256组XYZ值: \n{ActualXYZ256}")
+        print(f"最终需要的256组RGB值: \n{ActualRGB256}")
+        return ActualRGB256
+
+
+def dumpRGB2ini(RGB255):
+    """
+    功能:将最终计算的RGB255值输出至文本,供机芯调用.该格式的ini暂时用不到
+    :param RGB255:最终计算的RGB255数组(3X256),每行分别对应R/G/B的256个值
+    """
+    wb_mode = {}
+    list = ["COLD", "STANDARD", "STANDARD2", "WARM", "WARM2", "SDR2HDR COLD", "SDR2HDR STANDARD", "SDR2HDR WARM"]
+    for str in list:
+        wb_mode[str] = [{"R_VALUE": []}, {"G_VALUE": []}, {"B_VALUE": []}]
+        wb_mode[str][0]["R_VALUE"] = RGB255[0, :].tolist()
+        wb_mode[str][1]["G_VALUE"] = RGB255[1, :].tolist()
+        wb_mode[str][2]["B_VALUE"] = RGB255[2, :].tolist()
+    # print("wb_mode: \n", wb_mode)
+    options = jsbeautifier.default_options()
+    options.indent_size = 2
+    json_dict = jsbeautifier.beautify(json.dumps(wb_mode), options)
+    print(json_dict)
+    filename = 'gamma.ini'
+    with open(filename, 'w', encoding='utf-8') as f:
+        f.write(json_dict)
+    print(f"gamma文件已保存至{filename}")
+
+
+def dumpRGB2ini_commom(RGB255):
+    """
+    功能:将最终计算的RGB255值按一定格式输出至文本,供机芯调用.该格式比较通用
+    :param RGB255: 最终计算的RGB255数组(3X256),每行分别对应R/G/B的256个值
+    """
+    RGB_list = []
+    RGB_reg = "{0x000,0x010,0x020,0x030,0x040,0x050,0x060,0x070,0x080,0x090,0x0A0,0x0B0,0x0C0,0x0D0,0x0E0,0x0F0,\
+0x100,0x110,0x120,0x130,0x140,0x150,0x160,0x170,0x180,0x190,0x1A0,0x1B0,0x1C0,0x1D0,0x1E0,0x1F0,\
+0x200,0x210,0x220,0x230,0x240,0x250,0x260,0x270,0x280,0x290,0x2A0,0x2B0,0x2C0,0x2D0,0x2E0,0x2F0,\
+0x300,0x310,0x320,0x330,0x340,0x350,0x360,0x370,0x380,0x390,0x3A0,0x3B0,0x3C0,0x3D0,0x3E0,0x3F0,\
+0x400,0x410,0x420,0x430,0x440,0x450,0x460,0x470,0x480,0x490,0x4A0,0x4B0,0x4C0,0x4D0,0x4E0,0x4F0,\
+0x500,0x510,0x520,0x530,0x540,0x550,0x560,0x570,0x580,0x590,0x5A0,0x5B0,0x5C0,0x5D0,0x5E0,0x5F0,\
+0x600,0x610,0x620,0x630,0x640,0x650,0x660,0x670,0x680,0x690,0x6A0,0x6B0,0x6C0,0x6D0,0x6E0,0x6F0,\
+0x700,0x710,0x720,0x730,0x740,0x750,0x760,0x770,0x780,0x790,0x7A0,0x7B0,0x7C0,0x7D0,0x7E0,0x7F0,\
+0x800,0x810,0x820,0x830,0x840,0x850,0x860,0x870,0x880,0x890,0x8A0,0x8B0,0x8C0,0x8D0,0x8E0,0x8F0,\
+0x900,0x910,0x920,0x930,0x940,0x950,0x960,0x970,0x980,0x990,0x9A0,0x9B0,0x9C0,0x9D0,0x9E0,0x9F0,\
+0xA00,0xA10,0xA20,0xA30,0xA40,0xA50,0xA60,0xA70,0xA80,0xA90,0xAA0,0xAB0,0xAC0,0xAD0,0xAE0,0xAF0,\
+0xB00,0xB10,0xB20,0xB30,0xB40,0xB50,0xB60,0xB70,0xB80,0xB90,0xBA0,0xBB0,0xBC0,0xBD0,0xBE0,0xBF0,\
+0xC00,0xC10,0xC20,0xC30,0xC40,0xC50,0xC60,0xC70,0xC80,0xC90,0xCA0,0xCB0,0xCC0,0xCD0,0xCE0,0xCF0,\
+0xD00,0xD10,0xD20,0xD30,0xD40,0xD50,0xD60,0xD70,0xD80,0xD90,0xDA0,0xDB0,0xDC0,0xDD0,0xDE0,0xDF0,\
+0xE00,0xE10,0xE20,0xE30,0xE40,0xE50,0xE60,0xE70,0xE80,0xE90,0xEA0,0xEB0,0xEC0,0xED0,0xEE0,0xEF0,\
+0xF00,0xF10,0xF20,0xF30,0xF40,0xF50,0xF60,0xF70,0xF80,0xF90,0xFA0,0xFB0,0xFC0,0xFD0,0xFE0,0xFF0}"
+    # print("RGB255:\n", RGB255)
+    list = ["R //Gamma - R", "G //Gamma - G", "B //Gamma - B"]
+    for i in range(0, 3):
+        RGB_list.append(list[i])
+        for n in range(0, 256):
+            RGB_list.append(RGB255[i][n])
+        RGB_list.append("\n")
+    # 按照已知gamma.txt样式生成内容
+    RGB_list.append("/*")
+    RGB_list.append("//Settint R")
+    RGB_list.append(RGB_reg)
+    RGB_list.append("\n")
+    RGB_list.append("//Settint G")
+    RGB_list.append(RGB_reg)
+    RGB_list.append("\n")
+    RGB_list.append("//Settint R")
+    RGB_list.append(RGB_reg)
+    RGB_list.append("\n")
+    RGB_list.append("/*")
+    # print("RGB_list: \n", RGB_list)
+    time = datetime.strftime(datetime.now(), '%Y%m%d_%H%M%S')  #
+    filename = 'gamma.ini'
+    with open(filename, 'w') as fp:
+        [fp.write(str(item) + '\n') for item in RGB_list]
+        fp.close()
+    print(f"gamma文件已保存至{filename}")
+
+
+class Factory(metaclass=ABCMeta):
+    @abstractmethod
+    def create(self):
+        pass
+
+
+class FiretvFactory(Factory):
+    def __init__(self):
+        print("FiretvFactory...")
+
+    def create(self):
+        return Firetv_RGBcalculator()

+ 145 - 0
Python/baseSerail.py

@@ -0,0 +1,145 @@
+# -*- coding: UTF-8 -*-
+import serial
+import time
+import binascii
+
+'''
+描述:串口同步基类
+'''
+
+
+class BaseSerial():
+    # 构造函数
+    def __init__(self):
+        self.exception = None
+        self.ser = serial.Serial()
+
+    '''
+    打开串口
+    '''
+    def open(self, port, baudrate=115200, bytesize=8, parity=serial.PARITY_NONE, stopbits=1, timeout=3, writeTimeout=0.5):
+        if type(port) == type(0):
+            self.ser.port = "COM" + str(port)
+        else:
+            self.ser.port = port
+        # 波特率;
+        self.ser.baudrate = baudrate
+        # 数据位;
+        self.ser.bytesize = bytesize
+        # 校验码;
+        self.ser.parity = parity  # PARITY_NONE=0
+        # 停止位;
+        self.ser.stopbits = stopbits
+        # 串口读超时值
+        self.ser.timeout = timeout
+        # 串口写超时值
+        self.ser.write_timeout = writeTimeout
+        try:
+            self.ser.open()
+            # 返回结果;
+            return self.ser.is_open
+        except Exception as e:
+            self.exception = e
+            print('打开串口异常', str(e))
+            return False
+
+
+    '''
+    重新打开串口
+    '''
+    def reOpen(self):
+        self.exception = None
+        self.ser.close()
+        try:
+            self.ser.open()
+            # 返回结果;
+            return self.ser.is_open
+        except Exception as e:
+            self.exception = e
+            return False
+
+    '''关闭串口'''
+    def close(self):
+        self.ser.close()
+
+
+    '''
+    描述:写串口
+    '''
+
+    def write(self, cmd: bytearray):
+        try:
+            self.exception = None
+            writelen = self.ser.write(cmd)
+            # flush等待写入完成;
+            self.ser.flush()
+            print("writehex:" + str(binascii.b2a_hex(cmd)) + " writelen=" + str(writelen))
+            return True if writelen == len(cmd) else False
+        except Exception as e:
+            self.exception = e
+            print('写串口异常', str(e))
+            return False
+
+    '''
+    描述:读取串口数据;
+    返回:返回16进制字符串;
+    补充:ser.in_waiting 实际是缓冲区里的数据长度,也就是要读的长度;
+    '''    
+
+    def read(self, size=0):
+        try:
+            self.exception = None
+            # 先读取1个,如果超时表示通讯失败;
+            bys = self.ser.read(1)
+            for i in range(0, 3):
+                while self.ser.in_waiting > 0:
+                    d = self.ser.read(1)
+                    bys = bys + d
+                # 每当缓冲区为0,等10ms,循环3次确保机器响应完全;
+                time.sleep(0.01)
+            
+            # 再判断是否读取到指定大小(size!=0);
+            if bys.__len__() < size:
+                time.sleep(0.1) # 等待0.1秒让机器响应;
+                if self.ser.in_waiting > 0:
+                    d = self.ser.read(self.ser.in_waiting)
+                    bys = bys + d
+            print("readhex:" + str(binascii.hexlify(bys)) + " readlen=" + str(bys.__len__()))
+            return bys
+        except Exception as e:
+            self.exception = e
+            print('读串口异常', str(e))
+            return None
+    
+    def read2(self, size=0):
+        try:
+            self.exception = None
+            time.sleep(0.01)
+            # 先读取1个,如果超时表示通讯失败;
+            bys = self.ser.read(1)
+            while self.ser.in_waiting > 0:
+                d = self.ser.read(1)
+                bys = bys + d
+
+            # 再sleep一会,机器响应可能过慢,导致有字节遗漏;
+            time.sleep(0.015)
+            while self.ser.in_waiting > 0:
+                d = self.ser.read(1)
+                bys = bys + d
+           
+            # 再判断是否读取到指定大小(size!=0);
+            if bys.__len__() < size:
+                time.sleep(0.1) # 等待0.1秒让机器响应;
+                if self.ser.in_waiting > 0:
+                    d = self.ser.read(self.ser.in_waiting)
+                    bys = bys + d
+            print("readhex:" + str(binascii.hexlify(bys)) + " readlen=" + str(bys.__len__()))
+            return bys
+        except Exception as e:
+            self.exception = e
+            print('读串口异常', str(e))
+            return None
+
+
+if __name__ == "__main__":
+   pass

+ 182 - 0
Python/caSerail.py

@@ -0,0 +1,182 @@
+# -*- coding: UTF-8 -*-
+import re
+import time
+from baseSerail import BaseSerial
+
+class CASerail(BaseSerial):
+    def __init__(self):
+        BaseSerial.__init__(self)
+    
+    def __del__(self):
+        self.close()
+    
+    # 用于发命令前,检测串口是否打开;
+    def checkport(self):
+        if not self.ser.is_open:
+            self.reOpen()
+        
+        return self.ser.is_open
+    
+    def sendcmd(self, cmd:str):
+        self.write(cmd.encode('utf-8'))
+        return self.read()
+    
+    '''开启通讯'''
+    def startCommunication(self):
+        self.sendcmd('COM,1\r')
+
+    '''结束通讯'''
+    def endCommunication(self):
+        self.sendcmd('COM,0\r')
+
+    '''设置用于测量的校准通道'''
+    def setChannel(self, channel:str):
+        self.sendcmd('MCH,%s\r'%(channel))
+
+    '''
+    设置显示模式
+    mode:0-8
+    0: x,y,Lv
+    1: Tcp,duv,Lv
+    5: u',v',Lv
+    6: Flicker mode
+    7: X,Y,Z
+    8: λd,Pe,Lv
+    '''
+    def setDisplayMode(self, mode:int):
+        return self.sendcmd('MDS,%d\r'%(mode))
+
+    
+    def set_XYZ_Display(self):
+        return self.sendcmd('MDS,%d\r'%(7))
+
+
+    def set_xyLv_Display(self):
+        return self.sendcmd('MDS,%d\r'%(0))
+
+    '''
+    设置同步模式和同步频率
+    mode:同步模式,0-5
+        0:NTSC
+        1:PAL
+        2: EXTERNAL
+        3: UNIVERSAL
+        4: INTERNAL
+        5: MANUAL
+    freq:同步频率,1-6;
+    '''
+    def setSynchMode(self, mode: int, freq: int = None):
+        if freq == None:
+            return self.sendcmd('SCS,%d\r'%(mode))
+        else:
+            return self.sendcmd('SCS,%d,%d\r'%(mode,freq))
+
+    '''
+    设置测量速度
+    speed: 0-3
+        0: SLOW
+        1: FAST
+        2: LTD.AUTO
+        3: AUTO
+    '''
+    def setMeasureSpeed(self, speed: int):
+        return self.sendcmd('FSC,%d\r'%(speed))
+    
+
+    '''执行零点校准'''
+    def setZeroCalibration(self):
+        return self.sendcmd('ZRC\r')
+
+    def readDisplay(self):
+        data = self.sendcmd('MES\r')
+        return self.__get_display_data(data.decode('utf-8'))
+
+    def __readxyLv2(self):
+        return self.sendcmd('MES,1\r')
+
+    def __readXYZ(self):
+        data = self.sendcmd('MES,2\r')
+        # bytearray转str:使用decode。
+        result, des, X,Y,Z = self.__get_XYZ_data(data.decode('utf-8'))
+        return X,Y,Z
+
+
+    def __get_display_data(self, data):
+        # 正则表达式;
+        # OK00,P1 3716;3956;.0091\r
+        p = re.compile(r"(\D*)(\d+),P1 (.*);(.*);(.*)", re.DOTALL)
+        mo = p.search(data)
+        if mo is None:
+            print("无匹配正则")
+            pw = re.compile(r"(\D*)(\d+)", re.DOTALL)
+            mo = pw.search(data)
+            if mo is None:
+                # print("短匹配失败")
+                return None, None, None
+            else:
+                return None, None, None
+        else:
+            print(mo.group(1), mo.group(2), mo.group(3), mo.group(4), mo.group(5))
+            return mo.group(3), mo.group(4), mo.group(5)
+
+
+    def __get_XYZ_data(self, data):
+        # 正则表达式;
+        # 'OK00,P1,0,0.2265919,0.1769892,0.0437458,-0.03,-99999999,0.0560060,0.0437458,0.1474149\r'
+        p = re.compile(r"(\D*)(\d+),P1,(.*),(.*),(.*),(.*),(.*),(.*),(.*),(.*),(.*)\r", re.DOTALL)
+        mo = p.search(data)
+        if mo is None:
+            print("无匹配正则")
+            pw = re.compile(r"(\D*)(\d+)", re.DOTALL)
+            mo = pw.search(data)
+            if mo is None:
+                # print("短匹配失败")
+                return False, u"异常", None, None, None,
+            else:
+                return False, mo.group(2), None, None, None
+        else:
+            print(mo.group(1), mo.group(2), mo.group(3), mo.group(4), mo.group(5), mo.group(6), mo.group(7), mo.group(8), mo.group(9), mo.group(10), mo.group(11))
+            return True, mo.group(1), mo.group(9), mo.group(10), mo.group(11)
+
+
+def xyY_to_XYZ( x, y, Y):
+    X = x * Y / y
+    Z = (1 - x - y) * Y / y
+    return X, Y, Z
+
+
+def test_ca410():
+    print('\r\rtest ca410\r\r')
+    ca = CASerail()
+    ca.open("COM8", 19200, 7, 'E', 2)
+    ca.startCommunication()
+    ca.set_XYZ_Display()
+    ca.setSynchMode(3)
+    ca.setMeasureSpeed(2)
+    ca.setZeroCalibration()
+    ca.setChannel('01')
+    X,Y,Z=ca.readDisplay()
+    print("\rthe XYZ = ",float(X), float(Y), float(Z))
+    ca.endCommunication()
+
+
+def test_ca310():
+    print('\r\rtest ca310\r\r')
+    ca = CASerail()
+    ca.open("COM6", 19200, 7, 'E', 2)
+    ca.startCommunication()
+    ca.setChannel('01')
+    ca.setSynchMode(3)
+    ca.setMeasureSpeed(2)
+    #ca.set_xyLv_Display()
+    ca.set_XYZ_Display()
+    X,Y,Z=ca.readDisplay()
+    print("\rthe XYZ = ",float(X), float(Y), float(Z))
+    ca.endCommunication()
+
+
+if __name__ == "__main__":
+    test_ca410()
+    test_ca310()
+    X,Y,Z = xyY_to_XYZ(0.226, 0.171, 0.040)
+    print("xyLv=(%f,%f,%f), XYZ=(%f,%f,%f)"%(0.226, 0.171, 0.04, X,Y,Z))

+ 260 - 0
Python/cfg.txt

@@ -0,0 +1,260 @@
+COM7,COM6
+255,0,0
+0,255,0
+0,0,255
+255,255,255
+254,254,254
+253,253,253
+252,252,252
+251,251,251
+250,250,250
+249,249,249
+248,248,248
+247,247,247
+246,246,246
+245,245,245
+244,244,244
+243,243,243
+242,242,242
+241,241,241
+240,240,240
+239,239,239
+238,238,238
+237,237,237
+236,236,236
+235,235,235
+234,234,234
+233,233,233
+232,232,232
+231,231,231
+230,230,230
+229,229,229
+228,228,228
+227,227,227
+226,226,226
+225,225,225
+224,224,224
+223,223,223
+222,222,222
+221,221,221
+220,220,220
+219,219,219
+218,218,218
+217,217,217
+216,216,216
+215,215,215
+214,214,214
+213,213,213
+212,212,212
+211,211,211
+210,210,210
+209,209,209
+208,208,208
+207,207,207
+206,206,206
+205,205,205
+204,204,204
+203,203,203
+202,202,202
+201,201,201
+200,200,200
+199,199,199
+198,198,198
+197,197,197
+196,196,196
+195,195,195
+194,194,194
+193,193,193
+192,192,192
+191,191,191
+190,190,190
+189,189,189
+188,188,188
+187,187,187
+186,186,186
+185,185,185
+184,184,184
+183,183,183
+182,182,182
+181,181,181
+180,180,180
+179,179,179
+178,178,178
+177,177,177
+176,176,176
+175,175,175
+174,174,174
+173,173,173
+172,172,172
+171,171,171
+170,170,170
+169,169,169
+168,168,168
+167,167,167
+166,166,166
+165,165,165
+164,164,164
+163,163,163
+162,162,162
+161,161,161
+160,160,160
+159,159,159
+158,158,158
+157,157,157
+156,156,156
+155,155,155
+154,154,154
+153,153,153
+152,152,152
+151,151,151
+150,150,150
+149,149,149
+148,148,148
+147,147,147
+146,146,146
+145,145,145
+144,144,144
+143,143,143
+142,142,142
+141,141,141
+140,140,140
+139,139,139
+138,138,138
+137,137,137
+136,136,136
+135,135,135
+134,134,134
+133,133,133
+132,132,132
+131,131,131
+130,130,130
+129,129,129
+128,128,128
+127,127,127
+126,126,126
+125,125,125
+124,124,124
+123,123,123
+122,122,122
+121,121,121
+120,120,120
+119,119,119
+118,118,118
+117,117,117
+116,116,116
+115,115,115
+114,114,114
+113,113,113
+112,112,112
+111,111,111
+110,110,110
+109,109,109
+108,108,108
+107,107,107
+106,106,106
+105,105,105
+104,104,104
+103,103,103
+102,102,102
+101,101,101
+100,100,100
+99,99,99
+98,98,98
+97,97,97
+96,96,96
+95,95,95
+94,94,94
+93,93,93
+92,92,92
+91,91,91
+90,90,90
+89,89,89
+88,88,88
+87,87,87
+86,86,86
+85,85,85
+84,84,84
+83,83,83
+82,82,82
+81,81,81
+80,80,80
+79,79,79
+78,78,78
+77,77,77
+76,76,76
+75,75,75
+74,74,74
+73,73,73
+72,72,72
+71,71,71
+70,70,70
+69,69,69
+68,68,68
+67,67,67
+66,66,66
+65,65,65
+64,64,64
+63,63,63
+62,62,62
+61,61,61
+60,60,60
+59,59,59
+58,58,58
+57,57,57
+56,56,56
+55,55,55
+54,54,54
+53,53,53
+52,52,52
+51,51,51
+50,50,50
+49,49,49
+48,48,48
+47,47,47
+46,46,46
+45,45,45
+44,44,44
+43,43,43
+42,42,42
+41,41,41
+40,40,40
+39,39,39
+38,38,38
+37,37,37
+36,36,36
+35,35,35
+34,34,34
+33,33,33
+32,32,32
+31,31,31
+30,30,30
+29,29,29
+28,28,28
+27,27,27
+26,26,26
+25,25,25
+24,24,24
+23,23,23
+22,22,22
+21,21,21
+20,20,20
+19,19,19
+18,18,18
+17,17,17
+16,16,16
+15,15,15
+14,14,14
+13,13,13
+12,12,12
+11,11,11
+10,10,10
+9,9,9
+8,8,8
+7,7,7
+6,6,6
+5,5,5
+4,4,4
+3,3,3
+2,2,2
+1,1,1
+0,0,0

+ 307 - 0
Python/gamma-tools.py

@@ -0,0 +1,307 @@
+# -*- coding: UTF-8 -*-
+import time
+import re  # 正则表达式;
+import numpy as np
+import zipfile
+import gzip
+import autoGamma
+from mokaSerail import MokaSerial
+
+def readPartern():
+    f = open(u"cfg.txt")  # 返回一个文件对象
+    line = f.readline()  # 调用文件的 readline()方法,一次读取一行
+    print(u"第一行", line)
+    line = line.replace("\r", "")
+    line = line.replace('\n', '')
+    TV_COM = line.split(",")[0]
+    CA_COM = line.split(",")[1]
+
+    print("value", TV_COM, CA_COM)
+
+    PAR_LIST = []
+    while line:
+        print(line)
+        line = f.readline()
+        if line.__len__() != 0:
+            line = line.replace("\r", "")
+            line = line.replace('\n', '')
+            ls_d = line.split(',')
+            # PAR_LIST.append(line.split(','))
+            PAR_LIST.append([int(ls_d[0]), int(ls_d[1]), int(ls_d[2])])
+    f.close()
+
+    print(PAR_LIST)
+
+    return TV_COM, CA_COM, PAR_LIST
+
+
+def get_ca_data(data):
+    # 正则表达式;
+    # OK00,P1 3716;3956;.0091\r
+    p = re.compile(r"(\D*)(\d+),P1 (.*);(.*);(.*)", re.DOTALL)
+    mo = p.search(data)
+    if mo is None:
+        print("无匹配正则")
+        pw = re.compile(r"(\D*)(\d+)", re.DOTALL)
+        mo = pw.search(data)
+        if mo is None:
+            # print("短匹配失败")
+            return None, None, None, None, None
+        else:
+            return mo.group(1), mo.group(2), None, None, None
+    else:
+        return mo.group(1), mo.group(2), mo.group(3), mo.group(4), mo.group(5)
+
+
+def xyY_to_XYZ(x, y, Y):
+    X = x * Y / y
+    Z = (1 - x - y) * Y / y
+    return X, Y, Z
+
+
+def gen_gzip(gm_file, save_file):
+    # 压缩gamma.ini成zip,再写入到TV?? 还是直接写入gamma.ini到TV ??
+    fin = open(gm_file, 'rb')
+    gf = gzip.open('gamma_org.gzip', 'wb')
+    data = fin.read()
+    gf.write(data)
+    fin.close()
+    gf.close()
+
+    # 计算原始文件crc32;
+    # crc32 = binascii.crc32(data)
+
+    # 重新读取gzip,再切掉8个字节?后再保存
+    fg = open('gamma_org.gzip', 'rb')
+    g_data = fg.read()
+    fg.close()
+
+    # 生成新的gamma.zip.
+    fg = open(save_file, 'wb+')
+    start_index = 10 + 'gamma_org.gzip'.__len__() + 1
+    data_save = g_data[start_index:-4]
+    fg.write(g_data[8:10])
+    fg.write(data_save)
+    fg.close()
+
+def test_all():
+    # 读取配置文件;
+    TV_COM, CA_COM, PAR_LIST = readPartern()
+    NP_LIST = np.array(PAR_LIST)
+    # 取第一列,再取第4位到末尾;
+    Index11 = NP_LIST[:, 1][3:]
+    print("11 pattern", Index11)
+
+    # 打开CA310
+    ca_port = MokaSerial()
+    ca_port.open(CA_COM, 19200, 7, 'E', 2)
+    # 开启CA310
+    data = ca_port.sendcmd([0x43, 0x4F, 0x4D, 0x2C, 0x31, 0x0D])
+    print("CA310 开启", data)
+
+    if False:
+        data = ca_port.sendcmd([0x4D, 0x44, 0x53, 0x2C, 0x30, 0x0D])
+        print("CA310 显示模式", data)
+
+        data = ca_port.sendcmd([0x53, 0x43, 0x53, 0x2C, 0x33, 0x0D])
+        print("CA310 同步模式", data)
+
+        data = ca_port.sendcmd([0x46, 0x53, 0x43, 0x2C, 0x32, 0x0D])
+        print("CA310 测量速度", data)
+        time.sleep(0.5)
+
+        data = ca_port.sendcmd([0x5A, 0x52, 0x43, 0x0D])
+        print("CA310 零校准", data)
+
+    data = ca_port.sendcmd([0x4D, 0x43, 0x48, 0x2C, 0x30, 0x31, 0x0D])
+    print("CA310 锁定键控", data)
+
+    # 打开TV串口;
+    tv_port = MokaSerial()
+    tv_port.open(TV_COM)
+    print("进工厂模式")
+    data = tv_port.sendcmd([0xAA, 0x06, 0x10, 0x01, 0xA7, 0xEF])
+
+    print("白平衡初始化")
+    data = tv_port.sendcmd([0xAA, 0x06, 0x16, 0x01, 0x0D, 0x49])
+
+    print("关闭Localdimming")
+    data = tv_port.sendcmd([0xAA, 0x07, 0x9F, 0x07, 0x00, 0xE1, 0x74])
+
+    print("打开内置pattern")
+    data = tv_port.sendcmd([0xAA, 0x06, 0x27, 0x02, 0x0B, 0x8E])
+
+    print("切换标准色温")
+    data = tv_port.sendcmd([0xAA, 0x06, 0x31, 0x01, 0x92, 0x38])
+
+    print("gamma初始化")
+    data = tv_port.sendcmd([0xAA, 0x07, 0x9F, 0x09, 0x01, 0xD2, 0x5A])
+
+    XYZ_ALL = []
+    xyY_ALL = []
+    print("内置pattern切换与CA310读取")
+    for item in PAR_LIST:
+        # print("item",item)
+        if item.__len__() == 3:
+            tv_port.send_parttern([int(item[0]), int(item[1]), int(item[2])])
+            time.sleep(1)
+            data = ca_port.sendcmd([0x4D, 0x45, 0x53, 0x0D])
+            print("CA310数据读取",data)
+            # bytes转成字符串;
+            data = data.decode('utf-8')
+            data = data.replace('\r', '')
+            r1, r2, d1, d2, d3 = get_ca_data(data)
+            # print("CA310数据:", d1, d2, d3)
+            # X, Y, Z = xyY_to_XYZ(float(d1) / 10000, float(d2) / 10000, float(d3))
+
+            print("CA410数据:", d1, d2, d3)
+            X, Y, Z = xyY_to_XYZ(float(d1) / 1000, float(d2) / 1000, float(d3))
+
+            print("XYZ", X, Y, Z)
+            XYZ_ALL.append([X, Y, Z])
+            xyY_ALL.append([d1, d2, d3])
+
+    print("关闭内置pattern")
+    data = tv_port.sendcmd([0xAA, 0x06, 0x27, 0x00, 0x2B, 0xCC])
+
+    xyY_ALL = xyY_ALL[::-1]
+    # for n in range(0, 11):
+    #     xyY_ALL[n] = xyY_ALL[n].reverse()
+
+    print(f"原始测量的xyY_11 is : \n")
+    for i in range(0, 14):
+        print(f"0.{xyY_ALL[i][0]}  0.{xyY_ALL[i][1]}  {xyY_ALL[i][2]}")
+
+    # 关闭CA310
+    data = ca_port.sendcmd([0x43, 0x4F, 0x4D, 0x2C, 0x30, 0x0D])
+
+    # 调用gamma算法生成gamma.ini
+    factory = autoGamma.FiretvFactory()
+    RGBcalculator = factory.create()
+    print("XYZ_ALL", XYZ_ALL)
+    RGB255 = RGBcalculator.calRGB255_for_Debug(np.array(XYZ_ALL[0:3]).T, np.array(XYZ_ALL[3:]).T, np.array(Index11),
+                                               0.313, 0.329, 2.2)
+    autoGamma.dumpRGB2ini(RGB255)
+
+    #  生成切割文件;
+    gen_gzip('gamma.ini', 'gamma_cut.zip')
+    # 调用串口命令写入TV中;
+    tv_port.send_gamma('gamma_cut.zip')
+    # 使生效;
+    tv_port.send_gamma_active('gamma.ini')
+
+    autoGamma.showRGBcurve(RGB255)
+
+
+def get_test_result():
+    # 读取配置文件;
+    TV_COM, CA_COM, PAR_LIST = readPartern()
+    NP_LIST = np.array(PAR_LIST)
+    # 取第一列,再取第4位到末尾;
+    Index11 = NP_LIST[:, 1][3:]
+    print("11 pattern", Index11)
+
+    # 打开CA310
+    ca_port = MokaSerial()
+    ca_port.open(CA_COM, 19200, 7, 'E', 2)
+    # 开启CA310
+    data = ca_port.sendcmd([0x43, 0x4F, 0x4D, 0x2C, 0x31, 0x0D])
+    print("CA310 开启", data)
+
+    if True:
+        data = ca_port.sendcmd([0x4D, 0x44, 0x53, 0x2C, 0x30, 0x0D])
+        print("CA310 显示模式", data)
+
+        data = ca_port.sendcmd([0x53, 0x43, 0x53, 0x2C, 0x33, 0x0D])
+        print("CA310 同步模式", data)
+
+        data = ca_port.sendcmd([0x46, 0x53, 0x43, 0x2C, 0x32, 0x0D])
+        print("CA310 测量速度", data)
+        time.sleep(1)
+
+        data = ca_port.sendcmd([0x5A, 0x52, 0x43, 0x0D])
+        print("CA310 零校准", data)
+
+    data = ca_port.sendcmd([0x4D, 0x43, 0x48, 0x2C, 0x30, 0x31, 0x0D])
+    print("CA310 锁定键控", data)
+
+    # 打开TV串口;
+    tv_port = MokaSerial()
+    tv_port.open(TV_COM)
+    print("进工厂模式")
+    data = tv_port.sendcmd([0xAA, 0x06, 0x10, 0x01, 0xA7, 0xEF])
+
+    print("打开内置pattern")
+    data = tv_port.sendcmd([0xAA, 0x06, 0x27, 0x02, 0x0B, 0x8E])
+
+
+    XYZ_ALL = []
+    xyY_ALL = []
+    print("内置pattern切换与CA310读取")
+    for item in PAR_LIST:
+        # print("item",item)
+        if item.__len__() == 3:
+            tv_port.send_parttern([int(item[0]), int(item[1]), int(item[2])])
+            time.sleep(0.5)
+            data = ca_port.sendcmd([0x4D, 0x45, 0x53, 0x0D])
+            # bytes转成字符串;
+            data = data.decode('utf-8')
+            data = data.replace('\r', '')
+            r1, r2, d1, d2, d3 = get_ca_data(data)
+            # print("CA310数据:", d1, d2, d3)
+            # X, Y, Z = xyY_to_XYZ(float(d1) / 10000, float(d2) / 10000, float(d3))
+
+            print("CA410数据:", d1, d2, d3)
+            X, Y, Z = xyY_to_XYZ(float(d1) / 1000, float(d2) / 1000, float(d3))
+
+            print("XYZ", X, Y, Z)
+            XYZ_ALL.append([X, Y, Z])
+            xyY_ALL.append([d1, d2, d3])
+    print("关闭内置pattern")
+    data = tv_port.sendcmd([0xAA, 0x06, 0x27, 0x00, 0x2B, 0xCC])
+    xyY_ALL = xyY_ALL[::-1]
+    print(f"测量的xyY_11 is : \n")
+    for i in range(0, 14):
+        print(f"0.{xyY_ALL[i][0]}  0.{xyY_ALL[i][1]}  {xyY_ALL[i][2]}")
+
+    # 关闭CA310
+    data = ca_port.sendcmd([0x43, 0x4F, 0x4D, 0x2C, 0x30, 0x0D])
+
+
+def test_zip():
+    # 创建zip压缩包;
+    zipFile = zipfile.ZipFile(r'gamma.zip', 'w')
+    # 写入ini到zip中;
+    zipFile.write(r'gamma.ini', 'gamma.ini', zipfile.ZIP_DEFLATED)
+    zipFile.close()
+
+
+def test_gzip():
+    fin = open('gamma.ini', 'rb')
+    gf = gzip.open('gamma.gzip', 'wb')
+    data = fin.read()
+    gf.write(data)
+    fin.close()
+    gf.close()
+
+
+def test_result():
+    # xyY_ALL = [[1,2,3],[4,5,6],[7,8,9]]
+    # xyY_ALL = xyY_ALL[::-1]
+    # for i in range(0, 3):
+    #     print(f"0.{xyY_ALL[i][0]}  0.{xyY_ALL[i][1]}  {xyY_ALL[i][2]}")
+    X = 7.82195
+    Y = 3.78406
+    Z = 2.37614
+    x = X / (X + Y + Z)
+    y = Y / (X + Y + Z)
+    print(f"x={x} y= {y}")
+
+
+if __name__ == "__main__":
+    test_all()
+    # test_result()
+    # test_tv()
+    # gen_gzip(r'F:\bin\TCLTools\gamma.ini', 'aaa.cut.zip')
+    # test_ca310()
+    # test_gzip()

+ 303 - 0
Python/mokaSerail.py

@@ -0,0 +1,303 @@
+# -*- coding: UTF-8 -*-
+from xmlrpc.client import Boolean
+import serial
+import time
+import binascii
+from baseSerail import BaseSerial
+
+# CRC16矩阵表;
+CRC_TABLE = [
+    0x0000, 0x1021, 0x2042, 0x3063,
+    0x4084, 0x50A5, 0x60C6, 0x70E7,
+    0x8108, 0x9129, 0xA14A, 0xB16B,
+    0xC18C, 0xD1AD, 0xE1CE, 0xF1EF ]
+
+
+# 计算CRC16值;
+def crc16(bys, bysLen):
+    uwCRC, ucTemp = 0xFFFF, 0x00
+    index = 0
+    while index < bysLen:
+        ucTemp = (uwCRC >> 0x0C) & 0xFF
+        uwCRC <<= 4
+        uwCRC ^= CRC_TABLE[ucTemp ^ ((bys[index]) >> 0x04)]
+        uwCRC &= 0xFFFF
+        ucTemp = (uwCRC >> 0x0C) & 0xFF
+        uwCRC <<= 4
+        uwCRC ^= CRC_TABLE[ucTemp ^ (bys[index] & 0x0F)]
+        uwCRC &= 0xFFFF
+        index += 1
+    return uwCRC
+
+
+# 包头码(包引导码)
+PHeader = {
+    "TV_Debug": 0xAA,
+    "TV_Return": 0xAB,
+    "TV_Panel_Debug": 0xAC,
+    "TV_Panel_Return": 0xAD,
+    "TV_Debug_Other": 0xAE,
+    "TV_Other_Return": 0xAF,
+}
+
+# 命令结果码;
+RCode = {
+    "RC_OK": 0x0A,  # 命令执行通过;
+    "RC_ERR": 0x0E,  # 命令错误或无法执行;
+    "RC_LOSE": 0x0F,  # 命令丢失(链路层出错);
+}
+
+
+# 命令的封装与解析;
+
+
+class MokaComman():
+    def __init__(self):
+        # 头引导码(默认1字节,0x86命令二字节);
+        self.Header = [PHeader['TV_Debug']]
+        # 包长(默认1字节,0x86长度二字节);
+        self.Length = [0x00]
+        # 命令码,1字节(int类型);
+        self.Command = [0x00]
+        # 子命令参数;
+        self.SubCommand = []
+        # 数据,bytearray格式;
+        self.Data = []
+        # crch,1字节;
+        self.CRCH = 0xFF
+        # crcl,1字节;
+        self.CRCL = 0xFF
+        # 是否是特殊命令;
+        self.FEFlag = False
+        # 是否是多参数命令;(同一个Command,有多个含义的参数)
+        self.isMultipleParams = False
+        # 正确执行后的结果值,bytearray格式的二维数组;
+        self.successData = []
+
+    # 生成命令;
+    '''
+    描述:将参数根据协议格式转化为协议指令;
+    参数:
+        head:协议包引导头;
+        command:协议命令码;
+        data:协议命令码数据;
+        FEFlag:协议特殊标记,协议包引导头是否有2个;
+    注意:data必须是bytearray格式传递;
+    返回:
+    '''
+
+    def parseCommand(self, head: int, command: list[int], subCommand: list[int], data: bytearray, isMultipleParams=False, FEFlag=False):
+        self.Header[0] = head
+        self.Command = command
+        self.SubCommand = subCommand
+        self.Data = data
+        self.FEFlag = FEFlag
+        self.isMultipleParams = isMultipleParams
+        if FEFlag:
+            self.Header.append(0xFE)
+            # 注意:4 = crch + crcl + lenh + lenl
+            length = 4 + self.Header.__len__() + self.Data.__len__() + self.Command.__len__() + self.SubCommand.__len__()
+            # 高字节;
+            self.Length[0] = length >> 8
+            # 低字节;
+            self.Length.append(length & 0xFF)
+        else:
+            # 注意:3 = crch + crcl + len
+            self.Length[0] = 3 + self.Header.__len__() + self.Data.__len__() + self.Command.__len__() + self.SubCommand.__len__()
+
+        # 生成package;
+        package = bytearray(self.Header + self.Length + self.Command + self.SubCommand) + self.Data
+
+        # 计算crc
+        crc = crc16(package, package.__len__())
+        # 高字节;
+        self.CRCH = crc >> 8
+        # 低字节;
+        self.CRCL = crc & 0xFF
+
+        # 形成最终的命令;
+        package = bytearray(self.Header + self.Length + self.Command + self.SubCommand) + self.Data + bytearray([self.CRCH, self.CRCL])
+
+        return package
+
+    '''
+    描述:解析读串口数据是否正确返回,若正确返回剥离出结果值;
+    参数:data 16进制的字符串;
+    返回:
+    '''
+
+    def parseResult(self, data):
+        # 将16进制字符串转成字节数组;
+        data = bytearray(data)
+        # 检测长度;
+        if data.__len__() < 5:
+            return False
+
+        # 根据请求的引导码,识别对应的返回码;
+        retCode = 0
+        if self.Header[0] == PHeader['TV_Debug']:
+            retCode = PHeader['TV_Return']
+        elif self.Header[0] == PHeader['TV_Panel_Debug']:
+            retCode = PHeader['TV_Panel_Return']
+        elif self.Header[0] == PHeader['TV_Debug_Other']:
+            retCode = PHeader['TV_Other_Return']
+
+        # 检测包引导码;
+        if retCode != data[0]:
+            return False
+
+        package = []
+        tooken_len = 0
+        while True:
+            if tooken_len >= data.__len__():
+                break
+            # 取出包长;
+            if self.FEFlag:
+                package_len = data[tooken_len + 1] << 8 + data[tooken_len + 2]
+            else:
+                package_len = data[tooken_len + 1]
+            # 取出整包;
+            package = data[tooken_len:tooken_len + package_len]
+            
+            # 再次验证包头是否有效;
+            if package[0] != retCode:
+                print('Incorrect package head!\n')
+                return False
+
+            # 检测crc是否正确;
+            crc = crc16(package, package_len - 2)
+            CRCH = crc >> 8
+            CRCL = crc & 0xFF
+            if CRCH != package[-2] and CRCL != package[-1]:
+                return False
+
+            # 第一次,检测结果码(第三字节);
+            if tooken_len == 0:
+                if package[2] != RCode['RC_OK']:
+                    return False
+            else:  # 第二次,检测命令码
+                if self.Command[0] == 0xFC:
+                    pass
+                else:
+                    if package[2] - 1 != self.Command[0]:
+                        return False
+                    if package[2] == 0xFE: #返回超过255
+                        self.successData.append(package[5:-2])
+                    else:
+                        self.successData.append(package[3:-2])
+
+            tooken_len += package_len
+
+        print('successData:', binascii.b2a_hex(bytearray(self.Command)), self.successData)
+        return True
+
+
+class MokaSerial(BaseSerial):
+    def __init__(self):
+        BaseSerial.__init__(self)
+    
+    def __del__(self):
+        self.close()
+    
+    # 用于发命令前,检测串口是否打开;
+    def checkport(self):
+        if not self.ser.is_open:
+            self.reOpen()
+        
+        return self.ser.is_open
+    
+
+    '''
+    发送普通命令
+    '''
+    def sendcmd(self, cmd: list[int]):
+        self.write(bytearray(cmd))
+        return self.read()
+    
+
+    def sendcmdEx(self, head: int, command: list[int], subCommand: list[int], data: bytearray = b'', FEFlag: Boolean = False, returnParam: Boolean = False):
+        cmd = MokaComman()
+        package = cmd.parseCommand(head, command, subCommand, data, returnParam, FEFlag)
+        if self.write(package):
+            package = self.read()
+            return cmd.parseResult(package)
+
+        return False
+
+
+    '''发送pattern'''
+    def send_parttern(self, rgb: list[int]):
+        cmd = MokaComman()
+        package = cmd.parseCommand(0xAA, [0x28], [], bytearray(rgb))
+        if self.write(package):
+            package = self.read()
+            return cmd.parseResult(package)
+
+        return False
+
+
+    def send_gamma(self, file_path: str):
+        fp = open(file_path, 'rb')
+        cmd = MokaComman()
+        data = fp.read()
+        package = cmd.parseCommand(0xAA, [0xE9], [0x02], data, False, True)
+        if self.write(package):
+            package = self.read()
+            return cmd.parseResult(package)
+
+
+    def send_gamma_active(self, ini_file: str):
+        ini_fp = open(ini_file, 'rb')
+        ind_data = ini_fp.read()
+        ini_fp.close()
+
+        # 计算crc
+        crc = crc16(ind_data, ind_data.__len__())
+        CRC_GM = [crc >> 8, crc & 0xFF]
+
+        cmd = MokaComman()
+        package = cmd.parseCommand(0xAA, [0x99], [0x06], bytearray(CRC_GM))
+        if self.write(package):
+            package = self.read()
+            return cmd.parseResult(package)
+
+
+if __name__ == "__main__":
+    # 打开TV串口;
+    tv_port = MokaSerial()
+    tv_port.open('COM7')
+
+    print("进工厂模式")
+    data = tv_port.sendcmd([0xAA, 0x06, 0x10, 0x01, 0xA7, 0xEF])
+
+    print("白平衡初始化")
+    data = tv_port.sendcmd([0xAA, 0x06, 0x16, 0x01, 0x0D, 0x49])
+
+    print("关闭Localdimming")
+    data = tv_port.sendcmd([0xAA, 0x07, 0x9F, 0x07, 0x00, 0xE1, 0x74])
+
+    print("打开内置pattern")
+    data = tv_port.sendcmd([0xAA, 0x06, 0x27, 0x02, 0x0B, 0x8E])
+
+    print("切换标准色温")
+    data = tv_port.sendcmd([0xAA, 0x06, 0x31, 0x01, 0x92, 0x38])
+
+    print("gamma初始化")
+    data = tv_port.sendcmd([0xAA, 0x07, 0x9F, 0x09, 0x01, 0xD2, 0x5A])
+
+    print("读取 mac")
+    data = tv_port.sendcmdEx(0xAA, [0xBE], [0x00])
+
+    print("读取 wifimac")
+    data = tv_port.sendcmdEx(0xAA, [0xBE], [0x15])
+
+    print("读取 clientType")
+    data = tv_port.sendcmdEx(0xAA, [0x8C], [0x00])
+
+    for item in [[0,0,0],[26,26,26],[51,51,51],[76,76,76],[102,102,102],[127,127,127],[153,153,153],[178,178,178],[204,204,204],[229,229,229],[255,255,255],[0,0,255],[255,0,0]]:
+        if item.__len__() == 3:
+            tv_port.send_parttern([int(item[0]), int(item[1]), int(item[2])])
+            time.sleep(0.5)
+
+    print("关闭内置pattern")
+    data = tv_port.sendcmd([0xAA, 0x06, 0x27, 0x00, 0x2B, 0xCC])

+ 40 - 0
Python/pip_install.bat

@@ -0,0 +1,40 @@
+@echo off
+:: 临时换源:pip install requests -I https://pypi.tuna.tsinghua.edu.cn/simple
+:: 永久换源,本地创建pip.ini
+set pip_file=%USERPROFILE%\pip\pip.ini
+if not exist %pip_file% (
+    md %USERPROFILE%\pip
+    echo [global] >> %pip_file%
+    echo timeout=6000 >> %pip_file%
+    echo index-url = https://pypi.tuna.tsinghua.edu.cn/simple >> %pip_file%
+    echo [install] >> %pip_file%
+    echo trusted-host=mirrors.aliyun.com >> %pip_file%
+)
+@echo on
+
+::查看已安装的库;
+pip list
+
+pip uninstall numpy -y
+pip uninstall scipy -y
+pip uninstall xlrd -y
+pip uninstall matplotlib -y
+pip uninstall jsbeautifier -y
+
+:: 普通安装:pip install 模板名
+:: 指定版本安装:pip install 模板名==版本
+:: 卸载pip uninstall 模板名
+pip3 install numpy==1.23.2
+pip3 install scipy==1.9.1
+pip3 install xlrd==1.2.0
+pip3 install matplotlib==3.5.3
+pip3 install jsbeautifier==1.14.6
+
+:: 查看numpy的版本;
+pip show numpy
+
+:: 安装特定版本库:         pip install package==version
+:: 更新指定模块:          pip install --upgrade package
+:: 指定更新源更新版本:    pip install --upgrade -i https://pypi.tuna.tsinghua.edu.cn/simple package
+
+pause

+ 33 - 0
Python/pip_rinstall.bat

@@ -0,0 +1,33 @@
+::²é¿´ÒѰ²×°µÄ¿â;
+pip list
+
+pip uninstall numpy -y
+pip uninstall scipy -y
+pip uninstall xlrd -y
+pip uninstall matplotlib -y
+pip uninstall jsbeautifier -y
+pip uninstall cycler -y
+pip uninstall fonttools -y
+pip uninstall kiwisolver -y
+pip uninstall pillow -y
+pip uninstall pyparsing -y
+pip uninstall python-dateutil -y
+pip uninstall editorconfig -y
+
+pip3 install -i https://pypi.tuna.tsinghua.edu.cn/simple numpy==1.23.2
+pip3 install -i https://pypi.tuna.tsinghua.edu.cn/simple cycler==0.11.0
+pip3 install -i https://pypi.tuna.tsinghua.edu.cn/simple fonttools==4.37.1
+pip3 install -i https://pypi.tuna.tsinghua.edu.cn/simple kiwisolver==1.4.4 
+pip3 install -i https://pypi.tuna.tsinghua.edu.cn/simple pillow==9.2.0
+pip3 install -i https://pypi.tuna.tsinghua.edu.cn/simple pyparsing==3.0.9 
+pip3 install -i https://pypi.tuna.tsinghua.edu.cn/simple python-dateutil==2.8.2 
+pip3 install -i https://pypi.tuna.tsinghua.edu.cn/simple six==1.16.0
+pip3 install -i https://pypi.tuna.tsinghua.edu.cn/simple scipy==1.9.1
+pip3 install -i https://pypi.tuna.tsinghua.edu.cn/simple xlrd==1.2.0
+pip3 install -i https://pypi.tuna.tsinghua.edu.cn/simple matplotlib==3.5.3
+pip3 install -i https://pypi.tuna.tsinghua.edu.cn/simple editorconfig==0.12.3
+pip3 install -i https://pypi.tuna.tsinghua.edu.cn/simple jsbeautifier==1.14.6
+pip3 install -i https://pypi.tuna.tsinghua.edu.cn/simple pyserial==3.5
+pip3 list
+
+pause

+ 1 - 0
Python/pyinstall.bat

@@ -0,0 +1 @@
+pyinstaller -F -w -i gamma.ico gamma-tools.py