import modbus_tk.defines as cst
from modbus_tk import modbus_tcp, hooks, modbus
import menu_utils as utils
import menu_key_def as KEYDEF
import ZZ_9TE as device_9TE
import struct
from threading import Thread
from typing import Tuple
from device_conf import class_comm_device_config
from comm_device import class_comm_device
from menu_base import class_menu_layer_info, class_device_menu
import menu_page
import modbus_server
from modbus_server import class_modbus_tcp_server
import comm_global
from comm_thread import class_comm_master_thread

DEFAULT_EMPTY_STR            = " "
MAX_NUMS_OF_STRING           = 20   #每个字符串占用寄存器数
MAX_NUMS_OF_MENU_LINE        = MAX_NUMS_OF_STRING * 2
MAX_NUMS_OF_MODIFY_STRING    = 20


MENU_PAGE_DYNAMIC_MENU    =1
MENU_PAGE_ALARM           =2

class class_menu_group(Thread, class_device_menu) :
    def __init__(self, 
                 device_remap = 0,
                 max_menu_lines = 8, 
                 comm_config : class_comm_device_config = None, 
                 modbus_tcp_server : class_modbus_tcp_server = None):
        
        Thread.__init__(self)
        class_device_menu.__init__(self, device_remap, comm_config)

        self.modbus_tcp_server = modbus_tcp_server
        self.max_menu_lines = max_menu_lines
        self.server = None
        
        self.menu_reg_count = 10000
        self.menu_active = None if self.config == None else self.config.get_menu_top()
        return 

    def menu_get_regsiter_addr(self, row, col) :
        if col == 1:
            addr = modbus_server.REGISTER_MENU_STRING_ROW0_COL1 + row * MAX_NUMS_OF_STRING
        else :
            addr = modbus_server.REGISTER_MENU_STRING_ROW0_COL0 + row * MAX_NUMS_OF_STRING
        return addr

    def register_menu_string_fill(self, addr, menu_str, max_count) :
        index = 0
        for chr in menu_str :
            self.update_server_register(addr + index, ord(chr))
            if index >= max_count - 1:
                break
            index += 1
        self.update_server_register(addr + index, 0)

    def menu_display_string(self, row, col, menu_str) :
        addr = self.menu_get_regsiter_addr(row, col)
        self.register_menu_string_fill(addr, menu_str, MAX_NUMS_OF_STRING)

    def menu_display_modify_caption_string(self, menu_str) :
        addr = modbus_server.REGISTER_MENU_MODIFY_CAPTION_STRING
        self.register_menu_string_fill(addr, menu_str, MAX_NUMS_OF_MODIFY_STRING)
        
    def menu_display_modify_string(self, menu_str) :
        addr = modbus_server.REGISTER_MENU_MODIFY_STRING
        self.register_menu_string_fill(addr, menu_str, MAX_NUMS_OF_MODIFY_STRING)

    def menu_display_modify_select_string(self, menu_str) :
        addr = modbus_server.REGISTER_MENU_MODIFY_SELECT_STRING
        self.register_menu_string_fill(addr, menu_str, MAX_NUMS_OF_MODIFY_STRING)

    def menu_get_value_regs(self, menu_item) :
        comm_str = utils.dict_or_object_get_attr(menu_item, "addr", None)
        value = None
        comm_thread : class_comm_master_thread= None
        comm_device : class_comm_device = None
        comm_device, comm_thread, device_menu = comm_global.search_device_remap_infos(self.device_remap)
       
        if comm_str != None:
            reg_addr, bit, reg_count = utils.comm_str_unpack(comm_str)
            if comm_thread == None or reg_count == 0:
                value = None
            elif bit >= 0:
                start_index = int(bit / 16)
                end_index   = int((bit + reg_count + 15) / 16)
                reg_items   = end_index - start_index
                value = comm_device.read_register_count(reg_addr + start_index, reg_items)
                #value = comm_thread.comm_hold_register_read(comm_addr, reg_addr + start_index, reg_items)
                comm_device.device_comm_flush_request(reg_addr + start_index, reg_items, cycle = 3000)
            else :
                value = comm_device.read_register_count(reg_addr, reg_count)
                #value = comm_thread.comm_hold_register_read(comm_addr, reg_addr, reg_count)
                
                #刷新请求
                comm_device.device_comm_flush_request(reg_addr, reg_count, cycle = 3000)
        return value

    def menu_get_value_scale_float(self, menu_item) :
        comm_str = utils.dict_or_object_get_attr(menu_item, "addr", None)
        reg_addr, bit, reg_count = utils.comm_str_unpack(comm_str)
        value = self.menu_get_value_regs(menu_item)
        is_big_endian = self.menu_data_is_big_endian(menu_item)
        is_float = self.menu_data_is_float(menu_item)
        
        new_value = None
        if value != None :
            if len(value) == 2:
                if is_big_endian:
                    new_value = value[1] + (value[0] << 16)
                else :
                    new_value = value[0] + (value[1] << 16)

                if is_float :
                    bytes = struct.pack('BBBB', new_value & 0xFF, (new_value >> 8) & 0xFF, (new_value >> 16) & 0xFF, (new_value >> 24) & 0xFF)
                    new_value = struct.unpack("<f", bytes)
            elif len(value) == 1:
                new_value = value[0]

            if bit >= 0 and new_value != None and is_float == False:
                bit = bit % 16
                new_value = (new_value >> bit) & (0xFFFFFFFF >> (32 - reg_count))

        if new_value != None : 
            scale = utils.dict_or_object_get_attr(menu_item, "scale", 1.0)
            new_value = new_value * scale
        return new_value

    def menu_get_value(self, menu_item) :
        return self.menu_get_value_scale_float(menu_item)

    def menu_write_value(self, menu_item, new_value) :
        comm_str    = utils.dict_or_object_get_attr(menu_item, "addr", None)
        if comm_str != None and new_value != None:
            reg_addr, bit, reg_count = utils.comm_str_unpack(comm_str)
            
            comm_thread : class_comm_master_thread = None
            comm_device : class_comm_device = None
            comm_device, comm_thread, device_menu = comm_global.search_device_remap_infos(self.device_remap)

            if comm_thread != None and comm_device != None and reg_count == len(new_value) :
                return comm_thread.comm_hold_register_write(comm_device.comm_addr, reg_addr, reg_count, new_value)
        return False

    def menu_get_value_string(self, menu_item) :
        value_str = None
        value = None
        format_str = utils.dict_or_object_get_attr(menu_item, "format", "%f")
        is_big_endian = self.menu_data_is_big_endian(menu_item)
        action_str = utils.dict_or_object_get_attr(menu_item, "action", None)

        if action_str == "time" :
            value = self.menu_get_value_regs(menu_item)
            value_str = self.menu_get_time_string(value)
        elif action_str == "date" :
            value = self.menu_get_value_regs(menu_item)
            value_str = self.menu_get_date_string(value)
        elif action_str == "ip" :
            value = self.menu_get_value_regs(menu_item)
            value_str = self.menu_get_ip_string(value, is_big_endian)
        elif action_str != None:
            return None
        else :
            new_value = self.menu_get_value_scale_float(menu_item)
            if new_value != None : 
                value_str = format_str%(new_value)

        return value_str

    def menu_display_page(self, page) :
        self.update_server_register(modbus_server.REGISTER_MENU_DISPLAY_PAGE, page)

    def menu_popup_modify_status_change(self, active = False) :
        self.update_server_register(modbus_server.REGISTER_MENU_POPUP_MODIFY_ACTIVE, int(active))

    def menu_remap_addr_flush(self, menu_table) :
        if menu_table == None:
            return
        for menu_item in menu_table:
            sub_menu_name = utils.dict_or_object_get_attr(menu_item, "sub_menu", None)
            sub_menu = self.config.get_menu(sub_menu_name)
            
            if sub_menu :
                #子菜单递归调用
                self.menu_remap_addr_flush(sub_menu)
                continue
            remap_addr = utils.dict_or_object_get_attr(menu_item, "remap_addr", None)
            if remap_addr != None :
                #需要加上设备重映射基础地址
                remap_addr += self.device_remap
                new_value = self.menu_get_value_scale_float(menu_item)
                if type(new_value) == float:
                    reg_values = []
                    bs = struct.pack('f', new_value)
                    reg_values.append(bs[0] | bs[1] << 8)
                    reg_values.append(bs[2] | bs[3] << 8)
                    self.update_server_register(remap_addr, reg_values)

    def menu_top_remap_addr_flush(self) :
        self.menu_remap_addr_flush(self.menu_table)
  
    def menu_display(self):
        menu_count = 0
        row = 0
        col = 0
        active_layer  : class_menu_layer_info = self.menu_get_layer_info()
        menu_active = active_layer.menu_active

        menu_index = 0
        for menu_item in menu_active :
            if menu_index  < active_layer.menu_display_start:
                menu_index += 1
                continue

            caption = DEFAULT_EMPTY_STR
            if "name" in menu_item.keys() :
                caption = menu_item["name"]
            col = 0
            self.menu_display_string(row, col, caption)
            
            sub_menu_name = utils.dict_or_object_get_attr(menu_item, "sub_menu", None)
            sub_menu = self.config.get_menu(sub_menu_name)

            if sub_menu != None:
                self.menu_display_string(row, col + 1, " ")
            else :
                alias_str = self.menu_get_alias_value_string(menu_item)
                if alias_str :
                    self.menu_display_string(row, col + 1, alias_str)
                else :
                    value_str = self.menu_get_value_string(menu_item)
                    if value_str != None :
                        unit_str = utils.dict_or_object_get_attr(menu_item, "unit", DEFAULT_EMPTY_STR)
                        value_str = value_str + " " + unit_str
                        self.menu_display_string(row, col + 1, value_str)
                    else :
                        self.menu_display_string(row, col + 1, DEFAULT_EMPTY_STR)

            row = row + 1
            menu_count += 1
            if menu_count >= self.max_menu_lines :
                break

        while row < self.max_menu_lines :
            self.menu_display_string(row, 0, DEFAULT_EMPTY_STR)
            self.menu_display_string(row, 1, DEFAULT_EMPTY_STR)
            row += 1

    def menu_get_alias_dict(self, alias_name) :
        alias_value_dict = None
        if alias_name != None and self.alias_table != None:
            for alias_dict in self.alias_table :
                if alias_name in alias_dict.keys() :
                    alias_value_dict = alias_dict[alias_name]
        return alias_value_dict

    def menu_get_alias_value_string(self, menu_item) :
        alias = utils.dict_or_object_get_attr(menu_item, "alias", None)
        if alias == None :
            return None

        alias_dict = self.menu_get_alias_dict(alias)
        if alias_dict == None :
            return None
        
        value = self.menu_get_value_scale_float(menu_item)
        alias_value_str = None
        if alias_dict != None and value != None:
            alias_value = int(value)
            if alias_value in alias_dict.keys() :
                alias_value_str = alias_dict[alias_value]
        return alias_value_str

    def menu_modify_value_string(self, value_string, unit_string = DEFAULT_EMPTY_STR, caption_string = DEFAULT_EMPTY_STR) :
        sel_bit = 0 
        if value_string == None:
            return None
        
        modify_value_string = value_string
        key = KEYDEF.MENU_PACK_KEY_NULL
        while True :
            key = comm_global.g_key_thread.menu_wait_key(0.1)
            value_string_display = modify_value_string
            if key == KEYDEF.MENU_PACK_KEY_RESET_OR_UP or key == KEYDEF.MENU_PACK_KEY_ESCAPE :
                break
            if key == KEYDEF.MENU_PACK_KEY_DOWN :
                if str.isdigit(modify_value_string[sel_bit]) :
                    modify_value_list = list(modify_value_string)
                    modify_value_list[sel_bit] = chr(ord(modify_value_list[sel_bit]) - 1)
                    if modify_value_list[sel_bit] < '0':
                        modify_value_list[sel_bit] = '9'
                    modify_value_string = ''.join(modify_value_list)
                    
            elif key == KEYDEF.MENU_PACK_KEY_UP :
                if str.isdigit(modify_value_string[sel_bit]) :
                    modify_value_list = list(modify_value_string)
                    modify_value_list[sel_bit] = chr(ord(modify_value_list[sel_bit]) + 1)                    
                    if modify_value_list[sel_bit] > '9':
                        modify_value_list[sel_bit] = '0' 
                    modify_value_string = ''.join(modify_value_list)
                    
            elif key == KEYDEF.MENU_PACK_KEY_ENTER_OR_RIGHT or key == KEYDEF.MENU_PACK_KEY_RIGHT:
                while sel_bit < len(modify_value_string):
                    sel_bit += 1
                    if sel_bit >= len(modify_value_string) :
                        key = KEYDEF.MENU_PACK_KEY_ENTER
                        break
                    if str.isdigit(modify_value_string[sel_bit]) :
                        break
                if sel_bit >= len(modify_value_string):
                    break

            value_string_list = list(value_string_display)
            value_string_list[sel_bit] = ' '
            value_string_display = ''.join(value_string_list)
            
            value_string_display_select  = modify_value_string[0 : sel_bit + 1]
            
            self.menu_display_modify_caption_string(caption_string)
            self.menu_display_modify_string(value_string_display + unit_string)
            self.menu_display_modify_select_string(value_string_display_select)
            
        if key == KEYDEF.MENU_PACK_KEY_ENTER or key == KEYDEF.MENU_PACK_KEY_ENTER_OR_RIGHT:
            return modify_value_string
        return value_string

    def menu_modify_date_menu_item(self, menu_item) :
        origin_value = self.menu_get_value_regs(menu_item)
        value_string = self.menu_get_value_string(menu_item)
        unit_string = utils.dict_or_object_get_attr(menu_item, "unit", DEFAULT_EMPTY_STR)
        new_value_string:str = self.menu_modify_value_string(value_string, unit_string)
        if new_value_string != value_string :
            date_str =  new_value_string.split('-')
            new_value = origin_value

            if len(date_str) == 3:
                new_value = []
                year = int(date_str[0])
                month = int(date_str[1])
                day = int(date_str[2])
                new_value.append(year)
                new_value.append(month)
                new_value.append(day)
            return new_value, True
        else :
            return origin_value, False

    def menu_modify_time_menu_item(self, menu_item) :
        origin_value = self.menu_get_value_regs(menu_item)
        value_string = self.menu_get_value_string(menu_item)
        unit_string = utils.dict_or_object_get_attr(menu_item, "unit", DEFAULT_EMPTY_STR)
        new_value_string:str = self.menu_modify_value_string(value_string, unit_string)
        if new_value_string != value_string :
            date_str =  new_value_string.split(':')
            new_value = origin_value

            if len(date_str) == 3:
                new_value = []
                hour = int(date_str[0])
                minute = int(date_str[1])
                second = int(date_str[2])
                new_value.append(hour)
                new_value.append(minute)
                new_value.append(second)
            return new_value, True
        else :
            return origin_value, False

    def menu_get_date_string(self, date_value) :
        value_string = None
        if date_value != None:
            if len(date_value) == 3 :
                year = date_value[0]
                month = date_value[1]
                day = date_value[2]  
                value_string = "%04d-%02d-%02d"%(year, month, day) 
        return value_string

    def menu_get_time_string(self, time_value) :
        value_string = None
        if time_value != None :
            if len(time_value) == 3 :
                hour = time_value[0]
                minute = time_value[1]
                second = time_value[2]
                value_string = "%02d:%02d:%02d"%(hour, minute, second) 
        return value_string

    def menu_get_ip_string(self, ip_value, is_big_endian = False) :
        value_string:str = None
        if ip_value != None:
            if len(ip_value) == 2:
                if is_big_endian:
                    str_ip1 = "%03d"%(((ip_value[1] >> 0) & 0xFF),)
                    str_ip2 = "%03d"%(((ip_value[1] >> 8) & 0xFF),)
                    str_ip3 = "%03d"%(((ip_value[0] >> 16) & 0xFF),)
                    str_ip4 = "%03d"%(((ip_value[0] >> 24) & 0xFF),)  
                else :
                    str_ip1 = "%03d"%(((ip_value[0] >> 0) & 0xFF),)
                    str_ip2 = "%03d"%(((ip_value[0] >> 8) & 0xFF),)
                    str_ip3 = "%03d"%(((ip_value[1] >> 16) & 0xFF),)
                    str_ip4 = "%03d"%(((ip_value[1] >> 24) & 0xFF),)
                value_string = "%s.%s.%s.%s"%(str_ip1, str_ip2, str_ip3, str_ip4) 
        return value_string

    def menu_get_ip_value(self, ip_str:str) :
        ip = ip_str.split(sep = '.')
        ip_value = []

        if len(ip) == 4 :
            ip0 = int(ip[0])
            ip1 = int(ip[1])
            ip2 = int(ip[2])
            ip3 = int(ip[3])
            ip_value.append((ip0 << 0) | (ip1 << 8))
            ip_value.append((ip2 << 16) | (ip3 << 24))
        return ip_value

    def menu_data_is_big_endian(self, menu_item) :
        comm_str = utils.dict_or_object_get_attr(menu_item, "addr", DEFAULT_EMPTY_STR)
        is_big_endian = False
        if comm_str :
            if "#>" in comm_str :
                is_big_endian = True
        return is_big_endian

    def menu_data_is_float(self, menu_item) :
        comm_str = utils.dict_or_object_get_attr(menu_item, "addr", DEFAULT_EMPTY_STR)
        is_float = False
        if comm_str :
            if "#f" in comm_str :
                is_float = True
        return is_float
     
    def menu_modify_ip_menu_item(self, menu_item) :
        origin_value  = self.menu_get_value_regs(menu_item)
        is_big_endian = self.menu_data_is_big_endian(menu_item)
            
        value_string:str = self.menu_get_ip_string(origin_value, is_big_endian)
        unit_string = DEFAULT_EMPTY_STR
        new_value_string = self.menu_modify_value_string(value_string, unit_string)
        if new_value_string != value_string :
            new_value = self.menu_get_ip_value(new_value_string)
            return new_value, True
        else :
            return origin_value, False

    def menu_update_modify_regs(self, menu_item, old_value_regs, modify_value) :
        comm_str = utils.dict_or_object_get_attr(menu_item, "addr", DEFAULT_EMPTY_STR)
        reg_addr, bit, reg_count = utils.comm_str_unpack(comm_str)
        scale = utils.dict_or_object_get_attr(menu_item, "scale", 1.0)
        is_float = self.menu_data_is_float(menu_item)
        is_big_endian = self.menu_data_is_big_endian(menu_item)

        if modify_value == None :
            return old_value_regs

        modify_regs = None
        if len(old_value_regs) == 2:
            modify_regs = []
            modify_value = modify_value / scale

            if is_float : 
                #把浮点数转换成4字节数, 然后转化成32位整数
                bs = struct.pack("f", modify_value)
                modify_value = bs[0] | (bs[1] << 8) | (bs[2] << 16) | (bs[3] << 24)
            else :
                modify_value = int(modify_value)

            if bit >= 0:
                if is_big_endian :
                    old_value = (old_value_regs[0] << 16) | old_value_regs[1]
                else :
                    old_value = (old_value_regs[1] << 16) | old_value_regs[0]

                clear_mask = (0xFFFFFFFF >> (32 - reg_count)) << bit
                set_mask   = (modify_value << bit) & clear_mask
                modify_value = (old_value & ~clear_mask) | set_mask

            if is_big_endian :
                modify_regs.append((modify_value >> 16) & 0xFFFF)
                modify_regs.append(modify_value & 0xFFFF)     
            else :
                modify_regs.append(modify_value & 0xFFFF)
                modify_regs.append((modify_value >> 16) & 0xFFFF)
        elif  len(old_value_regs) == 1:
            modify_regs = []
            modify_value = int(modify_value / scale)
            if bit >= 0:
                old_value = old_value_regs[0]
                clear_mask = (0xFFFFFFFF >> (32 - reg_count)) << bit
                set_mask   = (modify_value << bit) & clear_mask
                modify_value = (old_value & ~clear_mask) | set_mask
            modify_regs.append(int(modify_value) & 0xFFFF)

        return modify_regs
              
    def menu_modify_normal_menu_item(self, menu_item) ->Tuple[list, bool]:
        old_value_regs = self.menu_get_value_regs(menu_item)
        value_string = self.menu_get_value_string(menu_item)
        
        new_value_string = value_string
        if value_string != None:
            unit_string = utils.dict_or_object_get_attr(menu_item, "unit", DEFAULT_EMPTY_STR)
            new_value_string = self.menu_modify_value_string(value_string, unit_string)

        if new_value_string != value_string and value_string != None:
            modify_value = float(new_value_string)
            new_value_regs = self.menu_update_modify_regs(menu_item, old_value_regs, modify_value)
            return new_value_regs, True
        else :
            return old_value_regs, False
     
    def menu_modify_alias_menu_item(self, menu_item):
        alias = utils.dict_or_object_get_attr(menu_item, "alias", None)
        alias_dict = self.menu_get_alias_dict(alias)
        old_value_regs = self.menu_get_value_regs(menu_item)

        if old_value_regs == None:
            return old_value_regs, False
        
        fvalue = self.menu_get_value_scale_float(menu_item)
        alias_value = int(fvalue)

        alias_value_index = -1
        search_index = 0
        for item in alias_dict.keys():
            if item == alias_value :
                alias_value_index = search_index
                break
            search_index += 1

        if alias_value_index < 0 :
            return old_value_regs, False

        while True :
            key = comm_global.g_key_thread.menu_wait_key(0.1)
            
            if key == KEYDEF.MENU_PACK_KEY_DOWN :
                alias_value_index += 1
                if alias_value_index >= len(alias_dict) :
                    alias_value_index = 0
                    
            elif key == KEYDEF.MENU_PACK_KEY_UP :
                alias_value_index -= 1
                if alias_value_index < 0 :
                    alias_value_index = len(alias_dict) - 1
                    
            new_alias_value = None
            new_alias_str = DEFAULT_EMPTY_STR

            search_index = 0
            for new_alias_value, new_alias_str in alias_dict.items() :
                if search_index == alias_value_index :
                    break
                search_index += 1

            self.menu_display_modify_string(new_alias_str)
            self.menu_display_modify_select_string(new_alias_str)

            if key == KEYDEF.MENU_PACK_KEY_ENTER or key == KEYDEF.MENU_PACK_KEY_ENTER_OR_RIGHT :
                new_value_regs = self.menu_update_modify_regs(menu_item, old_value_regs, new_alias_value)                  
                is_modified = True if (new_value_regs != old_value_regs) else False
                return new_value_regs, is_modified
            if key == KEYDEF.MENU_PACK_KEY_RESET_OR_UP:
                return old_value_regs, False

    def _menu_adjust_select_item(self, increase) :
        active_layer : class_menu_layer_info = self.menu_get_layer_info()

        active_layer.menu_select += increase
        if increase > 0 and active_layer.menu_select >= active_layer.menu_item_count :
            active_layer.menu_select = 0
            
        if active_layer.menu_select < 0 :
            active_layer.menu_select = active_layer.menu_item_count - 1
        
        if active_layer.menu_display_start + self.max_menu_lines <= active_layer.menu_select :
            active_layer.menu_display_start = active_layer.menu_select - self.max_menu_lines + 1

        if active_layer.menu_select < active_layer.menu_display_start :
           active_layer.menu_display_start = active_layer.menu_select
    
    def menu_process(self, key = KEYDEF.MENU_PACK_KEY_NULL) -> Tuple[bool] : 
        active_layer  : class_menu_layer_info = self.menu_get_layer_info()
        if active_layer == None :
            return False

        result = False

        select_row = active_layer.menu_select - active_layer.menu_display_start
        if select_row >= 0:
            self.update_server_register(modbus_server.REGISTER_MENU_ITEM_SELECT_MASK, 0x01 << select_row)
        if key == KEYDEF.MENU_PACK_KEY_UP :
            self._menu_adjust_select_item(-1)
            self.update_server_register(modbus_server.REGISTER_MENU_ITEM_SELECT_MASK, 0x01 << select_row)
        elif key == KEYDEF.MENU_PACK_KEY_DOWN :
            self._menu_adjust_select_item(1)
            self.update_server_register(modbus_server.REGISTER_MENU_ITEM_SELECT_MASK, 0x01 << select_row)
        elif key == KEYDEF.MENU_PACK_KEY_RESET_OR_UP or key == KEYDEF.MENU_PACK_KEY_ESCAPE:
            self.menu_return_to_upper_layer()
        elif key == KEYDEF.MENU_PACK_KEY_ENTER_OR_RIGHT or key == KEYDEF.MENU_PACK_KEY_ENTER :
            menu_item = self.menu_get_active_item()

            #地址映射有效, 修改映射地址
            device_remap = utils.dict_or_object_get_attr(menu_item, "device_remap", None)
            if device_remap != None:
                self.menu_update_device_remap_addr(device_remap)

            function = utils.dict_or_object_get_attr(menu_item, "function", None)
            if function != None :
                device_menu = self
                result = function(device_menu, menu_item)
                if result :
                    return True
                
            sub_menu_name = utils.dict_or_object_get_attr(menu_item, "sub_menu", None)
            sub_menu = self.config.get_menu(sub_menu_name)
            
            if sub_menu :
                self.menu_enter_sub_menu(sub_menu)
                return True
            
            format_str = utils.dict_or_object_get_attr(menu_item, "format", None)
            
            action_str = utils.dict_or_object_get_attr(menu_item, "action", None)
            if action_str == "exit" :
                self.menu_return_to_upper_layer()
                return True
            elif action_str == "exit2" :
                self.menu_return_to_most_upper_layer()
                return True

            self.menu_popup_modify_status_change(active = True)
            changed = False
            new_value = None 

            if action_str == "time":
                new_value, changed = self.menu_modify_time_menu_item(menu_item)
            elif action_str == "date" :
                new_value, changed = self.menu_modify_date_menu_item(menu_item)
            elif action_str == "ip" :
                new_value, changed = self.menu_modify_ip_menu_item(menu_item)
            elif action_str != None :
                return False
            else :
                alias = utils.dict_or_object_get_attr(menu_item, "alias", None)
                if alias != None :
                    new_value, changed = self.menu_modify_alias_menu_item(menu_item)
                else:
                    new_value, changed = self.menu_modify_normal_menu_item(menu_item)
            
            comm_str = utils.dict_or_object_get_attr(menu_item, "addr", DEFAULT_EMPTY_STR)
            reg_addr, bit, reg_count = utils.comm_str_unpack(comm_str)
            
            if changed and new_value != None:    
                comm_device :class_comm_device = None            

                comm_device, comm_thread, device_menu = comm_global.search_device_remap_infos(self.device_remap)
                if comm_thread != None and comm_device != None and reg_count > 0:
                    result = comm_device.write_register(reg_addr, reg_count, new_value)
                    #result = comm_thread.comm_hold_register_write(comm_addr, reg_addr, reg_count, new_value)

            self.menu_popup_modify_status_change(active = False)
        return result

    def update_server_register(self, addr, value) :
        if self.modbus_tcp_server :
            self.modbus_tcp_server.update_server_register(addr, value)

    def run(self) :
        while True:
            key = comm_global.g_key_thread.menu_wait_key(0.1)
            
            page = None
            active_menu = self.menu_get_active_menu()
            if self.config != None :
                page = self.config.menu_get_display_page(active_menu)
            if page == None :
                page = menu_page.KUNLUN_GRAOUP_PAGE_MENU
                
            self.menu_display_page(page)
            self.menu_process(key)
            self.menu_display()

'''
可使用的函数:
创建从站: server.add_slave(slave_id)
    slave_id(int):从站id
为从站添加存储区: slave.add_block(block_name, block_type, starting_address, size)

    block_name(str):block名
    block_type(int):block类型,COILS = 1,DISCRETE_INPUTS = 2,HOLDING_REGISTERS = 3,ANALOG_INPUTS = 4
    starting_address(int):起始地址
    size(int):block大小
设置block值:slave.set_values(block_name, address, values)
    block_name(str):block名
    address(int):开始修改的地址
    values(a list or a tuple or a number):要修改的一个(a number)或多个(a list or a tuple)值
获取block值:slave.get_values(block_name, address, size)
    block_name(str):block名
    address(int):开始获取的地址
    size(int):要获取的值的数量
'''

if __name__ == '__main__':
    comm_device_config: class_comm_device_config = device_9TE.comm_device_config
    _slave_addr  = 2

    _device_remap = 2000
    
    _modbus_tcp_server = class_modbus_tcp_server(slave_addr = _slave_addr,
                                                slave_ip= "192.168.1.54", 
                                                slave_port = 502)

    object_menu_group = class_menu_group(device_remap =_device_remap,
                                         comm_config = comm_device_config,
                                         modbus_tcp_server = _modbus_tcp_server,
                                         max_menu_lines = 8)

    object_menu_group.start()
    
