diff --git a/1.py b/1.py new file mode 100644 index 0000000..fa756f5 --- /dev/null +++ b/1.py @@ -0,0 +1,2 @@ +from kivy.properties import ObjectProperty, StringProperty +# print("ok") \ No newline at end of file diff --git a/assets/_img.png b/assets/_img.png new file mode 100644 index 0000000..0c44ff8 Binary files /dev/null and b/assets/_img.png differ diff --git a/assets/img.bmp b/assets/img.bmp deleted file mode 100644 index 48368c2..0000000 Binary files a/assets/img.bmp and /dev/null differ diff --git a/assets/img.png b/assets/img.png index 0c44ff8..c63fb4f 100644 Binary files a/assets/img.png and b/assets/img.png differ diff --git a/comm_device.py b/comm_device.py new file mode 100644 index 0000000..e460beb --- /dev/null +++ b/comm_device.py @@ -0,0 +1,99 @@ +import struct +from comm_protocol import class_protocol + +DEFAULT_COMM_WRITE_TIMEOUT_MS = 500 + +#通讯设备基类, 派生类需要实现 device_comm_cycle函数, read_register函数, write_registers函数 +class class_comm_device() : + def __init__(self, + comm_addr = 0, + device_remap = None, + unique_name = None, + protocol : class_protocol = None) : + + self.comm_active = True + self.comm_addr = comm_addr + self.device_remap = device_remap + self.uniqut_name = unique_name + self.protocol = protocol + + #该函数为虚基类, 本身未实现read_register, 以下为实现范例, 需要在派生类中实现 + def read_register(self, reg_addr, reg_count) : + raise NotImplementedError() + + #bool write_registers(int reg_addr, reg_count, uint16_t value[]) + #该函数为虚基类, 本身未实现write_registers, 以下为实现范例, 需要在派生类中实现 + def write_register(self, reg_addr, reg_count, new_value, timeout_ms = DEFAULT_COMM_WRITE_TIMEOUT_MS) : + raise NotImplementedError() + + #bool write_bit_register(int reg_addr, uint16_t value[]) + #bool write_bit_register(int reg_addr, uint16_t value) + def write_bit_register(self, reg_addr, value, timeout_ms = DEFAULT_COMM_WRITE_TIMEOUT_MS) : + raise NotImplementedError() + + def device_comm_cycle(self) : + comm_success_count = 0 + total_comm_count = 0 + return comm_success_count, total_comm_count + + def device_comm_flush_request(self, reg_addr, reg_count, cycle = 3000) : + return True + + def device_comm_deactive(self) : + self.comm_active = False + + def device_comm_active(self) : + self.comm_active = True + + + def value_u16_to_s16(self, value) : + if value == None: + return None + svalue = value + if (svalue & 0x8000) : + svalue = -((svalue ^ 0xFFFF) + 1) + return svalue + + def value_u32_to_s32(self, value) : + if value == None: + return None + svalue = value + if (svalue & 0x80000000) : + svalue = -((svalue ^ 0xFFFFFFFF) + 1) + return svalue + + def read_register_count(self, reg_addr, reg_count) : + return self.read_register(reg_addr, reg_count) + + def read_register_u16(self, reg_addr) : + result = self.read_register_count(reg_addr, 1) + if result : + result = (result[0] & 0xFFFF) + return result + + def read_register_s16(self, reg_addr) : + result = self.read_register_u16(reg_addr) + sign_result = self.value_u16_to_s16(result) + return sign_result + + def read_register_u32(self, reg_addr, big_endian = 0) : + result = self.read_register_count(reg_addr, 2) + if result : + if big_endian: + result = (result[1] & 0xFFFF) | ((result[0] & 0xFFFF) << 16) + else : + result = (result[0] & 0xFFFF) | ((result[1] & 0xFFFF) << 16) + return result + + def read_register_s32(self, reg_addr, big_endian = 0) : + result = self.read_register_u32(reg_addr, big_endian) + sign_value = self.value_u32_to_s32(result) + return sign_value + + def read_register_f32(self, reg_addr, big_endian = 0) : + result = self.read_register_u32(reg_addr, big_endian) + fvalue = None + if result : + bytes = struct.pack('BBBB', result & 0xFF, (result >> 8) & 0xFF, (result >> 16) & 0xFF, (result >> 24) & 0xFF) + fvalue = struct.unpack("= reg_addr_begin and reg_addr + reg_count <= reg_addr_end : + return True + return False + + def update_item_value(self, reg_addr, reg_count, new_value) : + if self.in_range(reg_addr, reg_count) and len(self.reg_buf) : + reg_off = reg_addr - self.reg_addr + new_reg = list(self.reg_buf) + for i in range(reg_count) : + new_reg[reg_off + i] = new_value[i] + self.reg_buf = new_reg + self.comm_trigger = True + + def read_register_count(self, reg_addr, reg_count) : + reg_addr_begin = self.reg_addr + reg_addr_end = self.reg_addr + self.reg_count + result = None + if reg_addr >= reg_addr_begin and reg_addr + reg_count <= reg_addr_end : + if len(self.reg_buf) >= self.reg_count: + result = [] + reg_off = reg_addr - self.reg_addr + for i in range(reg_count): + result.append(self.reg_buf[reg_off + i]) + + return result + +#为了防止阻塞 mqtt订阅流程, 采用class_mqtt_process_thread线程来处理 接收到的主题与消息 +class class_mqtt_process_thread(Thread): + def __init__(self, + parent_device, + topic : str = None, + message : str = None, + ): + + Thread.__init__(self) + self.topic = topic + self.message = message + self.parent_device : class_modbus_comm_device = parent_device + + def run(self): + self.parent_device.on_process_topic_message(self.topic, self.message) + +#modbus通讯设备, 管理modbus主机通讯, 实现mqtt客户端 +class class_modbus_comm_device(class_comm_device, class_comm_mqtt_interface) : + def __init__(self, + comm_addr = 0, + device_remap = None, + comm_table = None, + func_comm_trigger = None, + mqtt_thread : class_comm_mqtt_thread = None, + protocol : class_protocol_modbus = None, + mqtt_info_object : class_mqtt_info_object = None, + unique_name = None, + action_process_func = None, #action处理函数 + request_alarm_func = None, #故障查询函数 + alias_table = None): + class_comm_device.__init__(self, comm_addr, device_remap, unique_name) + class_comm_mqtt_interface.__init__(self) + + self.comm_loop_count = 0 + self.comm_index = 0 + self.mqtt_thread = mqtt_thread + self.comm_device_comm_active = True #设置该设备是否需要通讯 + self.comm_fail_delay = 0 #通讯延时 + self.func_comm_trigger = func_comm_trigger + self.protocol : class_protocol_modbus = protocol + self.mqtt_info_object : class_mqtt_info_object = mqtt_info_object + self.unique_name : str = unique_name + self.action_process_func = action_process_func + self.request_alarm_func = request_alarm_func + self.alias_table = alias_table + + self.comm_table = comm_table + self.comm_table_item_list : class_comm_table_item = [] #list of class_comm_table_item + self.create_comm_item_list(comm_table) + + def modbus_regs_flush(self, reg_addr, reg_count, values) : + for each in self.comm_table_item_list: + comm_item : class_comm_table_item = each + comm_item.update_item_value(reg_addr, reg_count, values) + + def create_comm_item_list(self, comm_table : class_comm_table_item) : + self.comm_table_item_list = [] + if comm_table != None : + for comm_item_dict in comm_table : + _name = utils.dict_or_object_get_attr(comm_item_dict, "name", " ") + _reg_addr = utils.dict_or_object_get_attr(comm_item_dict, "reg_addr", 0) + _reg_count = utils.dict_or_object_get_attr(comm_item_dict, "reg_count", 0) + _cycle = utils.dict_or_object_get_attr(comm_item_dict, "cycle", 0) + _mqtt_pack = utils.dict_or_object_get_attr(comm_item_dict, "mqtt_pack", None) + self.comm_table_item_list.append(class_comm_table_item(name = _name, + reg_addr =_reg_addr, + reg_count = _reg_count, + cycle = _cycle, + mqtt_pack = _mqtt_pack)) + + #@override, mqtt on connect + def on_connect(self, mqtt_thread, userdata, flags, rc) : + if rc == 0 : + #获取线程类 (class_comm_mqtt_thread) + mqtt_comm_thread : class_comm_mqtt_thread= mqtt_thread + #订阅相关主题, 获取参数, 修改参数, 获取测量值, 获取报警信息 + mqtt_comm_thread.subscribe("request/param/info/#") + mqtt_comm_thread.subscribe("request/param/modify/#") + mqtt_comm_thread.subscribe("request/measure/#") + mqtt_comm_thread.subscribe("request/status/#") + mqtt_comm_thread.subscribe("request/alarm/#") + mqtt_comm_thread.subscribe("request/action/#") + mqtt_comm_thread.subscribe("request/alias/#") + + def alias_table_convert(self) : + new_list_dict = [] + + for each_dict in self.alias_table : + for key_info, value_dict in each_dict.items() : + if isinstance(key_info, Enum) : + name = key_info.name + else : + name = key_info + + new_dict = {} + for dict_key, dict_value in value_dict.items() : + if isinstance(dict_key, Enum) : + dict_key_value = dict_key.value[0] + else : + dict_key_value = dict_key + + new_dict[dict_key_value] = dict_value + + new_list_dict.append({name : new_dict}) + + return new_list_dict + + #该函数在线程里面调用, 不会阻塞mqtt处理订阅的消息 + def on_process_topic_message(self, topic, message) : + try : + if isinstance(message, bytes) : + json_dict = json.loads(message.decode('utf-8')) + else : + json_dict = json.loads(message.encode('utf-8')) + print_normal_msg(f"有效的mqtt(topic = {topic}, message = {message})") + except Exception as e: + print_error_msg(f"{str(e)}无效的mqtt(topic = {topic}, message = {message})") + json_dict = None + + mqtt_info_name = utils.dict_or_object_get_attr(json_dict, "name", None) + value_str = utils.dict_or_object_get_attr(json_dict, "value", math.nan) + + + # if mqtt_info_name != None : + # mqtt_menu_item = self.mqtt_info_object.search_menu_item(mqtt_info_name) + # else : + # mqtt_menu_item = None + if mqtt_info_name != None: + mqtt_menu_item = self.mqtt_info_object.search_menu_item(mqtt_info_name) + else : + mqtt_menu_item = None + print_normal_msg(f"执行操作: topic={topic}, mqtt_info_name={mqtt_info_name}") + + if "request/alias" in topic : #获取别名 + new_alias_table = self.alias_table_convert() + if mqtt_info_name == None : + msg : str = json.dumps(new_alias_table, ensure_ascii=False) + if self.mqtt_thread != None : + self.mqtt_thread.publish("response/alias/" + self.unique_name, msg) + else : + search_dict = None + for each_dict in new_alias_table : + if mqtt_info_name in each_dict.keys() : + search_dict = each_dict + break + if search_dict : + msg : str = json.dumps(search_dict, ensure_ascii=False) + else : + msg : str = '{"%s" : None}'%(mqtt_info_name) + if self.mqtt_thread != None : + self.mqtt_thread.publish("response/alias/" + self.unique_name, msg) + elif "request/alarm" in topic: #故障查询 + if self.request_alarm_func != None: + fvalue = utils.dict_or_object_get_attr(json_dict, "index", math.nan) + if isinstance(fvalue, str) : + fvalue = float(fvalue) + if fvalue != math.nan : + alarm_index = round(fvalue) + self.request_alarm_func(self, alarm_index) + elif "request/action" in topic : + if self.action_process_func : + self.action_process_func(self, mqtt_menu_item, value_str) + + elif "request/param/info" in topic: #读取参数 + _scale = utils.dict_or_object_get_attr(mqtt_menu_item, "scale", 1.0) + _offset = utils.dict_or_object_get_attr(mqtt_menu_item, "offset", 0.0) + _comm_str = utils.dict_or_object_get_attr(mqtt_menu_item, "addr", None) + _alias_name = utils.dict_or_object_get_attr(mqtt_menu_item, "alias", None) + if isinstance(_alias_name, Enum): + _alias_name = _alias_name.name + + _format = utils.dict_or_object_get_attr(mqtt_menu_item, "format", "%f") + _fvalue = math.nan + + if _comm_str : + _fvalue = self.protocol.read_float(device_addr = self.comm_addr, + comm_str = _comm_str, + scale = _scale, + offset = _offset + ) + # if _alias_name != None : + # # if _fvalue == None or _fvalue == math.nan: + # # _format_str = "NAN" + # if _fvalue is None or math.isnan(_fvalue): + # _fvalue = 0 + # else : + # _format_str = "%d"%(round(_fvalue)) + # if self.mqtt_thread != None : + # self.mqtt_thread.publish("response/param/info/" + self.unique_name, '{"%s" : {"alias" : "%s", "value": "%s"}}'%(mqtt_info_name, _alias_name, _format_str)) + # else : + # _format_str = _format%(_fvalue) + # if self.mqtt_thread != None : + # self.mqtt_thread.publish("response/param/info/" + self.unique_name, '{"%s" : "%s"}'%(mqtt_info_name, _format_str)) + # else: + # print_warning_msg(f"未处理的请求:{topic, json_dict}") + if _alias_name is not None: + # 处理 _fvalue 的情况 + if _fvalue is None or math.isnan(_fvalue): + _fvalue = 0 + _format_str = "%d" % (round(_fvalue)) + else: + # 当 _alias_name 为 None 时处理 + _format_str = _format % (_fvalue) if _fvalue is not None and not math.isnan(_fvalue) else "UNKNOWN" + + # 发布消息 + if self.mqtt_thread is not None: + if _alias_name is not None: + self.mqtt_thread.publish("response/param/info/" + self.unique_name, + '{"%s" : {"alias" : "%s", "value": "%s"}}' % (mqtt_info_name, _alias_name, _format_str)) + else: + self.mqtt_thread.publish("response/param/info/" + self.unique_name, + '{"%s" : "%s"}' % (mqtt_info_name, _format_str)) + else: + print_warning_msg(f"未处理的请求:{topic, message}") + + + + elif "request/param/modify" in topic : #写入参数 + + mqtt_menu_item = self.comm_item_item_adjust(mqtt_menu_item) + scale = utils.dict_or_object_get_attr(mqtt_menu_item, "scale", 1.0) + format_str = utils.dict_or_object_get_attr(mqtt_menu_item, "format", "%1.0f") + offset = utils.dict_or_object_get_attr(mqtt_menu_item, "offset", 0.0) + _comm_str = utils.dict_or_object_get_attr(mqtt_menu_item, "addr", None) + + _fvalue = value_str + if isinstance(_fvalue, str) : + modify_fvalue = float(_fvalue) + else : + modify_fvalue = _fvalue + + _fvalue_origin = modify_fvalue / scale - offset + if _comm_str : + result = self.protocol.write_float(device_addr = self.comm_addr, + comm_str = _comm_str, + fvalue = _fvalue_origin) + if self.mqtt_thread != None : + self.mqtt_thread.publish("response/param/modify/" + self.unique_name, '{"name" : "%s", "result":%d}'%(mqtt_info_name, result)) + else: + print_warning_msg(f"未处理的请求:{topic, json_dict}") + elif mqtt_menu_item == None : + print_warning_msg(f"mqtt无法找到对应信息项(topic = {topic}, message = {json_dict})") + else : + pass + + #@override, mqtt subscirbe message process + def on_message(self, mqtt_thread, topic, message) : + if self.mqtt_info_object == None : + return False + if "request/alias" in topic : #获得别名不需要协议通讯, 此处直接处理即可 + self.on_process_topic_message(topic, message) + elif "request" in topic : + new_thread : class_mqtt_process_thread = class_mqtt_process_thread(self, topic, message) + new_thread.start() + return True + + #通过通讯字符串与scale类型, 获取浮点格式实际值, 通讯失败时返回None + def comm_item_get_value_float(self, comm_addr_str, scale = 1.0, offset = 0) : + reg_value = None + + if comm_addr_str != None: + reg_addr, bit, reg_count = utils.comm_str_unpack(comm_addr_str) + is_float = True if "#f" in comm_addr_str else False + is_big_endian = True if "#>" in comm_addr_str else False + is_sign = True if "#s" in comm_addr_str else False + + if reg_count == 1 : + if is_sign : + reg_value = self.read_register_s16(reg_addr) + else : + reg_value = self.read_register_u16(reg_addr) + if reg_count == 2 : + if is_float : + reg_value = self.read_register_f32(reg_addr, is_big_endian) + elif is_sign : + reg_value = self.read_register_s32(reg_addr, is_big_endian) + else : + reg_value = self.read_register_u32(reg_addr, is_big_endian) + + if bit >= 0 and reg_value != None: + reg_value = (reg_value >> bit) + reg_value = reg_value & (0xFFFFFFFF >> (32 - reg_count)) + + if reg_value != None : + reg_value = reg_value * scale + offset + + return reg_value + + #有些菜单项的scale, format或其他属性可以动态调节, 返回调整后的菜单项或 原菜单项 + def comm_item_item_adjust(self, menu_item) : + adjust_menu_item = None + func_item_adjust = utils.dict_or_object_get_attr(menu_item, "adjust", None) + if func_item_adjust != None : + adjust_menu_item = func_item_adjust(self, menu_item) + if adjust_menu_item == None : + adjust_menu_item = menu_item + return adjust_menu_item + + #通过菜单项中 特定的回调函数, 使每个菜单项可以独立处理一些任务, 比如添加自定义key, value + def comm_call_menu_item_func(self, menu_item_dict, mqtt_dict : dict = None) : + call_func = utils.dict_or_object_get_attr(menu_item_dict, "call", None) + if call_func != None : + call_func(self, menu_item_dict, mqtt_dict) + + #通过菜单项中 获取浮点格式实际值, reg_value在通讯失败时返回None, 需要额外检查 + def comm_get_value_from_menu_item(self, menu_item_dict) : + menu_item_dict = self.comm_item_item_adjust(menu_item_dict) + scale = utils.dict_or_object_get_attr(menu_item_dict, "scale", 1.0) + offset = utils.dict_or_object_get_attr(menu_item_dict, "offset", 0.0) + comm_addr_str = utils.dict_or_object_get_attr(menu_item_dict, "addr", None) + reg_value = self.comm_item_get_value_float(comm_addr_str, scale, offset) + return reg_value + + #通过mqtt名称中 获取浮点格式实际值, reg_value在通讯失败时返回None, 需要额外检查 + def comm_get_value_from_mqtt_name(self, mqtt_name : str) : + if mqtt_name in self.mqtt_info_object.mqtt_dict.keys() : + mqtt_menu_item = self.mqtt_info_object.mqtt_dict[mqtt_name] + return self.comm_get_value_from_menu_item(mqtt_menu_item) + return None + + #触发一组mqtt菜单组的 key, value字典 + def comm_item_trigger_mqtt_pack(self, mqtt_pack) : + if mqtt_pack == None : + return None + mqtt_dict = {} + for pack_item_dict in mqtt_pack : + self.comm_call_menu_item_func(pack_item_dict, mqtt_dict) + reg_value = self.comm_get_value_from_menu_item(pack_item_dict) + format_str = utils.dict_or_object_get_attr(pack_item_dict, "format", "%1.0f") + mqtt_name = utils.dict_or_object_get_attr(pack_item_dict, "mqtt", None) + min_value = utils.dict_or_object_get_attr(pack_item_dict, "min", None) + max_value = utils.dict_or_object_get_attr(pack_item_dict, "max", None) + + # 检查是否有最小值和最大值的约束,并验证 reg_value 是否在范围内 + if reg_value is not None: + if min_value is not None and reg_value < min_value: + reg_value = min_value # 如果小于最小值,设为最小值 + if max_value is not None and reg_value > max_value: + reg_value = max_value # 如果大于最大值,设为最大值 + + if mqtt_name != None and reg_value != None: + mqtt_dict[mqtt_name] = format_str%(reg_value) + return mqtt_dict + + def trigger_comm(self, name): + for item in self.comm_table_item_list: + comm_table_item : class_comm_table_item = item + if comm_table_item.name == name : + comm_table_item.comm_trigger = True + return + + def read_register_count(self, reg_addr, reg_count) : + result = None + for item in self.comm_table_item_list : + comm_table_item : class_comm_table_item = item + + if comm_table_item.in_range(reg_addr, reg_count) : + if comm_table_item.reg_buf == None : + comm_table_item.comm_trigger = True + elif len(comm_table_item.reg_buf) == 0 : + comm_table_item.comm_trigger = True + else : + result = comm_table_item.read_register_count(reg_addr, reg_count) + if result != None : + break + return result + + #@device_comm_flush_request override + def device_comm_flush_request(self, reg_addr, reg_count, cycle = 3000) : + for item in self.comm_table_item_list : + comm_table_item : class_comm_table_item = item + if comm_table_item.in_range(reg_addr, reg_count) : + if comm_table_item.flush_request_delay == 0 : + comm_table_item.flush_request_delay = cycle + + #@read_register override + def read_register(self, comm_str) : + reg_addr, bit, reg_count = utils.comm_str_unpack(comm_str) + if reg_count == 0: + return None + return self.read_register_count(reg_addr, reg_count) + + #@read_register override + #bool write_register(int reg_addr, reg_count, uint16_t value[]) + def write_register(self, reg_addr, reg_count, new_value, timeout_ms = comm_device.DEFAULT_COMM_WRITE_TIMEOUT_MS) : + try: + _protocol : class_protocol_modbus = self.protocol + + _old_timeout = _protocol.get_timeout() + _protocol.set_timeout(timeout_ms / BASE_FAIL_DELAY) + + write_result = _protocol.modbus_write_regs(self.comm_addr, + reg_addr, + reg_count, + new_value, + ) + + if write_result == True: + for item in self.comm_table_item_list : + comm_item : class_comm_table_item = item + if comm_item.in_range(reg_addr, reg_count) : + comm_item.update_item_value(reg_addr, reg_count, new_value) + + except Exception : + write_result = False + finally : + _protocol.set_timeout(_old_timeout) + + return write_result + + #bool write_bit_register(int reg_addr, uint16_t value[]) + #bool write_bit_register(int reg_addr, uint16_t value) + def write_bit_register(self, reg_addr, value, timeout_ms = comm_device.DEFAULT_COMM_WRITE_TIMEOUT_MS) : + write_result = False + + _protocol : class_protocol_modbus = self.protocol + try : + old_timeout = _protocol.get_timeout() + _protocol.set_timeout(timeout_ms / BASE_FAIL_DELAY) + _protocol.modbus_write_regs(self.comm_addr, + reg_addr % 100000 + 100000, + len(value), + value) + + write_result = True + except Exception as e: + write_result = False + print_error_msg(f"modbus寄存器无法写入{str(e)}") + finally : + _protocol.set_timeout(old_timeout) + + return write_result + + #@override cycle function + def update_time_difference(self, table_item): + cur_time = time.time() # 获取当前时间 + time_dif = cur_time - table_item.base_time # 计算时间差 + table_item.base_time = cur_time # 更新基准时间 + return time_dif * BASE_FAIL_DELAY # 返回时间差(毫秒) + + def should_trigger_comm(self, table_item, time_dif_ms): + comm_trigger = False # 初始化通信触发标志为False + if self.func_comm_trigger: + comm_trigger = self.func_comm_trigger(self, table_item) # 调用通信触发函数 + + if table_item.remain_cycle > time_dif_ms: + table_item.remain_cycle -= time_dif_ms # 减少剩余周期 + else: + table_item.remain_cycle = table_item.cycle + table_item.comm_fail_delay + self.comm_fail_delay # 重新计算剩余周期 + if table_item.cycle > 0 or (table_item.cycle == 0 and not table_item.comm_success): + comm_trigger = True # 如果周期大于0或周期为0但上次通信不成功,则触发通信 + + if table_item.flush_request_delay > 0: + if table_item.flush_request_delay > time_dif_ms: + table_item.flush_request_delay -= time_dif_ms # 减少刷新请求延迟 + else: + table_item.flush_request_delay = 0 # 重置刷新请求延迟 + comm_trigger = True # 触发通信 + + if not comm_trigger: + comm_trigger = table_item.comm_trigger # 如果前面没有触发通信,则使用通信触发标志 + + return comm_trigger # 返回是否触发通信 + + def execute_communication(self, table_item): + read_result = [] # 初始化读取结果 + timeout_ms = getattr(table_item, "通信超时", DEFAULT_TIMEOUT) # 获取超时时间,默认500ms + _protocol = self.protocol # 获取协议对象 + + try: + old_timeout = _protocol.get_timeout() # 保存旧的超时时间 + _protocol.set_timeout(timeout_ms / BASE_FAIL_DELAY) # 设置新的超时时间 + + read_result = _protocol.modbus_read_regs(self.comm_addr, table_item.reg_addr, table_item.reg_count) # 执行Modbus读取 + if len(read_result) != table_item.reg_count: + read_result = [] # 如果读取结果数量不匹配,视为读取失败 + except Exception as e: + print_error_msg("周期读异常: ", e, f"modbus_read_regs(addr={self.comm_addr}, reg={table_item.reg_addr}, count={table_item.reg_count})") # 打印错误信息 + finally: + _protocol.set_timeout(old_timeout) # 恢复旧的超时时间 + + return read_result # 返回读取结果 + + def handle_comm_result(self, table_item, read_result): + if not read_result: # 如果读取结果为空,表示通信失败 + self.handle_comm_failure(table_item) + else: # 否则通信成功 + self.handle_comm_success(table_item, read_result) + + table_item.comm_trigger = False # 重置通信触发标志 + + def handle_comm_failure(self, table_item): + table_item.comm_fail_count += 1 # 增加通信失败计数 + table_item.comm_fail_delay = self.limit_delay(table_item.comm_fail_delay + BASE_FAIL_DELAY, MAX_FAIL_DELAY) # 增加并限制通信失败延迟 + self.comm_fail_delay = self.limit_delay(self.comm_fail_delay + BASE_GLOBAL_FAIL_DELAY, 60000) # 增加并限制全局通信失败延迟 + table_item.comm_success = False # 标记通信失败 + + def limit_delay(self, delay, max_delay): + """限制延迟不超过最大值""" + return min(delay, max_delay) + + + + def handle_comm_success(self, table_item, read_result): + table_item.comm_fail_delay = 0 # 重置通信失败延迟 + self.comm_fail_delay = 0 # 重置全局通信失败延迟 + table_item.reg_buf = read_result # 更新寄存器缓存 + table_item.comm_success = True # 标记通信成功 + table_item.comm_success_count += 1 # 增加成功计数 + + if table_item.mqtt_pack: + self.publish_mqtt_message(table_item) # 发布MQTT消息 + + def publish_mqtt_message(self, table_item): + json_topic = f"{table_item.name}/{self.unique_name}" # 构建MQTT主题 + msg_dict = self.comm_item_trigger_mqtt_pack(table_item.mqtt_pack) # 触发MQTT打包 + + if msg_dict: + json_message = json.dumps(msg_dict, ensure_ascii=False) # 序列化消息字典 + if self.mqtt_thread: + self.mqtt_thread.publish(json_topic, json_message) # 发布MQTT消息 + + + def device_comm_cycle(self): + comm_success_count = 0 # 初始化成功计数 + total_comm_count = 0 # 初始化总计数 + + if self.comm_table is None: + return comm_success_count, total_comm_count # 如果通信表为空,提前返回 + + for item in self.comm_table_item_list: + table_item = item # 获取表项 + if table_item.reg_count == 0: + continue # 如果寄存器计数为0,跳过此表项 + + time_dif_ms = self.update_time_difference(table_item) # 更新时间差 + comm_trigger = self.should_trigger_comm(table_item, time_dif_ms) # 检查是否应触发通信 + + if comm_trigger: + total_comm_count += 1 # 增加总通信计数 + read_result = self.execute_communication(table_item) # 执行通信 + + if read_result: + comm_success_count += 1 # 如果读取成功,增加成功计数 + + self.handle_comm_result(table_item, read_result) # 处理通信结果 + + return comm_success_count, total_comm_count # 返回成功计数和总计数 diff --git a/comm_protocol.py b/comm_protocol.py new file mode 100644 index 0000000..e14c0a3 --- /dev/null +++ b/comm_protocol.py @@ -0,0 +1,45 @@ +import string +import modbus_tk.defines as cst +from modbus_tk import modbus_rtu, modbus_tcp, modbus +from print_color import * +import enum as Enum + +#所有协议基类 +class class_protocol() : + def __init__(self, protocol_name) : + self.protocol_name = protocol_name + self.mode : string = None + self.timeout = 5.0 + return + + def set_timeout(self, timeout = 5.0) : + self.timeout = timeout + + def open(self, mode : string) : + self.mode = mode + print_error_msg("class_protocol派生类未实现open函数") + raise NotImplementedError() + + def close(self) : + print_error_msg("class_protocol派生类未实现close函数") + raise NotImplementedError() + + #返回 math.nan 表示读取失败, 否则读取成功 + def read_float(self, device_addr : int, comm_str : string, scale : float = 1.0, offset : float = 0.0) -> float: + raise NotImplementedError() + + #返回 False 表示读取失败 + #返回 True 表示读取成功 + def write_float(self, device_addr : int, comm_str : string, fvalue : float, scale : float = 1.0, offset : float = 0.0) -> bool: + raise NotImplementedError() + + #读取comm_str地址开始的连续地址count个数, 地址连续 + #返回 math.nan 表示读取失败, 否则读取成功 + def read_float_arr(self, device_addr : int, comm_str : string, count : int) : + raise NotImplementedError() + + #写入comm_str地址开始的连续地址 写入总个数由 len(fvalue_arr)决定 + #返回 False 表示读取失败, 否则读取成功 + def write_float_arr(self, device_addr : int, comm_str : string, fvalue_arr : list) -> bool: + raise NotImplementedError() + \ No newline at end of file diff --git a/comm_protocol_modbus.py b/comm_protocol_modbus.py new file mode 100644 index 0000000..558097e --- /dev/null +++ b/comm_protocol_modbus.py @@ -0,0 +1,656 @@ +#modbus协议处理 +import sys +import string +import json +import serial +import time +import menu_utils as utils +import string +import struct +import math +import modbus_tk.defines as cst +from modbus_tk import modbus_rtu, modbus_tcp, modbus +from print_color import * +from comm_protocol import class_protocol +from comm_thread import class_comm_master_thread +from threading import Thread, Lock +from modbus_tk.exceptions import( + ModbusError, ModbusFunctionNotSupportedError, DuplicatedKeyError, MissingKeyError, InvalidModbusBlockError, + InvalidArgumentError, OverlapModbusBlockError, OutOfModbusBlockError, ModbusInvalidResponseError, + ModbusInvalidRequestError +) + +def json_load_message(message) : + json_dict = {} + if isinstance(message, bytes) : + json_dict = json.loads(message.decode('utf-8')) + elif isinstance(message, str) : + json_dict = json.loads(message.encode('utf-8')) + return json_dict + +#从通讯字符串中取出寄存器地址,位地址,个数 +# 以下为允许的Modbus通讯地址信息 +#"8000#2" 从8000地址开始,连续取两个寄存器 +#"8000#2#s" 从8000地址开始,连续取两个寄存器, #s 代表有符号数, 缺省为无符号数 +#"8000" 或 "8000#1" 8000地址, 1个寄存器 +#"8000.2#3" 取8000地址的第2位开始取 3个位的数据 +#"8000#2#f#>" 8000地址开始,连续取两个寄存器, #> 大端模式, #f 浮点数, +#"8000#2#f" 或 "8000#2#f#<" 8000地址开始,连续取两个寄存器, #< 小端模式(缺省模式), #f 浮点数, 单精度 +#"8000#4#f" 或 "8000#4#f#<" 8000地址开始,连续取4个寄存器, #< 小端模式(缺省模式), #f 浮点数, 双精度 +#"100618" 位地址618, 超过100000就表示是位地址 +def get_modbus_comm_info(addr_string : string) : #tuple(int, int, int, bool, bool, bool) + reg_addr = 0 + reg_bit = -1 + reg_count = 0 + + is_sign = True if "#s" in addr_string else False + is_float = True if "#f" in addr_string else False + is_big_endian = True if "#>" in addr_string else False + + info_splits = addr_string.split("#") # 使用'#'作为分隔符 + if info_splits != None : + reg_bit_str : string = info_splits[0] + reg_bit_splits = reg_bit_str.split('.') + + if len(reg_bit_splits) == 2 : + reg_addr = int(reg_bit_splits[0]) + reg_bit = int(reg_bit_splits[1]) + else : + reg_addr = int(reg_bit_splits[0]) + reg_bit = -1 + + if len(info_splits) >= 2: + reg_count = int(info_splits[1]) + else : + reg_count = 1 + + return (reg_addr, reg_bit, reg_count, is_float, is_big_endian, is_sign) + +#modbus协议基类, 无法独立工作, 需要派生类实现对应函数 +class class_protocol_modbus(class_protocol) : + #protocol_port_name = "COM1", "dev/tty1" 之类的设备 + def __init__(self, protocol_name) : + class_protocol.__init__(self, protocol_name) + self.mode : string = None #协议open时的mode字符串 + self.master : modbus.Master = None #modbus主机 + self.last_errcode = 0 #协议最后一次故障代码 + self.timeout = 1.0 + self.mutex = Lock() + return + + def set_modbus_master(self, master : modbus.Master) : + if self.master != None : + self.master.close() + self.master = master + if self.master != None : + self.master.set_timeout(self.timeout) + + #reg_value最大为64位数据, reg_count最大为4 + def modbus_value_to_list(self, reg_value, reg_count, is_big_endian = False) -> list : + result = [] + if reg_count == 1 : + result.append(reg_value & 0xFFFF) + elif reg_count == 2: + if is_big_endian : + result.append((reg_value >> 16) & 0xFFFF) + result.append((reg_value >> 0) & 0xFFFF) + else : + result.append((reg_value >> 0) & 0xFFFF) + result.append((reg_value >> 16) & 0xFFFF) + elif reg_count == 3: + if is_big_endian : + result.append((reg_value >> 32) & 0xFFFF) + result.append((reg_value >> 16) & 0xFFFF) + result.append((reg_value >> 0) & 0xFFFF) + else : + result.append((reg_value >> 0) & 0xFFFF) + result.append((reg_value >> 16) & 0xFFFF) + result.append((reg_value >> 32) & 0xFFFF) + elif reg_count == 4: + if is_big_endian : + result.append((reg_value >> 48) & 0xFFFF) + result.append((reg_value >> 32) & 0xFFFF) + result.append((reg_value >> 16) & 0xFFFF) + result.append((reg_value >> 0) & 0xFFFF) + else : + result.append((reg_value >> 0) & 0xFFFF) + result.append((reg_value >> 16) & 0xFFFF) + result.append((reg_value >> 32) & 0xFFFF) + result.append((reg_value >> 48) & 0xFFFF) + + return result + + def modbus_list_to_value(self, read_regs : tuple, is_big_endian : bool = False) -> int: + read_reg_cunt = len(read_regs) + read_value = 0 + + if read_reg_cunt == 4 : + if is_big_endian : + read_value = (read_regs[0] << 48) | (read_regs[1] << 32) | (read_regs[2] << 16) | (read_regs[3] << 0) + else : + read_value = (read_regs[0] << 0) | (read_regs[1] << 16) | (read_regs[2] << 32) | (read_regs[3] << 48) + if read_reg_cunt == 3 : + if is_big_endian : + read_value = (read_regs[0] << 32) | (read_regs[1] << 16) | (read_regs[2] << 0) + else : + read_value = (read_regs[0] << 0) | (read_regs[1] << 16) | (read_regs[2] << 32) + elif read_reg_cunt == 2 : + if is_big_endian : + read_value = (read_regs[0] << 16) | (read_regs[1] << 0) + else : + read_value = (read_regs[0] << 0) | (read_regs[1] << 16) + elif read_reg_cunt == 1 : + read_value = read_regs[0] + + return read_value + + + + def modbus_read_regs(self, device_addr, reg_addr, reg_count) -> tuple: + result_tuple = [] + try: + self.mutex.acquire() + + if self.master is None: + raise Exception("Call set_modbus_master() first to init protocol master") + + read_reg_count = reg_count + is_bit_addr = reg_addr >= 100000 + func_code = cst.READ_COILS if is_bit_addr else cst.READ_HOLDING_REGISTERS + result_tuple = self.master.execute(slave=device_addr, + function_code=func_code, + starting_address=reg_addr % 100000, + quantity_of_x=read_reg_count) + except ModbusError as e: + self.last_errcode = e.get_exception_code() + err_msg = f"Modbus 读寄存器错误: {str(e)}, modbus 读寄存器(addr={device_addr}, reg={reg_addr}, count={reg_count})" + print_error_msg(err_msg) + raise + except Exception as e: + err_msg = f"其他错误: {str(e)}" + print_error_msg(err_msg) + raise + finally: + self.mutex.release() + + return result_tuple + + #reg_addr >= 100000 时为写 位地址, 否则写保持寄存器 + #values 传入整数或浮点数时, 表示写单个寄存器, 或 强制单个位, + #强制单个位时,modbus-tk会转化成0xFF00, 0x0000, 要是其他值时,需要修改modbus-tk底层库 + def modbus_write_regs(self, device_addr, reg_addr, reg_count, values) -> bool : + try : + success = False + + self.mutex.acquire() + + if self.master == None : + raise Exception("Call set_modbus_master() first to init protocol master") + + if isinstance(values, float) : + reg_value = (int)(round(values)) + values = [reg_value] + elif isinstance(values, int) : + reg_value = (int)(values) + values = [reg_value] + + if len(values) != reg_count or reg_count == 0: + raise Exception("error, value size %d not match to reg_count"%(len(values))) + + is_bit_addr = True if reg_addr >= 100000 else False + + if len(values) >= 2 : + func_code = cst.WRITE_MULTIPLE_COILS if is_bit_addr else cst.WRITE_MULTIPLE_REGISTERS + self.master.execute(slave = device_addr, + function_code = func_code, + starting_address = reg_addr % 100000, + quantity_of_x = len(values), + output_value= values) + else : + func_code = cst.WRITE_SINGLE_COIL if is_bit_addr else cst.WRITE_SINGLE_REGISTER + if func_code == cst.WRITE_SINGLE_COIL : + write_value= values[0] & 0xFFFF + else : + write_value = values[0] + + self.master.execute(slave = device_addr, + function_code = func_code, + starting_address = reg_addr % 100000, + quantity_of_x = len(values), + output_value= write_value) + success = True + except ModbusError as e: + self.last_errcode = e.get_exception_code() + print_error_msg(f"modbus写寄存器错误: {str(e)}, modbus写寄存器(addr={device_addr}, reg={reg_addr}, count={reg_count})") + success = False + except Exception as e: + success = False + print_error_msg(f"其他错误: {str(e)}") + finally: + self.mutex.release() + return success + + def modbus_read_float(self, device_addr, comm_str :string, scale = 1.0, offset = 0.0) -> float: + fvalue = math.nan + try : + reg_addr, reg_bit, reg_count, is_float, is_big_endian, is_sign = get_modbus_comm_info(comm_str) + if self.master == None : + raise Exception("Call set_modbus_master() first to init protocol master") + + read_reg_count = reg_count + if reg_bit >= 0 : + read_reg_count = 1 + (int)((reg_bit + reg_count - 1) / 16) + + result = self.modbus_read_regs(device_addr, reg_addr, read_reg_count) + + if read_reg_count == 4 : + if is_big_endian : + read_value = (result[0] << 48) | (result[1] << 32) | (result[2] << 16) | (result[3] << 0) + else : + read_value = (result[0] << 0) | (result[1] << 16) | (result[2] << 32) | (result[3] << 48) + if is_sign : + read_value = utils.value_u64_to_s64(read_value) + elif read_reg_count == 3 : + if is_big_endian : + read_value = (result[0] << 32) | (result[1] << 16) | (result[2] << 0) + else : + read_value = (result[0] << 0) | (result[1] << 16) | (result[2] << 32) + elif read_reg_count == 2 : + if is_big_endian : + read_value = (result[0] << 16) | (result[1] << 0) + else : + read_value = (result[0] << 0) | (result[1] << 16) + if is_sign : + read_value = utils.value_u32_to_s32(read_value) + elif read_reg_count == 1 : + read_value = result[0] + if is_sign : + read_value = utils.value_u16_to_s16(read_value) + else : + raise Exception("目前只支持读取(1,2,3,4)个寄存器个数") + + if reg_bit >= 0 : + read_value = (read_value >> reg_bit) & (0xFFFFFFFF >> (32 - reg_count)) + + if is_float : + fvalue = math.nan + if read_reg_count == 2: + bytes = struct.pack('BBBB', read_value & 0xFF, (read_value >> 8) & 0xFF, (read_value >> 16) & 0xFF, (read_value >> 24) & 0xFF) + fvalue = struct.unpack("> 8) & 0xFF, (low_part >> 16) & 0xFF, (low_part >> 24) & 0xFF) + high_part = (read_value >> 32) & 0xFFFFFFFF + high_bytes = struct.pack('BBBB', high_part & 0xFF, (high_part >> 8) & 0xFF, (high_part >> 16) & 0xFF, (high_part >> 24) & 0xFF) + bytes = low_bytes + high_bytes + fvalue = struct.unpack(" tuple(float) + fvalue = math.nan + total_reg_count = 0 + + try : + reg_addr, reg_bit, reg_count, is_float, is_big_endian, is_sign = get_modbus_comm_info(comm_str) + if self.master == None : + raise Exception("Call set_modbus_master() first to init protocol master") + + float_result_arr = [] + read_reg_count = reg_count + if reg_bit >= 0 : + raise Exception("multi read for reg.bit is not support") + + #读连续寄存器数据, 再添加到浮点数 数组列表 + total_reg_count = read_reg_count * count + result = self.modbus_read_regs(device_addr, reg_addr, total_reg_count) + + for i in range(count) : + if read_reg_count == 4 : + if is_big_endian : + read_value = (result[0] << 48) | (result[1] << 32) | (result[2] << 16) | (result[3] << 0) + else : + read_value = (result[0] << 0) | (result[1] << 16) | (result[2] << 32) | (result[3] << 48) + if is_sign : + read_value = utils.value_u64_to_s64(read_value) + elif read_reg_count == 3 : + if is_big_endian : + read_value = (result[0] << 32) | (result[1] << 16) | (result[2] << 0) + else : + read_value = (result[0] << 0) | (result[1] << 16) | (result[2] << 32) + elif read_reg_count == 2 : + if is_big_endian : + read_value = (result[0] << 16) | (result[1] << 0) + else : + read_value = (result[0] << 0) | (result[1] << 16) + if is_sign : + read_value = utils.value_u32_to_s32(read_value) + elif read_reg_count == 1 : + read_value = result[0] + if is_sign : + read_value = utils.value_u16_to_s16(read_value) + else : + raise Exception("目前只支持读取(1,2,3,4)个寄存器个数") + + if is_float : + fvalue = math.nan + if read_reg_count == 2: + bytes = struct.pack('BBBB', read_value & 0xFF, (read_value >> 8) & 0xFF, (read_value >> 16) & 0xFF, (read_value >> 24) & 0xFF) + fvalue = struct.unpack("> 8) & 0xFF, (low_part >> 16) & 0xFF, (low_part >> 24) & 0xFF) + high_part = (read_value >> 32) & 0xFFFFFFFF + high_bytes = struct.pack('BBBB', high_part & 0xFF, (high_part >> 8) & 0xFF, (high_part >> 16) & 0xFF, (high_part >> 24) & 0xFF) + bytes = low_bytes + high_bytes + fvalue = struct.unpack(" bool: + success = False + write_value = 0 + is_writing = False + is_reading = False + + try : + reg_addr, reg_bit, reg_count, is_float, is_big_endian, is_sign = get_modbus_comm_info(comm_str) + if self.master == None : + raise Exception("Call set_modbus_master() first to init protocol master") + + fvalue_origin = round((fvalue - offset) / scale, 0) + + write_reg_count = reg_count + if reg_bit >= 0 : + write_reg_count = 1 + (int)((reg_bit + reg_count - 1) / 16) + + if write_reg_count > 4 : #寄存器个数大于4个, 目前不支持 + raise Exception("write_reg_count is more than 4") + + write_reg_values = [] #定义空列表 + + if reg_addr >= 100000 and reg_addr < 200000 : #位数据 + if fvalue > 0 : + write_reg_values.append(1) + else : + write_reg_values.append(0) + elif reg_bit >= 0 : + if write_reg_count > 2 : + raise Exception("register count is > 2") + is_reading = True + read_regs = self.modbus_read_regs(device_addr, reg_addr, write_reg_count) + is_reading = False + + origin_read_value = self.modbus_list_to_value(read_regs, is_big_endian) + + write_value = (int)(fvalue_origin) + bit_mask = (0xFFFFFFFF >> (32 - reg_count)) + set_mask = write_value & bit_mask + + modify_value = origin_read_value + modify_value = modify_value & ~(bit_mask << reg_bit) + modify_value = modify_value | (set_mask << reg_bit) + + write_reg_values = self.modbus_value_to_list(modify_value, write_reg_count, is_big_endian) + elif is_float : + if is_big_endian : + format_pack = ">d" if write_reg_count == 4 else ">f" + else : + format_pack = " bool: + success = False + is_writing = False + is_read = False + + try : + reg_addr, reg_bit, reg_count, is_float, is_big_endian, is_sign = get_modbus_comm_info(comm_str) + if self.master == None : + raise Exception("Call set_modbus_master() first to init protocol master") + + write_reg_values = [] + write_reg_count = reg_count + + if reg_bit >= 0 : + raise Exception("multi write for reg.bit is not support") + + if write_reg_count > 4 : #单次写入寄存器个数大于4个, 目前不支持 + raise Exception("write_reg_count is more than 4") + + for fvalue_origin in fvalue_arr: + if reg_addr >= 100000 and reg_addr < 200000 : #位数据 + if fvalue_origin > 0 : + write_reg_values.append(1) + else : + write_reg_values.append(0) + elif is_float : + if is_big_endian : + format_pack = ">d" if write_reg_count == 4 else ">f" + else : + format_pack = " float: + return self.modbus_read_float(device_addr, comm_str, scale, offset) + + #@override 返回 False 表示读取失败, 否则读取成功 + def write_float(self, device_addr, comm_str, fvalue, scale = 1.0, offset = 0.0) -> bool: + return self.modbus_write_float(device_addr, comm_str, fvalue, scale, offset) + + #@override + #读取comm_str地址开始的连续地址count个数, 地址连续 + #返回 math.nan 表示读取失败, 否则读取成功 + def read_float_arr(self, device_addr : int, comm_str : string, count : int) : + return self.modbus_read_float_arr(device_addr, comm_str, count) + + #@override + #写入comm_str地址开始的连续地址 写入总个数由 len(fvalue_arr)决定 + #返回 False 表示读取失败, 否则读取成功 + def write_float_arr(self, device_addr : int, comm_str : string, fvalue_arr : list) -> bool: + return self.modbus_write_float_arr(device_addr, comm_str, fvalue_arr) + + +#串口Modbus_rtu主机类 +class class_protocol_modbus_rtu(class_protocol_modbus) : + def __init__(self, protocol_name = "modbus_rtu") : + class_protocol_modbus.__init__(self, protocol_name) + self.comm_parity = serial.PARITY_NONE + self.comm_bytesize = serial.EIGHTBITS + self.comm_stopbits = serial.STOPBITS_ONE + self.is_open = False + self.comm_uart = None + self.rtu_master : modbus_rtu.RtuMaster = None + return + + #@override + #parity('N', 'E', 'O', 'M', 'S') 选其一, 含义分别代表 ("无校验", "偶校验", "奇校验", "校验1", "校验0"), 缺省为无校验 + #stop(1, 1.5, 2)选其一, 含义分别代表 ("1个停止位", "1.5个停止位", "2个停止位"), 缺省为1个停止位 + #bit(5, 6, 7, 8)选其一, 含义分别代表 ("5位数据", "6位数据", "7位数据","8位数据"), 缺省为8位数据 + #baud(50, 75, 110, 134, 150, 200, 300, 600, 1200, 1800, 2400, 4800, 9600, 19200, 38400, 57600, 115200)选其一, 缺省为9600位数据 + #mode 需要是一个json字符串, exam: "{"name" : 'COM1', "baud" : 115200, "parity" :'E', "stop" : 1}" 代表COM1, 115200波特率, 偶校验, 1停止位 + def open(self, mode : string) : + try : + class_protocol_modbus.mode = mode + mode_dict = json_load_message(mode) + + print_inform_msg(f"打开: 协议名称:{self.protocol_name} ,工作端口:{self.mode}") + except Exception as e: + print_error_msg(f"错误modbus rtu信息:{str(e)}") + mode_dict = None + + if mode_dict : + self.comm_port_name = utils.dict_or_object_get_attr(mode_dict, "name", "COM1") + self.comm_parity = utils.dict_or_object_get_attr(mode_dict, "parity", serial.PARITY_NONE) + self.comm_baud = utils.dict_or_object_get_attr(mode_dict, "baud", 9600) + self.comm_stopbits = utils.dict_or_object_get_attr(mode_dict, "stop", serial.STOPBITS_ONE) + self.comm_bytesize = utils.dict_or_object_get_attr(mode_dict, "bit", serial.EIGHTBITS) + + try : + self.comm_uart = serial.Serial(port= self.comm_port_name, + baudrate= self.comm_baud, + bytesize= self.comm_bytesize, + parity=self.comm_parity, + stopbits=self.comm_stopbits) + + self.rtu_master = modbus_rtu.RtuMaster(self.comm_uart) + self.set_modbus_master(self.rtu_master) + self.set_timeout(self.timeout) + self.is_open = True + except Exception as e: + print_error_msg(f"错误modbus rtu信息:{str(e)}") + self.comm_uart = None + self.is_open = False + + #@override 关闭到modbus rtu 对应的串口 + def close(self) : + if self.is_open : + try : + print_inform_msg(f"关闭 : 协议名称:{self.protocol_name} ,工作端口:{self.mode}") + if self.rtu_master : + self.rtu_master.close() + if self.comm_uart : + self.comm_uart.close() + except Exception as e: + print_error_msg(f"错误关闭modbus rtu信息:{str(e)}") + pass + finally : + self.rtu_master = None + self.comm_uart = None + self.is_open = False + +#以太网Modbus_tcp主机类 +class class_protocol_modbus_tcp(class_protocol_modbus) : + def __init__(self, protocol_name = "modbus_tcp") : + class_protocol_modbus.__init__(self, protocol_name) + self.ip_str = "127.0.0.1" + self.port = 502 + self.tcp_master : modbus_tcp.TcpMaster = None + + return + + #ip: 服务器ip地址, 缺省"127.0.0.1" + #port: 服务器端口, 缺省502 + #timeout: 超时时间, 缺省 5.0S + #mode 需要是一个json字符串, exam: '{"ip" : '192.168.1.201', "port" : 502, "timeout": 5.0}' 代表192.168.1.201, 502端口, 通讯超时 5.0S + def open(self, mode: str): + try: + self.mode = mode + print_inform_msg(f"打开 Modbus TCP: 协议名称:{self.protocol_name} ,工作端口:{self.mode}") + mode_dict = json_load_message(mode) + except json.decoder.JSONDecodeError as e: + print_error_msg(f"解析模式失败: {str(e)}") + mode_dict = None + except Exception as e: + print_error_msg(f"错误打开 Modbus TCP 信息: {str(e)}") + mode_dict = None + + if mode_dict: + self.ip_str = utils.dict_or_object_get_attr(mode_dict, "ip", "127.0.0.1") + self.port = utils.dict_or_object_get_attr(mode_dict, "port", 502) + if "timeout" in mode_dict: + self.timeout = mode_dict["timeout"] + + try: + self.tcp_master = modbus_tcp.TcpMaster(host=self.ip_str, port=self.port, timeout_in_sec=self.timeout) + self.set_modbus_master(self.tcp_master) + self.tcp_master.open() + self.is_open = True + except Exception as e: + print_error_msg(f"打开 Modbus TCP 失败: {str(e)} IP地址:{self.ip_str} ,端口号:{self.port}") + self.is_open = False + + + #关闭到modbus tcp服务器的连接 + def close(self): + if self.is_open or self.tcp_master is not None: + try: + if self.tcp_master: + print_inform_msg(f"关闭 Modbus TCP: 协议名称:{self.protocol_name} ,工作端口:{self.mode}") + self.tcp_master.close() + except Exception as e: + print_error_msg(f"错误关闭 Modbus TCP 信息: {str(e)}") + pass + finally: + self.tcp_master = None + self.is_open = False + print_inform_msg("已断开与 Modbus TCP 服务器的连接") + + + +if __name__ == "__main__": + protocol_modbus_rtu = class_protocol_modbus_rtu("modbus_rtu") + protocol_modbus_rtu.open('{"name" : "COM2", "baud":115200, "parity" :"E", "stop" : 1}') + protocol_modbus_rtu.close() + + protocol_modbus_tcp : class_protocol_modbus_tcp = class_protocol_modbus_tcp("modbus_tcp") + protocol_modbus_tcp.open('{"ip" : "127.0.0.1", "port" : 502, "timeout" : 4.0}') + protocol_modbus_tcp.close() diff --git a/comm_remap.py b/comm_remap.py new file mode 100644 index 0000000..a7831d0 --- /dev/null +++ b/comm_remap.py @@ -0,0 +1,19 @@ + +COMM_REMAP_MEASURE_SWITCH_STATUS = 0 +COMM_REMAP_MEASURE_SWITCH_ALARM = 2 +COMM_REMAP_MEASURE_FLOAT_IA = 4 +COMM_REMAP_MEASURE_FLOAT_IB = 5 +COMM_REMAP_MEASURE_FLOAT_IC = 6 +COMM_REMAP_MEASURE_FLOAT_UAB = 10 +COMM_REMAP_MEASURE_FLOAT_UBC = 12 +COMM_REMAP_MEASURE_FLOAT_UCA = 14 +COMM_REMAP_MEASURE_FLOAT_RO = 16 +COMM_REMAP_MEASURE_FLOAT_IO = 18 +COMM_REMAP_MEASURE_FLOAT_UO = 20 + + +COMM_REMAP_PARA_CURRENT_RATE = 1000 +COMM_REMAP_PARA_CURRENT_SHORT_MUL = 1002 +COMM_REMAP_PARA_RATE_VOLTAGE = 1004 +COMM_REMAP_PARA_LOW_VOLTAGE_RATIO = 1006 +COMM_REMAP_PARA_LOW_VOLTAGE_DELAY = 1008 \ No newline at end of file diff --git a/comm_thread.py b/comm_thread.py new file mode 100644 index 0000000..8db1f4a --- /dev/null +++ b/comm_thread.py @@ -0,0 +1,78 @@ +import threading +from threading import Thread +import time +import menu_utils as utils +from comm_device import class_comm_device +from print_color import * + +class class_comm_master_thread(Thread): + thread_object_id = 0 + CHECK_INTERVAL = 0.05 # 检查间隔(秒) + MAX_FAIL_TIME = 5 # 最大失败时间(秒) + + def __init__(self, + device_comm_object_list=None, + thread_name="device"): + super().__init__() + self.stop_request = False + self.device_comm_object_list = device_comm_object_list if device_comm_object_list is not None else [] + + if thread_name is None: + thread_name = "device" + str(class_comm_master_thread.thread_object_id) + class_comm_master_thread.thread_object_id += 1 + + self.thread_name = thread_name + + def close(self): + self.stop_request = True + + def run(self): + print_inform_msg(f"{self.thread_name} modbus 主线程启动") + + total_fail_time = 0 + base_time = time.time() + + while not self.stop_request: + comm_success_count = 0 + total_comm_count = 0 + + for device in self.device_comm_object_list: + try: + if device.comm_active: + success_count, comm_count = device.device_comm_cycle() + comm_success_count += success_count + total_comm_count += comm_count + except Exception as err: + print_error_msg(err) + + cur_time = time.time() + + if comm_success_count > 0: + total_fail_time = 0 + elif total_comm_count > 0: + total_fail_time += cur_time - base_time + base_time = cur_time + + if total_fail_time >= self.MAX_FAIL_TIME: + self.reconnect_devices() + total_fail_time = 0 + + time.sleep(self.CHECK_INTERVAL) + + print_error_msg(f"{self.thread_name} modbus 主线程停止") + + def reconnect_devices(self): + """重新连接所有设备的协议""" + for device in self.device_comm_object_list: + try: + device.protocol.close() + except Exception as err: + print_error_msg(f"关闭协议时出错: {err}") + + for device in self.device_comm_object_list: + try: + device.protocol.open(device.protocol.mode) + except Exception as err: + print_error_msg(f"重新打开协议时出错: {err}") + + print_warning_msg("与保护器连接超时, 重新进行连接") diff --git a/data/Users.csv b/data/Users.csv index 7e08a29..f1b2d56 100644 --- a/data/Users.csv +++ b/data/Users.csv @@ -1,4 +1,3 @@ -Student_Name,Student_PRN,Student_Email,Student_Whatsapp_no,Student_pass,Branch,Semester -Ravikiran Yavalkar,12444444,abcd.efgh24@gmail.com,9607111115,India,AIML-F,III -jfen,55775577,test@example.com,NULL,China,AIML-F,III - +Student_Name,Student_PRN,Student_Email,Student_Whatsapp_no,Student_pass,Branch,Semester,ssid +Ravikiran Yavalkar,12444444,abcd.efgh24@gmail.com,9607111115,India,AIML-F,III,NULL +测试用户,11111111,test@example.com,NULL,0000,测试,III,zhizhan-2 diff --git a/device_conf.py b/device_conf.py new file mode 100644 index 0000000..05284b3 --- /dev/null +++ b/device_conf.py @@ -0,0 +1,127 @@ +import menu_page +import menu_utils as utils + + +def func_comm_test_leak_exam(object_device, topic_item, topic, message) : + return None + +def func_comm_table_trigger_item_exam(object_comm_table, comm_table_item) : + return False + + +exam_menu_alias_table = [ + {"alias_bool": {0:"关闭", 1:"打开"}}, +] + +exam_mqtt_topic_table = [ + {"name": "test_exam", "execute": func_comm_test_leak_exam}, +] + +exam_measure_pack = [ + { + "name": "OnStatus", + "addr": "8162.0", + "alias": "alias_onoff", + }, +] + +exam_comm_table = [ + {"name":"measure", "reg_addr":8139, "reg_count":35, "cycle": 200, "mqtt_pack": "exam_measure_pack"}, +] + +exam_menu_top = [ + { + "name": "退出", + "action": "exit", + }, +] + +exam_menu_caption = [ + {"name" : "exam_menu_top", "menu" : exam_menu_top, "next" : exam_menu_top, "prev" : exam_menu_top, "caption": "测试菜单", "page" : 10, "max_items" : 8}, +] + +#通讯设备配置基类, 不同的modbus设备需要重载各个函数 +class class_comm_device_config : + def __init__(self): + pass + + #获取别名表 + def get_alias_table(self) : + return exam_menu_alias_table + + #获取通讯数据表, 及触发函数 + def get_comm_table(self) : + return exam_comm_table, func_comm_table_trigger_item_exam + + #获取主菜单 + def get_menu_top(self) : + return exam_menu_top + + #获取mqtt表, 实现mqtt通讯 + def get_mqtt_table(self) : + return exam_mqtt_topic_table + + #获取昆仑通态显示页面 + def menu_get_display_page(self, active_menu): + return menu_page.KUNLUN_GRAOUP_PAGE_MENU + + def get_menu_caption_info(self) : + return exam_menu_caption + + def modify_menu_item(self, menu_item, value) : + return False + #获取菜单相关信息 + def search_menu_info(self, menu_name) : + search_dict = None + list_caption_info = self.get_menu_caption_info() + for menu_dict in list_caption_info : + if "name" in menu_dict.keys() : + if menu_dict["name"] == menu_name : + search_dict = menu_dict + break + return search_dict + + #获取菜单相关信息 + def search_object_menu_info(self, menu_object) : + search_dict = None + list_caption_info = self.get_menu_caption_info() + for menu_dict in list_caption_info : + search_menu_object = utils.dict_or_object_get_attr(menu_dict, "menu", None) + if search_menu_object == menu_object : + search_dict = menu_dict + break + return search_dict + + #获取自身菜单 + def get_menu(self, menu_name) : + if menu_name != None : + search_dict = self.search_menu_info(menu_name) + if search_dict != None : + return utils.dict_or_object_get_attr(search_dict, "menu", None) + return None + + #获取自身菜单 + def get_menu_name(self, menu_object) : + menu_name = None + search_dict = self.search_object_menu_info(menu_object) + if search_dict != None : + menu_name = utils.dict_or_object_get_attr(search_dict, "name", None) + return menu_name + + #获取下一个兄弟菜单 + def get_menu_next(self, menu_name) : + search_dict = self.search_menu_info(menu_name) + menu_name = utils.dict_or_object_get_attr(search_dict, "next", None) + return self.get_menu(menu_name) + + #获取上一个兄弟菜单 + def get_menu_next(self, menu_name) : + search_dict = self.search_menu_info(menu_name) + menu_name = utils.dict_or_object_get_attr(search_dict, "prev", None) + return self.get_menu(menu_name) + + #获取菜单显示页面 + def get_menu_page(self, menu_name) : + search_dict = self.search_menu_info(menu_name) + page = utils.dict_or_object_get_attr(search_dict, "page", menu_page.KUNLUN_GRAOUP_PAGE_MENU) + return page \ No newline at end of file diff --git a/kv/app.kv b/kv/app.kv index 34a8ed7..9a9b425 100644 --- a/kv/app.kv +++ b/kv/app.kv @@ -8,7 +8,7 @@ ScreenManager: # 屏幕管理器,用于管理应用中所有的屏幕及其 SignUpScreen: # 注册屏幕 name: 'signup' # 用户注册界面,创建新账号 OtpScreen: # 验证码屏幕 - name: 'otp' # 用于验证码验证的界面(如手机验证、邮箱验证) + name: 'otp' # 用于验证码验证的界面 ResetPassScreen: # 重置密码屏幕 name: 'reset_pass' # 重置密码界面,设置新密码 ForgetPassScreen: # 忘记密码屏幕 @@ -16,9 +16,9 @@ ScreenManager: # 屏幕管理器,用于管理应用中所有的屏幕及其 ProfileScreen: # 个人资料屏幕 name: "profile" # 显示用户个人信息的界面 HistoryScreen: # 历史记录屏幕 - name: 'history' # 显示用户操作历史(如下单记录、浏览记录等) + name: 'history' # 显示用户操作历史 RecommendScreen: # 推荐屏幕 - name: 'recommend' # 显示推荐内容(如推荐书籍等) + name: 'recommend' # 显示推荐内容 SearchBookScreen: # 搜索书籍屏幕 name: 'searchBook' # 用于搜索书籍的界面 ProfileEditScreen: # 编辑个人资料屏幕 @@ -35,17 +35,19 @@ ScreenManager: # 屏幕管理器,用于管理应用中所有的屏幕及其 : MDFloatLayout: - Image: - source: 'assets/logo.png' - size_hint: None, 0.9 - width: dp(150) - pos_hint: {'center_x': 0.1, 'center_y': 0.95} - + # Image: + # source: 'assets/logo.png' + # size_hint: None, 0.9 + # width: dp(150) + # pos_hint: {'center_x': 0.1, 'center_y': 0.95} Image: source: 'assets/img.png' - size_hint: None, 0.8 - width: dp(400) - pos_hint: {'center_x': 0.5, 'center_y': 0.8} + size_hint: 1, 1 + allow_stretch: True + keep_ratio: False + width: dp(360) + height: dp(720) + pos_hint: {'center_x': 0.5, 'center_y': 0.5} MDLabel: text: "欢 迎 使 用!" @@ -53,7 +55,8 @@ ScreenManager: # 屏幕管理器,用于管理应用中所有的屏幕及其 font_size: "23sp" pos_hint: {'center_y': 0.53} halign: "center" - color: rgba(0, 0, 59, 255) + # color: rgba(0, 0, 59, 255) + color: get_color_from_hex("#0c5edaff") MDLabel: text: "矿用上位机测试软件---Jfen" font_name: "MPoppins" @@ -63,12 +66,12 @@ ScreenManager: # 屏幕管理器,用于管理应用中所有的屏幕及其 halign: "center" color: rgba(127, 127, 127, 255) Button: - text: "登 入" + text: "进 入 使 用" size_hint: None, None font_name: "BPoppins" width: dp(350) height: dp(59) - pos_hint: {'center_x': 0.5, 'center_y': 0.28} + pos_hint: {'center_x': 0.5, 'center_y': 0.23} background_color: 0,0,0,0 on_touch_down: app.change_cursor(True) on_touch_up: app.change_cursor(False) @@ -82,26 +85,26 @@ ScreenManager: # 屏幕管理器,用于管理应用中所有的屏幕及其 size: self.size pos: self.pos radius: [5] - Button: - text: "注 册" - size_hint: None, None - font_name: "BPoppins" - width: dp(350) - height: dp(59) - pos_hint: {'center_x': 0.5, 'center_y': 0.19} - background_color: 0,0,0,0 - color: rgba(52, 0, 231, 255) - on_touch_down: app.change_cursor(True) - on_touch_up: app.change_cursor(False) - on_release: - root.manager.transition.direction = "left" - root.manager.current = "signup" - canvas.before: - Color: - rgb: rgba(52, 0, 231, 255) - Line: - width: 1.2 - rounded_rectangle: self.x, self.y, self.width, self.height, 5,5,5,5,100 + # Button: + # text: "注 册" + # size_hint: None, None + # font_name: "BPoppins" + # width: dp(350) + # height: dp(59) + # pos_hint: {'center_x': 0.5, 'center_y': 0.19} + # background_color: 0,0,0,0 + # color: rgba(52, 0, 231, 255) + # on_touch_down: app.change_cursor(True) + # on_touch_up: app.change_cursor(False) + # on_release: + # root.manager.transition.direction = "left" + # root.manager.current = "signup" + # canvas.before: + # Color: + # rgb: rgba(52, 0, 231, 255) + # Line: + # width: 1.2 + # rounded_rectangle: self.x, self.y, self.width, self.height, 5,5,5,5,100 @@ -117,7 +120,8 @@ ScreenManager: # 屏幕管理器,用于管理应用中所有的屏幕及其 height: "54dp" pos_hint: {"top": 1} MDTopAppBar: - title: "LMS" + title: "测试软件" + title_font_style: "H5" halign: 'center' elevation: 10 left_action_items: [["menu", lambda x: nav_drawer.set_state('toggle')]] @@ -130,15 +134,15 @@ ScreenManager: # 屏幕管理器,用于管理应用中所有的屏幕及其 pos_hint:{'center_x':0.15, 'center_y': 0.5} on_press: root.manager.current = 'home' MDActionBottomAppBarButton: - icon: "cart-outline" + icon: "tools" pos_hint:{'center_x':0.38, 'center_y': 0.5} on_press: root.manager.current = 'borrow_book' MDActionBottomAppBarButton: - icon: "thumb-up-outline" + icon: "alert" pos_hint:{'center_x':0.61, 'center_y': 0.5} on_press: root.manager.current = 'recommend' MDActionBottomAppBarButton: - icon: "history" + icon: "chart-bell-curve-cumulative" pos_hint:{'center_x':0.85, 'center_y': 0.5} on_press: root.manager.current = 'history' @@ -146,200 +150,192 @@ ScreenManager: # 屏幕管理器,用于管理应用中所有的屏幕及其 # Carousel at the top - Carousel: - id: carousel - size_hint_y: None - height: "205dp" - width: self.parent.width - pos_hint: {'top': .918} # Adjust position to fit below the AppBar - loop: False - anim_type: 'in_out_quad' - anim_move_duration: 0.2 - BoxLayout: - orientation: 'vertical' + # Carousel: + # id: carousel + # size_hint_y: None + # height: "205dp" + # width: self.parent.width + # pos_hint: {'top': .918} # Adjust position to fit below the AppBar + # loop: False + # anim_type: 'in_out_quad' + # anim_move_duration: 0.2 + # BoxLayout: + # orientation: 'vertical' - Image: - source: 'assets/lib_img1.jpg' - allow_stretch: True - keep_ratio: False + # Image: + # source: 'assets/lib_img1.jpg' + # allow_stretch: True + # keep_ratio: False - MDLabel: - text: "Bajrangdas Lohiya Central Library" - font_name: "BPoppins" - font_size: "24sp" - size_hint0: None, None - bold: True - width: self.parent.width - padding: "12dp",0,0,"5dp" - color: rgba(255, 255, 255, 255) + # MDLabel: + # text: "Bajrangdas Lohiya Central Library" + # font_name: "BPoppins" + # font_size: "24sp" + # size_hint0: None, None + # bold: True + # width: self.parent.width + # padding: "12dp",0,0,"5dp" + # color: rgba(255, 255, 255, 255) + + # BoxLayout: + # orientation: 'vertical' + + # Image: + # source: 'assets/Book_rack.jpg' + # allow_stretch: True + # keep_ratio: False + + # MDLabel: + # text: "Vast Book Collection (80,000+)" + # font_name: "BPoppins" + # font_size: "24sp" + # size_hint0: None, None + # width: self.parent.width + # padding: "12dp",0,0,"5dp" + # color: rgba(255, 255, 255, 255) + + # BoxLayout: + # orientation: 'vertical' + + # Image: + # source: 'assets/reading_space.jpg' + # allow_stretch: True + # keep_ratio: False + + # MDLabel: + # text: "Quiet Room" + # font_name: "BPoppins" + # font_size: "24sp" + # size_hint0: None, None + # width: self.parent.width + # padding: "12dp",0,0,"5dp" + # color: rgba(255, 255, 255, 255) - BoxLayout: - orientation: 'vertical' + # BoxLayout: + # orientation: 'vertical' - Image: - source: 'assets/Book_rack.jpg' - allow_stretch: True - keep_ratio: False + # Image: + # source: 'assets/journal_section.jpg' + # allow_stretch: True + # keep_ratio: False - MDLabel: - text: "Vast Book Collection (80,000+)" - font_name: "BPoppins" - font_size: "24sp" - size_hint0: None, None - width: self.parent.width - padding: "12dp",0,0,"5dp" - color: rgba(255, 255, 255, 255) - - BoxLayout: - orientation: 'vertical' - - Image: - source: 'assets/reading_space.jpg' - allow_stretch: True - keep_ratio: False - - MDLabel: - text: "Quiet Room" - font_name: "BPoppins" - font_size: "24sp" - size_hint0: None, None - width: self.parent.width - padding: "12dp",0,0,"5dp" - color: rgba(255, 255, 255, 255) - - - BoxLayout: - orientation: 'vertical' - - Image: - source: 'assets/journal_section.jpg' - allow_stretch: True - keep_ratio: False - - MDLabel: - text: "National & International Journals & Research Paper" - font_name: "BPoppins" - font_size: "24sp" - size_hint0: None, None - width: self.parent.width - padding: "12dp",0,0,"5dp" - color: rgba(255, 255, 255, 255) + # MDLabel: + # text: "National & International Journals & Research Paper" + # font_name: "BPoppins" + # font_size: "24sp" + # size_hint0: None, None + # width: self.parent.width + # padding: "12dp",0,0,"5dp" + # color: rgba(255, 255, 255, 255) - BoxLayout: - orientation: 'vertical' + # BoxLayout: + # orientation: 'vertical' - Image: - source: 'assets/lib_img2.jpg' - allow_stretch: True - keep_ratio: False + # Image: + # source: 'assets/lib_img2.jpg' + # allow_stretch: True + # keep_ratio: False - MDLabel: - text: "Reference Section" - font_name: "BPoppins" - font_size: "24sp" - size_hint0: None, None - width: self.parent.width - padding: "12dp",0,0,"5dp" - color: rgba(255, 255, 255, 255) + # MDLabel: + # text: "Reference Section" + # font_name: "BPoppins" + # font_size: "24sp" + # size_hint0: None, None + # width: self.parent.width + # padding: "12dp",0,0,"5dp" + # color: rgba(255, 255, 255, 255) - BoxLayout: - orientation: 'vertical' + # BoxLayout: + # orientation: 'vertical' - Image: - source: 'assets/lib_img3.jpg' - allow_stretch: True - keep_ratio: False + # Image: + # source: 'assets/lib_img3.jpg' + # allow_stretch: True + # keep_ratio: False - MDLabel: - text: "Career and Test Preparation Section:" - font_name: "BPoppins" - font_size: "24sp" - size_hint0: None, None - width: self.parent.width - padding: "12dp",0,0,"5dp" - color: rgba(255, 255, 255, 255) + # MDLabel: + # text: "Career and Test Preparation Section:" + # font_name: "BPoppins" + # font_size: "24sp" + # size_hint0: None, None + # width: self.parent.width + # padding: "12dp",0,0,"5dp" + # color: rgba(255, 255, 255, 255) - BoxLayout: - orientation: 'vertical' + # BoxLayout: + # orientation: 'vertical' - Image: - source: 'assets/pc1.jpg' - allow_stretch: True - keep_ratio: False + # Image: + # source: 'assets/pc1.jpg' + # allow_stretch: True + # keep_ratio: False - MDLabel: - text: "Online E-Resources" - font_name: "BPoppins" - font_size: "24sp" - size_hint0: None, None - width: self.parent.width - padding: "12dp",0,0,"5dp" - color: rgba(255, 255, 255, 255) + # MDLabel: + # text: "Online E-Resources" + # font_name: "BPoppins" + # font_size: "24sp" + # size_hint0: None, None + # width: self.parent.width + # padding: "12dp",0,0,"5dp" + # color: rgba(255, 255, 255, 255) - BoxLayout: - orientation: 'vertical' + # BoxLayout: + # orientation: 'vertical' - Image: - source: 'assets/keyboard.jpg' - allow_stretch: True - keep_ratio: False + # Image: + # source: 'assets/keyboard.jpg' + # allow_stretch: True + # keep_ratio: False - MDLabel: - text: "iThenticate Software for Plagiarism checking" - font_name: "BPoppins" - font_size: "24sp" - size_hint0: None, None - width: self.parent.width - padding: "12dp",0,0,"5dp" - color: rgba(255, 255, 255, 255) + # MDLabel: + # text: "iThenticate Software for Plagiarism checking" + # font_name: "BPoppins" + # font_size: "24sp" + # size_hint0: None, None + # width: self.parent.width + # padding: "12dp",0,0,"5dp" + # color: rgba(255, 255, 255, 255) # Navigation Buttons Positioned Over the Carousel - MDIconButton: - icon: "arrow-left-thin-circle-outline" - pos_hint: {'x': 0.025, 'center_y': 0.7705} # Adjusted position - size_hint: None, None - size: "48dp", "48dp" - user_font_size: "30sp" - theme_text_color: "Custom" - text_color: (1,1,1,1) - on_release: carousel.load_previous() - disabled: carousel.index == 0 + # MDIconButton: + # icon: "arrow-left-thin-circle-outline" + # pos_hint: {'x': 0.025, 'center_y': 0.7705} # Adjusted position + # size_hint: None, None + # size: "48dp", "48dp" + # user_font_size: "30sp" + # theme_text_color: "Custom" + # text_color: (1,1,1,1) + # on_release: carousel.load_previous() + # disabled: carousel.index == 0 - MDIconButton: - icon: "arrow-right-thin-circle-outline" - pos_hint: {'right': 0.9725, 'center_y': 0.7705} - size_hint: None, None - size: "48dp", "48dp" - user_font_size: "30sp" - theme_text_color: "Custom" - text_color: (1,1,1,1) - on_release: carousel.load_next() - disabled: carousel.index == len(carousel.slides) - 1 + # MDIconButton: + # icon: "arrow-right-thin-circle-outline" + # pos_hint: {'right': 0.9725, 'center_y': 0.7705} + # size_hint: None, None + # size: "48dp", "48dp" + # user_font_size: "30sp" + # theme_text_color: "Custom" + # text_color: (1,1,1,1) + # on_release: carousel.load_next() + # disabled: carousel.index == len(carousel.slides) - 1 ScrollView: size_hint: 1, None - height: self.parent.height - dp(324) - pos_hint: {'top':0.615} + height: self.parent.height - dp(150) + pos_hint: {'top':0.895} MDList: id : image_label_grid padding: "20dp", "40dp", "12dp", "17dp" spacing: "50dp" - MDLabel: - text: "Bajrangdas Lohiya Central Library" - font_name: "BPoppins" - font_size: "24sp" - halign: 'center' - bold: True - color: rgba(0, 0, 59, 255) MDCard: size_hint: None, None - width: self.parent.width-50 # Set the size of the box + width: self.parent.width - dp(50) # Set the size of the box height: "225dp" pos_hint: {"center_x": 0.5, "center_y": 0.3} # Center the box elevation: 5 # Add shadow (elevation) @@ -347,7 +343,7 @@ ScreenManager: # 屏幕管理器,用于管理应用中所有的屏幕及其 spacing: "10dp" orientation: 'vertical' MDLabel: - text: "The main objective of Bajrangdas Lohiya Central Library, established in the year 1984, is to support the educational & research programmes of the Institute by providing physical and intellectual access to information consistent with the present and anticipated educational and research functions of the Institute." + text: "测试程序" font_name: "MPoppins" font_size: "13sp" size_hint_x: 1 @@ -361,7 +357,7 @@ ScreenManager: # 屏幕管理器,用于管理应用中所有的屏幕及其 pos_hint: {"center_x": 0.5} on_release: root.manager.current= "home" MDLabel: - text: "Where is the Book Kept?" + text: "测试" font_name: "BPoppins" font_size: "24sp" pos_hint: {'center_x': 0.45} @@ -370,7 +366,7 @@ ScreenManager: # 屏幕管理器,用于管理应用中所有的屏幕及其 color: rgba(0, 0, 0, 255) MDCard: size_hint: None, None - width: self.parent.width-50 # Set the size of the box + width: self.parent.width-dp(50) # Set the size of the box height: "225dp" pos_hint: {"center_x": 0.5, "center_y": 0.3} # Center the box elevation: 5 # Add shadow (elevation) @@ -379,8 +375,8 @@ ScreenManager: # 屏幕管理器,用于管理应用中所有的屏幕及其 orientation: 'vertical' MDTextField: id: book_pos_name - text: "" - hint_text: "Enter Book Name.." + text: "测试" + hint_text: "text" font_name: "MPoppins" font_size: "15sp" helper_text_mode: "on_focus" @@ -399,7 +395,7 @@ ScreenManager: # 屏幕管理器,用于管理应用中所有的屏幕及其 rounded_rectangle: self.x-5, self.y+20, self.width+10, self.height-25, 10,10,10,10,100 MDRaisedButton: - text: "Find" + text: "测试2" size_hint: None, None size: "150dp", "25dp" pos_hint: {"center_x": 0.5} @@ -407,7 +403,7 @@ ScreenManager: # 屏幕管理器,用于管理应用中所有的屏幕及其 MDLabel: id : book_pos - text: "Example.,Book Position is 1L42 , 1 represent row no., L represent left side of row, 4 represent rack/shelves no. ,2 represent shelves step no." + text: "测试3" halign: 'center' bold: True font_name: "BPoppins" @@ -425,8 +421,10 @@ ScreenManager: # 屏幕管理器,用于管理应用中所有的屏幕及其 MDNavigationDrawerMenu: MDNavigationDrawerHeader: id : user - title: "Welcome! TeenAger" - text: "Make your Bright future with us..!" + title: "欢迎使用" + text: "功能总览" + title_font_style: "H5" # 使用自定义标题字体 + text_font_style: "Caption" # 使用自定义正文字体 text_color: "#6c7272" text_font_size: '13sp' title_color: "#00f0ff" @@ -459,12 +457,23 @@ ScreenManager: # 屏幕管理器,用于管理应用中所有的屏幕及其 MDNavigationDrawerDivider: - MDNavigationDrawerLabel: - text: "Menu" + # MDNavigationDrawerLabel: + # text: "菜单" + MDLabel: + text: "菜单" + font_style: "H6" + theme_text_color: "Custom" + text_color: "#000000ff" + size_hint_y: None + height: "40dp" + padding: "12dp", "12dp" DrawerClickableItem: icon: "home-outline" - text: "Home" + text: "主页" + font_style: "H6" + theme_text_color: "Custom" + text_color: "#000000ff" on_touch_down: app.change_cursor(True) on_touch_up: app.change_cursor(False) on_release: @@ -473,10 +482,9 @@ ScreenManager: # 屏幕管理器,用于管理应用中所有的屏幕及其 root.manager.current = "home" DrawerClickableItem: - icon: "bell-outline" - right_text: '+99' - text_right_color: "#4a4930" - text: "Notification" + icon: "current-dc" + text: "电流设置" + font_style: "H6" on_touch_down: app.change_cursor(True) on_touch_up: app.change_cursor(False) on_release: @@ -485,19 +493,23 @@ ScreenManager: # 屏幕管理器,用于管理应用中所有的屏幕及其 root.manager.current = "home" DrawerClickableItem: - icon: 'book-search-outline' - text: "Search Book" + icon: 'meter-electric-outline' + text: "电压设置" + font_style: "H6" + theme_text_color: "Custom" + text_color: "#000000ff" on_touch_down: app.change_cursor(True) on_touch_up: app.change_cursor(False) on_release: root.manager.transition.direction = "left" nav_drawer.set_state('close') - app.data_load() - root.manager.current = "searchBook" + # app.data_load() + root.manager.current = "home" DrawerClickableItem: - icon: 'thumb-up-outline' - text: "Recommendations" + icon: 'resistor' + text: "漏电设置" + font_style: "H6" on_touch_down: app.change_cursor(True) on_touch_up: app.change_cursor(False) on_release: @@ -506,8 +518,9 @@ ScreenManager: # 屏幕管理器,用于管理应用中所有的屏幕及其 root.manager.current = "recommend" DrawerClickableItem: - icon: 'cart-outline' - text: "Borrow Book" + icon: 'shield-lock-outline' + text: "保护设置" + font_style: "H6" on_touch_down: app.change_cursor(True) on_touch_up: app.change_cursor(False) on_release: @@ -517,8 +530,9 @@ ScreenManager: # 屏幕管理器,用于管理应用中所有的屏幕及其 root.manager.current = "borrow_book" DrawerClickableItem: - icon: 'keyboard-return' - text: "Return Book" + icon: 'cog-outline' + text: "系统设置" + font_style: "H6" on_touch_down: app.change_cursor(True) on_touch_up: app.change_cursor(False) on_release: @@ -528,8 +542,11 @@ ScreenManager: # 屏幕管理器,用于管理应用中所有的屏幕及其 root.manager.current = "return_book" DrawerClickableItem: - icon: 'autorenew' - text: "Re-New Book" + icon: 'alert-circle-outline' + right_text: '+99' + text_right_color: "#4a4930" + text: "故障记录" + font_style: "H6" on_touch_down: app.change_cursor(True) on_touch_up: app.change_cursor(False) on_release: @@ -538,9 +555,20 @@ ScreenManager: # 屏幕管理器,用于管理应用中所有的屏幕及其 app.return_book("Renew") root.manager.current = "renew_book" + MDNavigationDrawerDivider: + MDLabel: + text: "功能" + font_style: "H6" + theme_text_color: "Custom" + text_color: "#000000ff" + size_hint_y: None + height: "40dp" + padding: "12dp", "12dp" + DrawerClickableItem: - icon: 'history' - text: "History" + icon: 'chart-bell-curve-cumulative' + text: "实时曲线" + font_style: "H6" on_touch_down: app.change_cursor(True) on_touch_up: app.change_cursor(False) on_release: @@ -550,8 +578,9 @@ ScreenManager: # 屏幕管理器,用于管理应用中所有的屏幕及其 root.manager.current = "history" DrawerClickableItem: - icon: 'account-edit-outline' - text: "Edit Profile" + icon: 'tools' + text: "控制命令" + font_style: "H6" on_touch_down: app.change_cursor(True) on_touch_up: app.change_cursor(False) on_release: @@ -566,7 +595,8 @@ ScreenManager: # 屏幕管理器,用于管理应用中所有的屏幕及其 DrawerClickableItem: icon: 'information-outline' - text: "About Us" + text: "预留" + font_style: "H6" on_touch_down: app.change_cursor(True) on_touch_up: app.change_cursor(False) on_release: @@ -576,7 +606,8 @@ ScreenManager: # 屏幕管理器,用于管理应用中所有的屏幕及其 DrawerClickableItem: icon: 'message-alert-outline' - text: "Feedback" + text: "预留" + font_style: "H6" on_touch_down: app.change_cursor(True) on_touch_up: app.change_cursor(False) on_release: @@ -588,7 +619,8 @@ ScreenManager: # 屏幕管理器,用于管理应用中所有的屏幕及其 DrawerClickableItem: icon: 'logout' - text: "Logout" + text: "退出" + font_style: "H6" on_touch_down: app.change_cursor(True) on_touch_up: app.change_cursor(False) on_release: @@ -621,7 +653,7 @@ ScreenManager: # 屏幕管理器,用于管理应用中所有的屏幕及其 app.on_press_back_arrow() MDLabel: - text: "W e l c o m e !" + text: "欢迎使用等待认证中..." font_name: "BPoppins" font_size: "26sp" size_hint_x: None @@ -630,62 +662,62 @@ ScreenManager: # 屏幕管理器,用于管理应用中所有的屏幕及其 color: rgba(0, 0, 59, 255) MDLabel: - text: "登录并继续 ... " + text: app.wifi_status_text font_name: "MPoppins" size_hint_x: None width: dp(350) font_size: "18sp" - pos_hint: {'center_x': 0.5,'center_y': 0.79} + pos_hint: {'center_x': 0.5,'center_y': 0.70} color: rgba(135, 133, 193, 255) - MDTextField: - id: username - hint_text: "Enter PRN" - font_name: "MPoppins" - helper_text: "PRN 仅为八位数字" - helper_text_mode: "on_focus" - icon_right: "dialpad" - icon_color: app.theme_cls.primary_color - pos_hint: {"center_x": 0.5, "center_y": 0.63} - size_hint_x: None - width: dp(335) - on_touch_down: app.change_cursor(True) - on_touch_up: app.change_cursor(False) + # MDTextField: + # id: username + # hint_text: "Enter PRN" + # font_name: "MPoppins" + # helper_text: "PRN must be 8 digits" + # helper_text_mode: "on_focus" + # icon_right: "dialpad" + # icon_color: app.theme_cls.primary_color + # pos_hint: {"center_x": 0.5, "center_y": 0.63} + # size_hint_x: None + # width: dp(335) + # on_touch_down: app.change_cursor(True) + # on_touch_up: app.change_cursor(False) - MDRelativeLayout: - size: self.width, self.height + # MDRelativeLayout: + # size: self.width, self.height - MDTextField: - id : password - font_name: "MPoppins" - font_size: "14sp" - hint_text: "Enter Password" - helper_text: "must contain at least one 'a' , 'A' , '@' & '0'" - helper_text_mode: "on_focus" - pos_hint: {"center_x": 0.5, "center_y": 0.53} - size_hint_x: None - width: dp(335) - password:True - on_focus: app.focus_text_field(icon, self.focus, "password") - on_touch_down: app.change_cursor(True) - on_touch_up: app.change_cursor(False) + # MDTextField: + # id : password + # font_name: "MPoppins" + # font_size: "14sp" + # hint_text: "Enter Password" + # helper_text: "must contain at least one 'a' , 'A' , '@' & '0'" + # helper_text_mode: "on_focus" + # pos_hint: {"center_x": 0.5, "center_y": 0.53} + # size_hint_x: None + # width: dp(335) + # password:True + # on_focus: app.focus_text_field(icon, self.focus, "password") + # on_touch_down: app.change_cursor(True) + # on_touch_up: app.change_cursor(False) - MDIconButton: - id: icon - icon: 'eye-off' - theme_icon_color: "Custom" - icon_color: rgba(162, 169, 188, 255) - size_hint: None, None - size: dp(24), dp(24) - pos: password.x + password.width - self.width+dp(4), password.center_y - self.height / 2+dp(5) - on_touch_down: app.change_cursor(True) - on_touch_up: app.change_cursor(False) - on_release: - app.toggle_password_visibility(password, self) - password.focus=True + # MDIconButton: + # id: icon + # icon: 'eye-off' + # theme_icon_color: "Custom" + # icon_color: rgba(162, 169, 188, 255) + # size_hint: None, None + # size: dp(24), dp(24) + # pos: password.x + password.width - self.width+dp(4), password.center_y - self.height / 2+dp(5) + # on_touch_down: app.change_cursor(True) + # on_touch_up: app.change_cursor(False) + # on_release: + # app.toggle_password_visibility(password, self) + # password.focus=True Button: - text: "登录" + text: "wifi认证登录" font_name: "BPoppins" size_hint: None, None width: dp(300) @@ -706,66 +738,66 @@ ScreenManager: # 屏幕管理器,用于管理应用中所有的屏幕及其 pos: self.pos radius: [5] - MDTextButton: - text: "忘记密码" - pos_hint: {'center_x': 0.5, 'center_y': 0.31} - color: rgba(68,78,132,255) - font_name: "BPoppins" - font_size: '13sp' - on_touch_down: app.change_cursor(True) - on_touch_up: app.change_cursor(False) - on_release: - root.manager.transition.direction= "down" - root.manager.current="forgot_pass" - MDLabel: - text: "or" - color: rgba(52, 0, 231, 255) - pos_hint: {'center_y': 0.25} - font_name: "BPoppins" - font_size: '13sp' - halign: 'center' + # MDTextButton: + # text: "忘记密码" + # pos_hint: {'center_x': 0.5, 'center_y': 0.31} + # color: rgba(68,78,132,255) + # font_name: "BPoppins" + # font_size: '13sp' + # on_touch_down: app.change_cursor(True) + # on_touch_up: app.change_cursor(False) + # on_release: + # root.manager.transition.direction= "down" + # root.manager.current="forgot_pass" + # MDLabel: + # text: "or" + # color: rgba(52, 0, 231, 255) + # pos_hint: {'center_y': 0.25} + # font_name: "BPoppins" + # font_size: '13sp' + # halign: 'center' - MDFloatLayout: - md_bg_color: rgba(178, 178, 178, 255) - size_hint: 0.35, 0.002 - pos_hint: {'center_x': 0.27, 'center_y': .248} - MDFloatLayout: - md_bg_color: rgba(178, 178, 178, 255) - size_hint: 0.35, 0.002 - pos_hint: {'center_x': 0.73, 'center_y': .248} + # MDFloatLayout: + # md_bg_color: rgba(178, 178, 178, 255) + # size_hint: 0.35, 0.002 + # pos_hint: {'center_x': 0.27, 'center_y': .248} + # MDFloatLayout: + # md_bg_color: rgba(178, 178, 178, 255) + # size_hint: 0.35, 0.002 + # pos_hint: {'center_x': 0.73, 'center_y': .248} - MDTextButton: - id: prn_mode - text: "验证邮箱" - font_name: 'BPoppins' - font_size: '13sp' - color: rgba(135, 133, 193, 255) - pos_hint: {'center_x': 0.5, 'center_y': 0.19} - on_touch_down: app.change_cursor(True) - on_touch_up: app.change_cursor(False) - on_release: - app.login_mode(username, self) + # MDTextButton: + # id: prn_mode + # text: "验证邮箱" + # font_name: 'BPoppins' + # font_size: '13sp' + # color: rgba(135, 133, 193, 255) + # pos_hint: {'center_x': 0.5, 'center_y': 0.19} + # on_touch_down: app.change_cursor(True) + # on_touch_up: app.change_cursor(False) + # on_release: + # app.login_mode(username, self) - MDGridLayout: - cols: 1 - size_hint: 0.2, 0.07 - pos_hint: {'center_x': 0.5, 'center_y': 0.15} - Image: - source: 'assets/google.png' + # MDGridLayout: + # cols: 1 + # size_hint: 0.2, 0.07 + # pos_hint: {'center_x': 0.5, 'center_y': 0.15} + # Image: + # source: 'assets/google.png' - MDTextButton: - text: "Don't have an account? Sign up" - font_name: 'BPoppins' - halign: 'center' - font_size: '11sp' - color: rgba(52, 0, 231, 255) - pos_hint: {'center_x': 0.5, 'center_y': 0.05} - on_touch_down: app.change_cursor(True) - on_touch_up: app.change_cursor(False) - on_release: - root.manager.transition.direction = "left" - root.manager.current="signup" + # MDTextButton: + # text: "Don't have an account? Sign up" + # font_name: 'BPoppins' + # halign: 'center' + # font_size: '11sp' + # color: rgba(52, 0, 231, 255) + # pos_hint: {'center_x': 0.5, 'center_y': 0.05} + # on_touch_down: app.change_cursor(True) + # on_touch_up: app.change_cursor(False) + # on_release: + # root.manager.transition.direction = "left" + # root.manager.current="signup" : name: "signup" @@ -906,7 +938,7 @@ ScreenManager: # 屏幕管理器,用于管理应用中所有的屏幕及其 hint_text: "邮箱地址*" font_name: "MPoppins" font_size: "15sp" - helper_text: "只是测试邮箱*" + helper_text: "email test*" helper_text_mode: "on_focus" icon_right: "email-arrow-left-outline" min_text_length: 11 @@ -1521,30 +1553,27 @@ ScreenManager: # 屏幕管理器,用于管理应用中所有的屏幕及其 Widget: - MDBottomAppBar: - height: "62dp" - elevation: 10 - MDActionBottomAppBarButton: - icon: "home" - pos_hint:{'center_x':0.15, 'center_y': 0.5} - on_press: root.manager.current = 'home' - MDActionBottomAppBarButton: - icon: "cart-outline" - pos_hint:{'center_x':0.38, 'center_y': 0.5} - on_press: root.manager.current = 'borrow_book' - MDActionBottomAppBarButton: - icon: "thumb-up-outline" - pos_hint:{'center_x':0.61, 'center_y': 0.5} - on_press: root.manager.current = 'recommend' - MDActionBottomAppBarButton: - icon: "history" - pos_hint:{'center_x':0.85, 'center_y': 0.5} - on_press: - root.manager.current = 'history' - app.book_history() - - - + # MDBottomAppBar: + # height: "62dp" + # elevation: 10 + # MDActionBottomAppBarButton: + # icon: "home" + # pos_hint:{'center_x':0.15, 'center_y': 0.5} + # on_press: root.manager.current = 'home' + # MDActionBottomAppBarButton: + # icon: "tools" + # pos_hint:{'center_x':0.38, 'center_y': 0.5} + # on_press: root.manager.current = 'borrow_book' + # MDActionBottomAppBarButton: + # icon: "thumb-up-outline" + # pos_hint:{'center_x':0.61, 'center_y': 0.5} + # on_press: root.manager.current = 'recommend' + # MDActionBottomAppBarButton: + # icon: "history" + # pos_hint:{'center_x':0.85, 'center_y': 0.5} + # on_press: + # root.manager.current = 'history' + # app.book_history() MDIconButton: icon: "arrow-left" @@ -1649,6 +1678,7 @@ ScreenManager: # 屏幕管理器,用于管理应用中所有的屏幕及其 md_bd_color: 1,1,1,1 anchor_x: 'center' anchor_y: 'center' + MDBottomAppBar: height: "62dp" elevation: 10 @@ -1657,21 +1687,20 @@ ScreenManager: # 屏幕管理器,用于管理应用中所有的屏幕及其 pos_hint:{'center_x':0.15, 'center_y': 0.5} on_press: root.manager.current = 'home' MDActionBottomAppBarButton: - icon: "cart-outline" + icon: "tools" pos_hint:{'center_x':0.38, 'center_y': 0.5} on_press: root.manager.current = 'borrow_book' MDActionBottomAppBarButton: - icon: "thumb-up-outline" + icon: "alert" pos_hint:{'center_x':0.61, 'center_y': 0.5} on_press: root.manager.current = 'recommend' MDActionBottomAppBarButton: - icon: "history" + icon: "chart-bell-curve-cumulative" pos_hint:{'center_x':0.85, 'center_y': 0.5} on_press: root.manager.current = 'history' app.book_history() - MDIconButton: icon: "arrow-left" pos_hint: {'center_y': 0.95} @@ -1729,7 +1758,7 @@ ScreenManager: # 屏幕管理器,用于管理应用中所有的屏幕及其 pos_hint:{'center_x':0.15, 'center_y': 0.5} on_press: root.manager.current = 'home' MDActionBottomAppBarButton: - icon: "cart-outline" + icon: "tools" pos_hint:{'center_x':0.38, 'center_y': 0.5} on_press: root.manager.current = 'borrow_book' MDActionBottomAppBarButton: @@ -1810,7 +1839,7 @@ ScreenManager: # 屏幕管理器,用于管理应用中所有的屏幕及其 pos_hint:{'center_x':0.15, 'center_y': 0.5} on_press: root.manager.current = 'home' MDActionBottomAppBarButton: - icon: "cart-outline" + icon: "tools" pos_hint:{'center_x':0.38, 'center_y': 0.5} on_press: root.manager.current = 'borrow_book' MDActionBottomAppBarButton: @@ -1892,7 +1921,7 @@ ScreenManager: # 屏幕管理器,用于管理应用中所有的屏幕及其 pos_hint:{'center_x':0.15, 'center_y': 0.5} on_press: root.manager.current = 'home' MDActionBottomAppBarButton: - icon: "cart-outline" + icon: "tools" pos_hint:{'center_x':0.38, 'center_y': 0.5} on_press: root.manager.current = 'borrow_book' MDActionBottomAppBarButton: @@ -2162,7 +2191,7 @@ ScreenManager: # 屏幕管理器,用于管理应用中所有的屏幕及其 pos_hint:{'center_x':0.15, 'center_y': 0.5} on_press: root.manager.current = 'home' MDActionBottomAppBarButton: - icon: "cart-outline" + icon: "tools" pos_hint:{'center_x':0.38, 'center_y': 0.5} on_press: root.manager.current = 'borrow_book' MDActionBottomAppBarButton: @@ -2211,7 +2240,7 @@ ScreenManager: # 屏幕管理器,用于管理应用中所有的屏幕及其 pos_hint:{'center_x':0.15, 'center_y': 0.5} on_press: root.manager.current = 'home' MDActionBottomAppBarButton: - icon: "cart-outline" + icon: "tools" pos_hint:{'center_x':0.38, 'center_y': 0.5} on_press: root.manager.current = 'borrow_book' MDActionBottomAppBarButton: @@ -2332,7 +2361,7 @@ ScreenManager: # 屏幕管理器,用于管理应用中所有的屏幕及其 root.manager.transition.direction= 'right' root.manager.current= "home" MDActionBottomAppBarButton: - icon: "cart-outline" + icon: "tools" pos_hint:{'center_x':0.38, 'center_y': 0.5} on_press: root.manager.current = 'borrow_book' MDActionBottomAppBarButton: @@ -2406,7 +2435,7 @@ ScreenManager: # 屏幕管理器,用于管理应用中所有的屏幕及其 pos_hint:{'center_x':0.15, 'center_y': 0.5} on_press: root.manager.current = 'home' MDActionBottomAppBarButton: - icon: "cart-outline" + icon: "tools" pos_hint:{'center_x':0.38, 'center_y': 0.5} on_press: root.manager.current = 'borrow_book' MDActionBottomAppBarButton: diff --git a/main.py b/main.py index a97c1df..f933c3a 100644 --- a/main.py +++ b/main.py @@ -1,5 +1,5 @@ -from kivy.properties import ObjectProperty +from kivy.properties import ObjectProperty, StringProperty from datetime import datetime, timedelta from kivy.core.text import LabelBase from kivy.metrics import dp @@ -24,16 +24,21 @@ from kivymd.uix.dialog import MDDialog from kivymd.uix.label import MDLabel from kivy.uix.label import Label +from kivy.utils import platform +from kivy.clock import Clock +from kivymd.theming import ThemeManager + LabelBase.register(name="MPoppins", fn_regular="fonts/Chinese/msyh.ttf") LabelBase.register(name="BPoppins", fn_regular="fonts/Chinese/msyh.ttf") LabelBase.register(name="RRubik", fn_regular="fonts/Chinese/msyh.ttf") LabelBase.register(name="RCro", fn_regular="fonts/Chinese/msyh.ttf") LabelBase.register(name="RPac", fn_regular="fonts/Chinese/msyh.ttf") + + def get_books(): pass - class MainScreen(Screen): pass @@ -41,7 +46,7 @@ class LoginScreen(Screen): login = ObjectProperty(None) class SignUpScreen(Screen): - signup: ObjectProperty = ObjectProperty(None) + signup = ObjectProperty(None) class ForgetPassScreen(Screen): forget_Password = ObjectProperty(None) @@ -82,7 +87,10 @@ class RenewBookScreen(Screen): class HistoryScreen(Screen): history = ObjectProperty(None) +# Window.size = (dp(360), dp(680)) + class app(MDApp): + wifi_status_text = StringProperty("") def __init__(self): super().__init__() self.signup_sem = None @@ -140,6 +148,19 @@ class app(MDApp): self.password = None self.username = None + self.theme_cls.font_styles["H1"] = ["BPoppins", 96, False, -1.5] + self.theme_cls.font_styles["H2"] = ["BPoppins", 60, False, -0.5] + self.theme_cls.font_styles["H3"] = ["BPoppins", 48, False, 0] + self.theme_cls.font_styles["H4"] = ["BPoppins", 34, False, 0.25] + self.theme_cls.font_styles["H5"] = ["BPoppins", 25, False, 0.15] + self.theme_cls.font_styles["H6"] = ["BPoppins", 16, False, 0.15] + self.theme_cls.font_styles["Subtitle1"] = ["BPoppins", 16, False, 0.15] + self.theme_cls.font_styles["Subtitle2"] = ["BPoppins", 14, False, 0.1] + self.theme_cls.font_styles["Body1"] = ["BPoppins", 16, False, 0.5] + self.theme_cls.font_styles["Body2"] = ["BPoppins", 14, False, 0.25] + self.theme_cls.font_styles["Button"] = ["BPoppins", 14, True, 1.25] + self.theme_cls.font_styles["Caption"] = ["BPoppins", 13, False, 0.15] + self.theme_cls.font_styles["Overline"] = ["BPoppins", 10, True, 1.5] def build(self): Builder.load_file('kv/app.kv') @@ -161,115 +182,197 @@ class app(MDApp): screen_manager.add_widget(RenewBookScreen(name="renew_book")) screen_manager.add_widget(HistoryScreen(name="history")) + Clock.schedule_once(self.update_wifi_status, 2) return screen_manager #############################################ALL INPUT TEXT############################################################ def on_start(self): + + # 从根窗口中获取名为"login"的屏幕(登录界面) login_screen = self.root.get_screen("login") - self.username = login_screen.ids.username - self.password = login_screen.ids.password + # 通过界面ID获取登录界面中的用户名输入框组件 + # self.username = login_screen.ids.username + # 通过界面ID获取登录界面中的密码输入框组件 + # self.password = login_screen.ids.password + # 从根窗口中获取名为"home"的屏幕(主界面) home_screen = self.root.get_screen("home") + # 通过界面ID获取主界面中的用户信息组件 self.user = home_screen.ids.user + # 通过界面ID获取主界面中的书架位置名称组件 self.book_pos_name = home_screen.ids.book_pos_name + # 通过界面ID获取主界面中的书架位置组件 self.book_pos = home_screen.ids.book_pos + # 从根窗口中获取名为"forgot_pass"的屏幕(忘记密码界面) forgot_pass_screen = self.root.get_screen("forgot_pass") + # 通过界面ID获取忘记密码界面中的手机号输入框组件 self.mobile_no = forgot_pass_screen.ids.forgotPass_no + # 从根窗口中获取名为"reset_pass"的屏幕(重置密码界面) reset_pass_screen = self.root.get_screen("reset_pass") + # 通过界面ID获取重置密码界面中的新密码输入框组件 self.new_pass = reset_pass_screen.ids.new_pass + # 通过界面ID获取重置密码界面中的确认新密码输入框组件 self.new_pass1 = reset_pass_screen.ids.new_pass1 + # 从根窗口中获取名为"otp"的屏幕(验证码界面) otp_screen = self.root.get_screen("otp") + # 通过界面ID获取验证码界面中的"验证"按钮组件 self.otp_button = otp_screen.ids.otp_button + # 通过界面ID获取验证码界面中的"返回"按钮组件 self.back_button = otp_screen.ids.back_button + # 通过界面ID获取验证码界面中的第一个数字输入框组件 self.d1 = otp_screen.ids.d1 + # 通过界面ID获取验证码界面中的第二个数字输入框组件 self.d2 = otp_screen.ids.d2 + # 通过界面ID获取验证码界面中的第三个数字输入框组件 self.d3 = otp_screen.ids.d3 + # 通过界面ID获取验证码界面中的第四个数字输入框组件 self.d4 = otp_screen.ids.d4 + # 从根窗口中获取名为"signup"的屏幕(注册界面) signup_screen = self.root.get_screen("signup") + # 通过界面ID获取注册界面中的用户名输入框组件 self.user_name = signup_screen.ids.signup_name + # 通过界面ID获取注册界面中的PRN(可能是学号/身份标识)输入框组件 self.user_prn = signup_screen.ids.signup_prn + # 通过界面ID获取注册界面中的邮箱输入框组件 self.user_email = signup_screen.ids.signup_email + # 通过界面ID获取注册界面中的手机号输入框组件 self.user_no = signup_screen.ids.signup_no + # 通过界面ID获取注册界面中的密码输入框组件 self.user_pass = signup_screen.ids.signup_pass + # 通过界面ID获取注册界面中的所属部门/专业选择组件 self.signup_branch = signup_screen.ids.signup_branch + # 通过界面ID获取注册界面中的年级/学期选择组件 self.signup_sem = signup_screen.ids.signup_sem + # 从根窗口中获取名为"profile"的屏幕(个人资料界面) profile_screen = self.root.get_screen("profile") + # 通过界面ID获取个人资料界面中的姓名展示组件 self.profile_name = profile_screen.ids.profile_name + # 通过界面ID获取个人资料界面中的邮箱展示组件 self.profile_email = profile_screen.ids.profile_email + # 通过界面ID获取个人资料界面中的PRN(身份标识)展示组件 self.profile_prn = profile_screen.ids.profile_prn + # 通过界面ID获取个人资料界面中的手机号展示组件 self.profile_number = profile_screen.ids.profile_number + # 通过界面ID获取个人资料界面中的所属部门/专业展示组件 self.profile_branch = profile_screen.ids.profile_branch + # 通过界面ID获取个人资料界面中的年级/学期展示组件 self.profile_semester = profile_screen.ids.profile_semester + # 从根窗口中获取名为"profile_edit"的屏幕(个人资料编辑界面) self.profile_edit_screen = self.root.get_screen("profile_edit") + # 通过界面ID获取资料编辑界面中的姓名编辑输入框组件 self.edit_name = self.profile_edit_screen.ids.edit_name + # 通过界面ID获取资料编辑界面中的邮箱编辑输入框组件 self.edit_email = self.profile_edit_screen.ids.edit_email + # 通过界面ID获取资料编辑界面中的PRN(身份标识)编辑输入框组件 self.edit_prn = self.profile_edit_screen.ids.edit_prn + # 通过界面ID获取资料编辑界面中的手机号编辑输入框组件 self.edit_number = self.profile_edit_screen.ids.edit_no + # 通过界面ID获取资料编辑界面中的所属部门/专业编辑选择组件 self.edit_branch = self.profile_edit_screen.ids.edit_branch + # 通过界面ID获取资料编辑界面中的年级/学期编辑选择组件 self.edit_semester = self.profile_edit_screen.ids.edit_sem + # 从根窗口中获取名为"borrow_book"的屏幕(借书界面) borrow_screen = self.root.get_screen("borrow_book") + # 通过界面ID获取借书界面中的ISBN(图书编号)输入框组件 self.isbn = borrow_screen.ids.isbn + # 从根窗口中获取名为"return_book"的屏幕(还书界面) return_book_screen = self.root.get_screen("return_book") + # 通过界面ID获取还书界面中的图片与标签网格布局组件 self.image_label_grid = return_book_screen.ids.image_label_grid + # 从根窗口中获取名为"renew_book"的屏幕(图书续借界面) renew_book_screen = self.root.get_screen("renew_book") + # 通过界面ID获取续借界面中的网格布局组件 self.renew_grid = renew_book_screen.ids.renew_grid + # 从根窗口中获取名为"history"的屏幕(借阅历史界面) history_screen = self.root.get_screen("history") + # 通过界面ID获取借阅历史界面中的网格布局组件 self.history_grid = history_screen.ids.history_grid + # 从根窗口中获取名为"searchBook"的屏幕(图书搜索界面) search_book_screen = self.root.get_screen("searchBook") + # 通过界面ID获取图书搜索界面中的搜索输入框组件 self.search_field = search_book_screen.ids.search_field + # 通过界面ID获取图书搜索界面中的搜索结果网格布局组件 self.search_grid = search_book_screen.ids.search_grid + # 从根窗口中获取名为"recommend"的屏幕(图书推荐界面) self.recommend_screen = self.root.get_screen("recommend") + # 通过界面ID获取推荐界面中的图书输入框组件 self.book_input = self.recommend_screen.ids.book_input + # 通过界面ID获取推荐界面中的推荐结果网格布局组件 self.rec_grid = self.recommend_screen.ids.rec_grid + # 通过界面ID获取推荐界面中的推荐信息标签组件 self.rec_label = self.recommend_screen.ids.rec_label #############################################@Frequently used functions################################################## @staticmethod def change_cursor(is_enter): + """改变鼠标指针样式""" + # 如果is_enter为True(鼠标进入目标区域),将鼠标指针改为手型 if is_enter: Window.set_system_cursor('hand') + # 否则(鼠标离开目标区域),将鼠标指针改回默认箭头样式 else: Window.set_system_cursor('arrow') + @staticmethod def toggle_password_visibility(password_field, icon_button): + """切换密码输入框的可见性状态""" + # 检查密码输入框当前是否处于密码隐藏状态 if password_field.password: + # 如果是隐藏状态,切换为可见状态 password_field.password = False + # 更新图标为"eye"(眼睛图标,表示当前可见) icon_button.icon = "eye" else: + # 如果是可见状态,切换为隐藏状态(显示为圆点/星号) password_field.password = True + # 更新图标为"eye-off"(闭眼图标,表示当前隐藏) icon_button.icon = "eye-off" def clear_text(self): + """清空界面中所有输入框和文本组件的内容""" + # 清空图书搜索框内容 self.search_field.text = "" + # 清空图书推荐输入框内容 self.book_input.text = "" - self.username.text = "" - self.password.text = "" + # 清空登录界面的用户名输入框内容 + # self.username.text = "" + # 清空登录界面的密码输入框内容 + # self.password.text = "" + # 清空忘记密码界面的手机号输入框内容 self.mobile_no.text = "" + # 清空验证码界面的第一个输入框内容 self.d1.text = "" + # 清空验证码界面的第二个输入框内容 self.d2.text = "" + # 清空验证码界面的第三个输入框内容 self.d3.text = "" + # 清空验证码界面的第四个输入框内容 self.d4.text = "" + # 清空书架位置名称文本内容 self.book_pos_name.text = "" - self.book_pos.text = "Example.,Book Position is 1L42 , 1 represent row no., L represent left side of row, 4 represent rack/shelves no. ,2 represent shelves step no." + # 重置书架位置说明文本为示例内容 + self.book_pos.text = "测试内容示例" + # 再次清空手机号输入框(可能是为了确保完全清空) self.mobile_no.text = "" + # 清空借书界面的ISBN输入框内容 self.isbn.text = '' def on_press_back_arrow(self): - self.username.text = "" - self.password.text = "" + # self.username.text = "" + # self.password.text = "" self.mobile_no.text = "" self.d1.text = "" self.d2.text = "" @@ -340,54 +443,154 @@ class app(MDApp): ###################################LoginPageWork-Start################################################# - # login Verification function "verify" - def verify(self, obj): - status = False - if self.username.text == "" or self.password.text == "": - check = "请输入账户和密码" - return self.dialog1(check) - else: - name = "" - with open('data/Users.csv', "r", encoding="utf-8") as file: - csv_reader = csv.reader(file, delimiter=",") - for line in csv_reader: - try: - if not obj: - a = (line[1] == self.username.text or line[2] == self.username.text) - else: - a = (line[1] == self.edit_prn.text or line[2] == self.edit_email.text) - if a and line[4] == self.password.text: - status = True - name = line[0] - prn = line[1] - email = line[2] - number = line[3] - branch = line[5] - semester = line[6] - break - except IndexError: - pass - except Exception as e: - print(e) + # # login Verification function "verify" + # def verify(self, obj): + # status = False + # if self.username.text == "" or self.password.text == "": + # check = "请输入账户和密码" + # return self.dialog1(check) + # else: + # name = "" + # with open('data/Users.csv', "r", encoding="utf-8") as file: + # csv_reader = csv.reader(file, delimiter=",") + # for line in csv_reader: + # try: + # if not obj: + # a = (line[1] == self.username.text or line[2] == self.username.text) + # else: + # a = (line[1] == self.edit_prn.text or line[2] == self.edit_email.text) + # if a and line[4] == self.password.text: + # status = True + # name = line[0] + # prn = line[1] + # email = line[2] + # number = line[3] + # branch = line[5] + # semester = line[6] + # break + # except IndexError: + # pass + # except Exception as e: + # print(e) - if status: - if not obj: - check = "Login Successful" + "\nHello! " + name - else: - check = "Profile Updated Successfully!" - self.secure_profile() - self.root.current = "home" - self.user.text = f"""[b]Hey! {name}[/b]""" - self.profile_name.text = self.edit_name.text = name - self.profile_email.text = self.edit_email.text = email - self.profile_prn.text = self.edit_prn.text = prn - self.profile_number.text = self.edit_number.text = number - self.profile_semester.text = self.edit_semester.text = semester - self.profile_branch.text = self.edit_branch.text = branch + # if status: + # if not obj: + # check = "Login Successful" + "\nHello! " + name + # else: + # check = "Profile Updated Successfully!" + # self.secure_profile() + # self.root.current = "home" + # self.user.text = f"""[b]Hey! {name}[/b]""" + # self.profile_name.text = self.edit_name.text = name + # self.profile_email.text = self.edit_email.text = email + # self.profile_prn.text = self.edit_prn.text = prn + # self.profile_number.text = self.edit_number.text = number + # self.profile_semester.text = self.edit_semester.text = semester + # self.profile_branch.text = self.edit_branch.text = branch + # else: + # check = "Login Failed!. " + "Enter correct username and password." + # self.dialog1(check) + def update_wifi_status(self, dt): + self.check_wifi() + Clock.schedule_once(self.update_wifi_status, 1) # 5秒刷新一次 + + def check_wifi(self): + if platform != 'android': + self.wifi_status_text = '非Android设备' + return + + try: + from jnius import autoclass, cast + + PythonActivity = autoclass('org.kivy.android.PythonActivity') + Context = autoclass('android.content.Context') + activity = PythonActivity.mActivity + + WifiManager = autoclass('android.net.wifi.WifiManager') + Formatter = autoclass('android.text.format.Formatter') + + wifi_service = activity.getSystemService(Context.WIFI_SERVICE) + wifi_manager = cast('android.net.wifi.WifiManager', wifi_service) + + if not wifi_manager.isWifiEnabled(): + self.wifi_status_text = 'WiFi 未启用' + return # 这里直接返回,不再继续取SSID等信息 + + wifi_info = wifi_manager.getConnectionInfo() + if wifi_info is None: + self.wifi_status_text = 'WiFi 信息不可用' + return + + ssid = wifi_info.getSSID() + if ssid: + ssid_display = ssid.strip('"') else: - check = "Login Failed!. " + "Enter correct username and password." - self.dialog1(check) + ssid_display = "SSID Unknown" + + ip_int = wifi_info.getIpAddress() + ip_str = Formatter.formatIpAddress(ip_int) if ip_int != 0 else "0.0.0.0" + link_speed = wifi_info.getLinkSpeed() # Mbps + bssid = wifi_info.getBSSID() + rssi = wifi_info.getRssi() + + self.wifi_status_text = ( + f'SSID: {ssid_display}\n' + f'IP: {ip_str}\n' + f'速度: {link_speed} Mbps\n' + f'BSSID: {bssid}\n' + f'信号: {rssi} dBm' + ) + + except Exception as e: + self.wifi_status_text = f'获取WiFi信息失败\n{e}' + + def verify(self, obj=None): + try: + if platform == "android": + from jnius import autoclass + PythonActivity = autoclass('org.kivy.android.PythonActivity') + Context = autoclass('android.content.Context') + activity = PythonActivity.mActivity + wifi_service = activity.getSystemService(Context.WIFI_SERVICE) + wifi_info = wifi_service.getConnectionInfo() + wifi_id = wifi_info.getSSID().strip('"').lower() + else: + wifi_id = "zhizhan-2" # 非 Android 用模拟 WiFi + except Exception as e: + self.dialog1(f"获取WiFi信息失败:{e}") + return + + try: + with open("data/Users.csv", "r", encoding="utf-8") as file: + csv_reader = csv.reader(file) + for line in csv_reader: + # 确保至少有7列数据 + if len(line) < 7: + continue + + csv_ssid = line[7].strip().lower() if len(line) > 7 else "" + + if wifi_id == csv_ssid or wifi_id in map(str.lower, line): + name, prn, email, number, password, branch, semester = line[:7] + + self.root.current = "home" + self.user.text = f"[b]Hey! {name}[/b]" + self.profile_name.text = self.edit_name.text = name + self.profile_email.text = self.edit_email.text = email + self.profile_prn.text = self.edit_prn.text = prn + self.profile_number.text = self.edit_number.text = number + self.profile_semester.text = self.edit_semester.text = semester + self.profile_branch.text = self.edit_branch.text = branch + + self.dialog1(f"欢迎你,{name} wifi认证成功") + return + except Exception as e: + self.dialog1(f"读取用户信息失败:{e}") + return + + self.dialog1("当前WiFi认证失败,请检查网络或退出") + click_count = 0 def login_mode(self, username_text, button_text): diff --git a/menu_mine.py b/menu_mine.py new file mode 100644 index 0000000..054c7d2 --- /dev/null +++ b/menu_mine.py @@ -0,0 +1,2536 @@ +from device_conf import class_comm_device_config +import comm_remap as remap +import menu_utils as uitls +from enum import Enum +from datetime import datetime +import time +import menu_utils as utils +import json +import re +from enum import Enum +from comm_modbus_device import class_modbus_comm_device + +now_time = datetime.now() +current_time = now_time.strftime("(%y/%m/%d)") +VERSION_DATE = current_time + +class StrInfo(Enum): + STR_MAX_VALUE = 0, #"最大值" + STR_MIN_VALUE = 1, #"最小值" + STR_SET_VALUE = 2, #"设定值" + STR_VALUE_EXCEED = 3, #"设定值越界" + STR_ZERO_PHASE = 4, #"有功相位" + STR_INPUT_PASSWORD = 5, #"请输入系统密码 + STR_SYSTEM_PASSWORD = 6, #"系统密码" + STR_PASSWORD_ERR = 7, #"密码错误" + STR_SHORT_PHASE_MODIFY =8, #"短路倍数\n恢复缺省" + STR_SWITCH_TYPE_MODIFY =9, #"漏电延时\n恢复缺省" + STR_SOFT_VERSION =10, #"软件版本" + STR_NO_RECORD =11, #"无记录" + STR_ENTER_TO_CLEAR =12, #"确定清除" + STR_ESC_TO_CANCEL =13, #"ESC取消" + STR_CLEAR_FINISH =14, #"清除成功" + STR_ESC_TO_EXIT =15, #"ESC退出 + STR_INPUT_NEW_PASSWORD =16, #"请设置新密码" + STR_NOT_POSSIBLE_TO_MODIFY =17, #"不可修改" + STR_CAPACITOR_MEASURE =18, #"电容检测 + STR_CAPACITOR_EQUAL =19, #"电容" + STR_LEEKAGE_MEASURE =20, #"检测中.." + STR_TEST_ACTIVE =21, #"测试状态 + STR_POWER_ON=22, #"合闸" + STR_POWER_OFF=23, #"分闸" + STR_FAIL=24, #"失效" + STR_NORMAL=25, #"正常" + STR_MEASURE_CURRENT=26, #"实测电流" + STR_RATE_CURRENT=27, #"额定电流" + STR_ANGLE=28, #"角度" + STR_AVERAGE_CURRENT=29, #"平均电流" + STR_MAX_CURRENT=30, #"最大电流 + STR_LOW_VOLTAGE_RATE=31, #"欠压值 + STR_MEASURE_VOLTAGE=32, #"实测电压" + STR_ZERO_CURRENT=33, #"零序电流" + STR_ZERO_VOLTAGE=34, #"零序电压" + STR_RECORD=35, #"记录" + STR_LEEAKGE_LOCK_VALUE=36, #"闭锁设定值" + STR_LEEAKGE_ACTIVE_VALUE=37, #"动作设定值" + STR_MEASURE_RESISTOR=38, #"电阻测量值" + STR_FRONT_CIRCUIT=39, #"前机" + STR_NEXT_CIRCUIT=40, #"后机" + STR_RATE_VOLTAGE=41, #"额定电压" + STR_PARA_MODIFY_ERROR=42, #"参数修改失败" + STR_LEEKAGE_RES_FINISH=43, #"闭锁电阻检测完" + STR_LEEKAGE_RES_MEASURE=44, #"闭锁电阻检测中" + STR_SFJ_MONITOR=45, #"双风机磁力监控" + STR_CL_MONITOR=46, #"磁力监控系统" + STR_CL_DB_MONITOR=47, #"双回路磁力监控" + STR_YB_MONITOR=48, #"移变低压侧" + STR_KUI_DIAN_MONITOR=49, #"馈电开关监控" + STR_TYPE=50, #"类型:" + STR_TOP_SWITCH=51, #"总开关" + STR_BOTTOM_SWITCH=52, #"分开关" + STR_COS_V=53, #"COS(ΦaΦbΦc)" + STR_POWER_VI=54, #"有功功率" + STR_POWER_VI_T=55, #"电量" + STR_FRONT_POWER_ON=56, #"前机合" + STR_NEXT_POWER_ON=57, #"后机合" + STR_FRONT_POWER_OFF=58, #"前机分" + STR_NEXT_POWER_OFF=59, #"后机分" + STR_FRONT_RESISTOR=60, #"前机电阻:" + STR_NEXT_RESISTOR=61, #"后机电阻:" + STR_WORK_MODE=62, #"模式:" + STR_LEEKAGE_RES=63, #"漏电电阻" + STR_REVERSE_VOLTAGE=64, #"反电动势\n注意安全" + STR_LIGHT=65, #"照明" + STR_SIGNAL=66, #"信号" + STR_SWITCH_STATUS=67, #"开关状态" + STR_POWER_ON_COUNTS=68, #"合闸次数" + STR_SETTING=69, #"设定值" + STR_POW_U=70, #"无功功率" + STR_TEST_FAIL=71, #"测试不正确" + STR_LEAKAGE_TEST_FAIL=72, #"漏电测试不正确" + STR_DETECT_FAIL_WHEN_POWER_ON=73, #"合闸时不能检测" + STR_LINK=74, #"联机系统" + STR_SINGLE=75, #"单机系统" + STR_STATUS=76, #"状态" + STR_MAIN_FIRST=77, #"主机优先" + STR_MAIN_BAK_SAME=78, #"主辅对等" + STR_MAIN_BAK_SWITCH=79, #"主辅轮动" + STR_SWITCH_DISABLE=80, #"局扇模式" + STR_MAIN=81, #"主机" + STR_BACKUP=82, #"备机" + STR_SINGLE_CIRCUIT_FRONT=83, #"单前机" + STR_SINGLE_CIRCUIT_NEXT=84, #"单后机" + STR_DOUBLE_CIRCUIT=85, #"双回路" + STR_WATER_PUMP_ON=86, #"水泵已开阀" + STR_WATER_PUMP_OFF=87, #"水泵已关阀" + STR_SWITCH_FAIL=88, #"粘连" + STR_SWITCH_THROUGH_2=89, #"击穿" + STR_GB_MONITOR=90, #"高压真空配电装置" + STR_GB_PROTECT_SYSTEM=91, #"综合保护监控系统" + STR_YBGY_MONITOR=92, #"移变高压侧" + STR_ERROR=93, #"故障" + STR_TEMPERATURE=94, #"温度" + STR_NEXT_CURRENT=95, #"后机电流" + STR_FRONT_CURRENT=96, #"前机电流" + STR_FJ_WS_LOCK=97, #"风机处瓦斯闭锁" + STR_GZM_WS_LOCK=98, #"工作面瓦斯闭锁" + STR_CURRENT=99, #"电流" + STR_VOLTAGE=100, #"电压" + STR_MONITOR_RES=101, #"监视电阻" + STR_SWITCH_THROUGH_SIMPLE=102, #"击穿" + STR_LINK_FAIL=103, #"联机失败" + STR_RES_OFF=104, #"漏电临界值" + STR_ANOTHER_REPAIR=105, #"另台维修请勿停机" + STR_WS_INTENSITY=106, #"瓦斯浓度" + STR_HIGH_VOLT=107, #"高压" + STR_AUTO=108, #"自动" + STR_MANUAL=109, #"手动" + STR_FREQUENCY=110, #"频率" + STR_FWD_ON=111, #正向启动 + STR_RVS_ON=112, #反向启动 + STR_TEMPERATURE_UNIT=113, #温度单位 + STR_POWER_LOSS=114, + STR_CALIBRATE_SUCCESS=115, #标定成功 + STR_ISOLATE_RES=116, #绝缘电阻 + STR_WATER_MONITOR=117, #水位控制排水监控系统 + STR_SWTICH_PROTECTOR=118, #ZDB-200电动机保护器 + STR_INDEPENDENT=119, #独立模式 + STR_DEPENDENT=120, #联动模式 + STR_OUT_POSITION=121, + STR_IN_POSITION=122, + STR_QUIT_IN_POSITION=123, + STR_RUN_DATA=124, + STR_WATER_MODE=125, + STR_INV_MODE=126, + STR_ISOLATE_RESISTOR_LOW=127, + STR_SWITCH=128, + STR_DIDAO=129, + STR_REMOTE=130, + STR_LOCAL=131, + STR_ONE_KEY_LOCK=132, + STR_COMM_FAIL=133, + +class Alarm(Enum): + ERR_NONE = 0, + ERR_CREEPAGE_LOCK = 1, #漏电报警 + ERR_VOLTAGE_LOW = 2, #欠压报警 + ERR_OVER_CURRENT = 3, #过流保护 + ERR_SHORT_CURRENT = 4, #短路保护 + ERR_CREEPAGE_SELECT = 5, #选漏 + ERR_UNBANLANCE_CURRENT = 6, #三相不平衡 + ERR_FLASH_FAIL = 7, #FLASH无法写入 + ERR_CREEPAGE_ACTIVE = 8, #漏电动作 + ERR_BREAK_PHASE = 9, #断相保护动作 + ERR_VOLTAGE_BREAK_PHASE = 10, #电压断相 + ERR_GAS_LOCK = 11, #瓦斯闭锁 + ERR_WIND_MOTOR_LOCK = 12, #风电闭锁 + ERR_VOLTAGE_OVER = 13, #过压报警 + ERR_SHORT_PHASE = 14, #相敏短路 + ERR_WATCH_DOG = 15, #看门狗保房 + ERR_SWITCH_FAIL = 16, #开关粘轿 + ERR_SWITCH_THROUGH = 17, #开关击? + ERR_SWITCH_ON_FAIL = 18, #开关合闸失? + ERR_COMM_FAIL = 19, #通讯故障 + ERR_CREEPAGE_SELECT2 = 20, #选择性报? + ERR_TEMP_IO_HIGH = 21, #IO口温度过驿 + ERR_SHORT_CURRENT_SIGNAL = 22, #信号电流短路 + ERR_REMOTE_OFF = 23, #远方分励 + ERR_REPAIR = 24, #维修状? + ERR_TEMP_ALARM = 25, #温度报警 + ERR_QUICK_CREEPAGE_ACT = 26, #快速漏电动仿 + ERR_GB_LEEK_SHORT = 27, #高爆绝缘短路 + ERR_GB_LEEK_BREAK = 28, #高爆绝缘断路 + ERR_YB_LOW_VOLTAGE_FAIL = 29, #移变低压侧失? + ERR_YB_LOW_VOLTAGE_UNREADY = 30, #移变低压侧未准备? + ERR_WS_FREQ_FAIL = 31, #瓦斯浓度超标 + ERR_HIGH_LOW_LOCK = 32, #高低压联钿 + ERR_MOTOR_ROTATE_LOCK = 33, #电机堵转 + ERR_FIX_TIME_OVER_CURRENT = 34, #定时限过泿 + ERR_FIX_TIME_SHORT_CURRENT = 35, #定时限短? + ERR_PHASE_ERROR = 36, #相序故障 + ERR_UNDER_LOAD = 37, #欠载保护 + ERR_PHASE_UNMATCH = 38, #互感器反? + ERR_EXTERNAL_ALARM = 39, #外部故障 + ERR_SENSOR_LOSS = 40, #水位传感器未? + ERR_ROTOR_SLOW = 41, + ERR_ROTOR_LOCK = 42, + ERR_POWER_LOSS = 43, #系统断电 + ERR_RSV1 = 44, + ERR_RSV2 = 45, + ERR_RSV3 = 46, + ERR_RSV4 = 47, + ERR_RSV5 = 48, + ERR_RSV6 = 49, + ERR_RSV7 = 50, + ERR_USER_ALARM1 = 51, #50,用户自定义故阿 + ERR_USER_ALARM2 = 52, #51,用户自定义故阿 + ERR_USER_ALARM3 = 53, #52,用户自定义故阿 + ERR_USER_ALARM4 = 54, #53,用户自定义故阿 + ERR_GB_LIGHT_THREAD_SON_LOSS = 55, + ERR_GB_LIGHT_THREAD_BROTHER_LOSS = 56, + ERR_GB_LIGHT_MODULE_LOSS = 57, + ERR_GB_FORCE_LEEK_OFF = 58, + ERR_GB_FORCE_SHORT_OFF = 59, + ERR_GB_CREEPAGE_SELECT = 60, #选择性报警 + ERR_SWITCH_OUT_POSITION = 61, + ERR_DZM_CREEPAGE_ACTIVE = 62, + ERR_OIL_PRESSURE_SENSOR_FAIL = 63, + ERR_FREQ_SENSOR_EXCEED = 64, + ERR_FREQ_SENSOR_FAIL = 65, + ERR_ANALOG_SENSOR_EXCEED = 66, + ERR_ANALOG_SENSOR_FAIL = 67, + ERR_VOLTAGE_LOSS = 68, + ERR_SPEED_LOSS = 69, + ERR_GB_ZERO_VOLT_OVER = 70, #零序过压 + ERR_PT_BREAK = 71, + ERR_CT_BREAK = 72, + ERR_ZERO_OVER_VOLTAGE = 73, + ERR_ZERO_OVER_CURRENT = 74, + ERR_GAS_WARNING = 75, + ERR_TEMP_WARNING = 76, + ERR_COMM_LOSS = 77, + ERR_CAR_FAIL = 78, + ERR_WATER_LOW = 79, + ERR_SWITCH_DIDAO_IN_POSITION = 80, + ERR_SWITCH_DIDAO_MID_POSITION = 81, + ERR_GB_CREEPAGE_SELECT_5 = 82, #5次谐波选漏 + ERR_FLUX_LOCK = 83, + ERR_GLOBAL_OIL_TEMP_LOCK = 84, + ERR_GLOBAL_WATER_PRESSURE_LOCK = 85, + ERR_GLOBAL_WATER_LEVEL_LOCK = 86, + ERR_GLOBAL_CURRENT_SENSOR_LOSS = 87, + ERR_GLOBAL_GAS_PERCENTAGE_LOCK = 88, + ERR_GLOBAL_GAS_SENSOR_LOSS = 89, + ERR_MEASURE_OVER_CURRENT = 90, #测量回路过流保护 + ERR_CREAPGE_BREAK = 91, + ERR_REMOTE_CREAPGE_TEST_FAIL = 92, + ERR_REMOTE_CREAPGE_TEST_SUCCESS = 93, + + + +class Alias(Enum): + alias_alarm = 0, + alias_str = 1, + alias_company = 2, + alias_bool = 3, + alias_voltage = 4, + alias_parity = 5, + alias_baud = 6, + alias_input = 7, + alias_onoff = 8, + alias_test = 9, + alias_can_baud = 10, + alias_measure_v = 11, + alias_switch_type = 12, + alias_work_mode = 13, + alias_us_phase = 14, + alias_sensor = 15, + alias_table = 16, + alias_remote = 17, + alias_current = 18, + alias_eth = 19, + alias_lcd = 20, + alias_dev = 21, + alias_fyj = 22, + alias_alarm_type = 23, + alias_car_position = 24, + +class Menu(Enum): + menu_current = 0, + menu_test = 1, + +alias_table = [ + {Alias.alias_alarm: {Alarm.ERR_NONE: "无故障", + Alarm.ERR_CREEPAGE_LOCK: "漏电闭锁", + Alarm.ERR_VOLTAGE_LOW: "欠压", + Alarm.ERR_OVER_CURRENT : "过流保护", #过流保护 + Alarm.ERR_SHORT_CURRENT : "短路保护", #短路保护 + Alarm.ERR_CREEPAGE_SELECT : "选漏", #选漏 + Alarm.ERR_UNBANLANCE_CURRENT : "三相不平衡", #三相不平衡报? + Alarm.ERR_FLASH_FAIL : "FLASH无法写入", #FLASH无法写入 + Alarm.ERR_CREEPAGE_ACTIVE : "漏电动作", #漏电动作 + Alarm.ERR_BREAK_PHASE : "电流断相", #断相保护动作 + Alarm.ERR_VOLTAGE_BREAK_PHASE : "电压断相", #电压断相 + Alarm.ERR_GAS_LOCK : "瓦斯闭锁", #瓦斯闭锁 + Alarm.ERR_WIND_MOTOR_LOCK : "风电闭锁", #风电闭锁 + Alarm.ERR_VOLTAGE_OVER : "过压", #过压报警 + Alarm.ERR_SHORT_PHASE : "相敏短路", #相敏短路 + Alarm.ERR_WATCH_DOG : "看门狗重启", #看门狗保房 + Alarm.ERR_SWITCH_FAIL : "真空管粘连", #真空管粘连 + Alarm.ERR_SWITCH_THROUGH : "真空管击穿", #真空管击穿 + Alarm.ERR_SWITCH_ON_FAIL : "合闸失败", #合闸失败 + Alarm.ERR_COMM_FAIL : "通讯失败", #通讯失败 + Alarm.ERR_CREEPAGE_SELECT2 : "选 漏", #选 漏 + Alarm.ERR_TEMP_IO_HIGH : "温度过高", #温度过高 + Alarm.ERR_SHORT_CURRENT_SIGNAL : "信号电流短路", #信号电流短路 + Alarm.ERR_REMOTE_OFF : "远方分励", #远方分励 + Alarm.ERR_REPAIR : "维修状态", #维修状态 + Alarm.ERR_TEMP_ALARM : "温度报警", #温度报警 + Alarm.ERR_QUICK_CREEPAGE_ACT : "漏电 动作", #快速漏电 动作 + Alarm.ERR_GB_LEEK_SHORT : "绝缘短路", #绝缘短路 + Alarm.ERR_GB_LEEK_BREAK : "绝缘断路", #绝缘断路 + Alarm.ERR_YB_LOW_VOLTAGE_FAIL : "低压侧故障", #低压侧故障 + Alarm.ERR_YB_LOW_VOLTAGE_UNREADY : "低压侧未准备好", #低压侧未准备好 + Alarm.ERR_WS_FREQ_FAIL : "瓦斯浓度超标", #瓦斯浓度超标 + Alarm.ERR_HIGH_LOW_LOCK : "高低压联锁", #高低压联锁 + Alarm.ERR_MOTOR_ROTATE_LOCK : "电机堵转", #电机堵转 + Alarm.ERR_FIX_TIME_OVER_CURRENT : "定时限过流", #定时限过流 + Alarm.ERR_FIX_TIME_SHORT_CURRENT : "定时限短路", #定时限短路? + Alarm.ERR_PHASE_ERROR : "相序故障", #相序故障 + Alarm.ERR_UNDER_LOAD : "欠载保护", #欠载保护 + Alarm.ERR_EXTERNAL_ALARM : "外部故障", #外部故障 + Alarm.ERR_POWER_LOSS : "系统断电", #系统断电 + Alarm.ERR_USER_ALARM1 : "触头温度超标", #用户自定义故阿 + Alarm.ERR_USER_ALARM2 : "触头温度超标", #用户自定义故阿 + Alarm.ERR_GB_LIGHT_THREAD_SON_LOSS : "光纤子节点丢失", + Alarm.ERR_GB_LIGHT_THREAD_BROTHER_LOSS : "光纤兄弟节点丢失", + Alarm.ERR_GB_LIGHT_MODULE_LOSS : "无光纤模块", + Alarm.ERR_GB_FORCE_LEEK_OFF : "光纤漏电越级跳", + Alarm.ERR_GB_FORCE_SHORT_OFF : "光纤短路越级跳", + Alarm.ERR_GB_CREEPAGE_SELECT : "漏电", #选择性报警 + Alarm.ERR_SWITCH_OUT_POSITION : "开关未推到位", + Alarm.ERR_DZM_CREEPAGE_ACTIVE : "漏电动作", + Alarm.ERR_OIL_PRESSURE_SENSOR_FAIL : "油压传感器未接入", + Alarm.ERR_FREQ_SENSOR_EXCEED : "频率传感器超限", + Alarm.ERR_FREQ_SENSOR_FAIL : "频率传感器未接入", + Alarm.ERR_ANALOG_SENSOR_EXCEED : "模拟量传感器超限", + Alarm.ERR_ANALOG_SENSOR_FAIL : "模拟量传感器未接入", + Alarm.ERR_VOLTAGE_LOSS : "失压故障", + Alarm.ERR_SPEED_LOSS : "失速故障", + Alarm.ERR_GB_ZERO_VOLT_OVER : "零序过压", + Alarm.ERR_PT_BREAK : "PT断线", + Alarm.ERR_CT_BREAK : "CT断线", + Alarm.ERR_ZERO_OVER_VOLTAGE : "零序过压", + Alarm.ERR_ZERO_OVER_CURRENT : "零序过流", + Alarm.ERR_GAS_WARNING : "轻瓦斯告警", + Alarm.ERR_TEMP_WARNING : "温度异常", + Alarm.ERR_COMM_LOSS : "通讯中断", + Alarm.ERR_CAR_FAIL : "手车控制器故障", + Alarm.ERR_WATER_LOW : "循环泵无水故障", + Alarm.ERR_SWITCH_DIDAO_IN_POSITION: "地刀合闸保护中", + Alarm.ERR_SWITCH_DIDAO_MID_POSITION: "地刀未到位", + Alarm.ERR_GB_CREEPAGE_SELECT_5: "5次谐波选漏", + Alarm.ERR_FLUX_LOCK: "流量闭锁", + Alarm.ERR_GLOBAL_OIL_TEMP_LOCK: "油温闭锁", + Alarm.ERR_GLOBAL_WATER_PRESSURE_LOCK: "水压闭锁", + Alarm.ERR_GLOBAL_WATER_LEVEL_LOCK: "液位闭锁", + Alarm.ERR_GLOBAL_CURRENT_SENSOR_LOSS: "互感器未接入", + Alarm.ERR_GLOBAL_GAS_PERCENTAGE_LOCK: "瓦斯浓度闭锁", + Alarm.ERR_GLOBAL_GAS_SENSOR_LOSS: "瓦斯断线", + Alarm.ERR_MEASURE_OVER_CURRENT: "测量回路过流", + Alarm.ERR_CREAPGE_BREAK: "接地开路", + Alarm.ERR_REMOTE_CREAPGE_TEST_FAIL: "漏电测试失败", + Alarm.ERR_REMOTE_CREAPGE_TEST_SUCCESS: "漏电测试成功", + } + }, + + {"alias_str" : { StrInfo.STR_MAX_VALUE :"最大值", + StrInfo.STR_MIN_VALUE :"最小值", + StrInfo.STR_SET_VALUE :"设定值", + StrInfo.STR_VALUE_EXCEED :"设定值越界", + StrInfo.STR_ZERO_PHASE :"有功相位", + StrInfo.STR_INPUT_PASSWORD :"请输入系统密码", + StrInfo.STR_SYSTEM_PASSWORD :"系统密码", + StrInfo.STR_PASSWORD_ERR :"密码错误", + StrInfo.STR_SHORT_PHASE_MODIFY :"短路倍数\n恢复缺省", + StrInfo.STR_SWITCH_TYPE_MODIFY :"漏电延时\n恢复缺省", + StrInfo.STR_SOFT_VERSION:"软件版本", + StrInfo.STR_NO_RECORD :"无记录", + StrInfo.STR_ENTER_TO_CLEAR:"确定清除", + StrInfo.STR_ESC_TO_CANCEL:"ESC取消", + StrInfo.STR_CLEAR_FINISH :"清除成功", + StrInfo.STR_ESC_TO_EXIT :"ESC退出", + StrInfo.STR_INPUT_NEW_PASSWORD :"请设置新密码", + StrInfo.STR_NOT_POSSIBLE_TO_MODIFY :"不可修改", + StrInfo.STR_CAPACITOR_MEASURE :"电容检测", + StrInfo.STR_CAPACITOR_EQUAL :"电容", + StrInfo.STR_LEEKAGE_MEASURE :"检测中..", + StrInfo.STR_TEST_ACTIVE :"测试状态", + StrInfo.STR_POWER_ON :"合闸", + StrInfo.STR_POWER_OFF :"分闸", + StrInfo.STR_FAIL :"失效", + StrInfo.STR_NORMAL :"正常", + StrInfo.STR_MEASURE_CURRENT :"实测电流", + StrInfo.STR_RATE_CURRENT :"额定电流", + StrInfo.STR_ANGLE :"角度", + StrInfo.STR_AVERAGE_CURRENT :"平均电流", + StrInfo.STR_MAX_CURRENT :"最大电流", + StrInfo.STR_LOW_VOLTAGE_RATE :"欠压值", + StrInfo.STR_MEASURE_VOLTAGE :"实测电压", + StrInfo.STR_ZERO_CURRENT :"零序电流", + StrInfo.STR_ZERO_VOLTAGE :"零序电压", + StrInfo.STR_RECORD :"记录", + StrInfo.STR_LEEAKGE_LOCK_VALUE :"闭锁设定值", + StrInfo.STR_LEEAKGE_ACTIVE_VALUE :"动作设定值", + StrInfo.STR_MEASURE_RESISTOR :"电阻测量值", + StrInfo.STR_FRONT_CIRCUIT :"前机", + StrInfo.STR_NEXT_CIRCUIT :"后机", + StrInfo.STR_RATE_VOLTAGE :"额定电压", + StrInfo.STR_PARA_MODIFY_ERROR :"参数修改失败", + StrInfo.STR_LEEKAGE_RES_FINISH :"闭锁电阻检测完", + StrInfo.STR_LEEKAGE_RES_MEASURE :"闭锁电阻检测中", + StrInfo.STR_SFJ_MONITOR :"双风机磁力监控", + StrInfo.STR_CL_MONITOR :"磁力监控系统", + StrInfo.STR_CL_DB_MONITOR :"双回路磁力监控", + StrInfo.STR_YB_MONITOR :"移变低压侧", + StrInfo.STR_KUI_DIAN_MONITOR :"馈电开关监控", + StrInfo.STR_TYPE :"类型:", + StrInfo.STR_TOP_SWITCH :"总开关", + StrInfo.STR_BOTTOM_SWITCH :"分开关", + StrInfo.STR_COS_V :"COS(ΦaΦbΦc)", + StrInfo.STR_POWER_VI :"有功功率", + StrInfo.STR_POWER_VI_T :"电量", + StrInfo.STR_FRONT_POWER_ON :"前机合", + StrInfo.STR_NEXT_POWER_ON :"后机合", + StrInfo.STR_FRONT_POWER_OFF :"前机分", + StrInfo.STR_NEXT_POWER_OFF :"后机分", + StrInfo.STR_FRONT_RESISTOR :"前机电阻:", + StrInfo.STR_NEXT_RESISTOR :"后机电阻:", + StrInfo.STR_WORK_MODE :"模式:", + StrInfo.STR_LEEKAGE_RES :"漏电电阻", + StrInfo.STR_REVERSE_VOLTAGE :"反电动势\n注意安全", + StrInfo.STR_LIGHT :"照明", + StrInfo.STR_SIGNAL :"信号", + StrInfo.STR_SWITCH_STATUS :"开关状态", + StrInfo.STR_POWER_ON_COUNTS :"合闸次数", + StrInfo.STR_SETTING :"设定值", + StrInfo.STR_POW_U :"无功功率", + StrInfo.STR_TEST_FAIL :"测试不正确", + StrInfo.STR_LEAKAGE_TEST_FAIL :"漏电测试不正确", + StrInfo.STR_DETECT_FAIL_WHEN_POWER_ON :"合闸时不能检测", + StrInfo.STR_LINK :"联机系统", + StrInfo.STR_SINGLE :"单机系统", + StrInfo.STR_STATUS :"状态", + StrInfo.STR_MAIN_FIRST :"主机优先", + StrInfo.STR_MAIN_BAK_SAME :"主辅对等", + StrInfo.STR_MAIN_BAK_SWITCH :"主辅轮动", + StrInfo.STR_SWITCH_DISABLE :"局扇模式", + StrInfo.STR_MAIN :"主机", + StrInfo.STR_BACKUP :"备机", + StrInfo.STR_SINGLE_CIRCUIT_FRONT :"单前机", + StrInfo.STR_SINGLE_CIRCUIT_NEXT :"单后机", + StrInfo.STR_DOUBLE_CIRCUIT :"双回路", + StrInfo.STR_WATER_PUMP_ON :"水泵已开阀", + StrInfo.STR_WATER_PUMP_OFF :"水泵已关阀", + StrInfo.STR_SWITCH_FAIL :"粘连", + StrInfo.STR_SWITCH_THROUGH_2 :"击穿", + StrInfo.STR_GB_MONITOR :"高压真空配电装置", + StrInfo.STR_GB_PROTECT_SYSTEM :"综合保护监控系统", + StrInfo.STR_YBGY_MONITOR :"移变高压侧", + StrInfo.STR_ERROR :"故障", + StrInfo.STR_TEMPERATURE:"温度", + StrInfo.STR_NEXT_CURRENT :"后机电流", + StrInfo.STR_FRONT_CURRENT :"前机电流", + StrInfo.STR_FJ_WS_LOCK :"风机处瓦斯闭锁", + StrInfo.STR_GZM_WS_LOCK :"工作面瓦斯闭锁", + StrInfo.STR_CURRENT :"电流", + StrInfo.STR_VOLTAGE:"电压", + StrInfo.STR_MONITOR_RES :"监视电阻", + StrInfo.STR_SWITCH_THROUGH_SIMPLE :"击穿", + StrInfo.STR_LINK_FAIL :"联机失败", + StrInfo.STR_RES_OFF :"漏电临界值", + StrInfo.STR_ANOTHER_REPAIR :"另台维修请勿停机", + StrInfo.STR_WS_INTENSITY :"瓦斯浓度", + StrInfo.STR_HIGH_VOLT :"高压", + StrInfo.STR_AUTO :"自动", + StrInfo.STR_MANUAL :"手动", + StrInfo.STR_FREQUENCY :"频率", + StrInfo.STR_FWD_ON :"正向启动", + StrInfo.STR_RVS_ON :"反向启动", + StrInfo.STR_TEMPERATURE_UNIT : "℃", + StrInfo.STR_POWER_LOSS :"系统断电", + StrInfo.STR_CALIBRATE_SUCCESS :"标定成功", + StrInfo.STR_ISOLATE_RES :"绝缘电阻", + StrInfo.STR_WATER_MONITOR :"水位控制排水监控系统", + StrInfo.STR_SWTICH_PROTECTOR :"ZDB-200电动机保护器", + StrInfo.STR_INDEPENDENT :"独立模式", + StrInfo.STR_DEPENDENT :"联动模式", + StrInfo.STR_OUT_POSITION : "未到位", + StrInfo.STR_IN_POSITION : "进到位", + StrInfo.STR_QUIT_IN_POSITION : "退到位", + StrInfo.STR_RUN_DATA : "运行数据", + StrInfo.STR_WATER_MODE : "水位", + StrInfo.STR_INV_MODE : "可逆", + StrInfo.STR_ISOLATE_RESISTOR_LOW : "绝缘电阻降低", + StrInfo.STR_SWITCH : "开关", + StrInfo.STR_DIDAO : "地刀", + StrInfo.STR_REMOTE : "程控", + StrInfo.STR_LOCAL : "就地", + StrInfo.STR_ONE_KEY_LOCK : "闭锁中", + StrInfo.STR_COMM_FAIL : "通讯失败", + }}, + + {"alias_company": {0: "浙江志展智能科技", + 1: "华荣科技", + } + }, + {"alias_sensor" : {0: "10 A", 1: "30 A", 2: "50 A", 3: "75 A", 4: "80 A", 5: "100 A", + 6: "120 A", 7: "150 A", 8: "200 A", 9: "300 A", 10: "315 A", 11: "400 A", + 12: "500 A", 13: "600 A", 14: "630 A", 15: "800 A", 16: "1000 A", 17: "1250 A", + 18: "1300 A", 19: "1600 A", 20: "160 A", 21: "250 A", 22: "750 A", 23: "1200 A", + 24: "1500 A", 25: "2000 A", 26: "2500 A" ,27: "5 A"}}, + {"alias_switch_type" : {0: "总开关", 1: "分开关"}}, + {"alias_measure_v": {0: "正电压", 1: "负电压"}}, + {"alias_select": {0: "工频负载", 1:"变频负载"}}, + {"alias_bool": {0:"关闭", 1:"打开"}}, + {"alias_voltage": {0:"380V", 1:"660V", 2:"1140V", 3:"3300V", 4:"100V", 5:"127V", 6:"220V", 7:"440V"}}, + {"alias_parity": {0: "无校验", 1: "奇校验", 2: "偶校验"}}, + {"alias_baud": {0: "1200", 1: "2400", 2: "4800", 3: "9600", 4:"19200", 5:"38400", 6:"51200", 7:"115200"}}, + {"alias_input": {0: "常开", 1: "常闭"}}, + {"alias_onoff": {0: "分闸", 1: "合闸"}}, + {"alias_test": {0: "模拟测试", 1: "继电器测试"}}, + {"alias_can_baud": {0: "1000K", 1: "800K", 2: "500K", 3: "250K", 4: "125K", 5: "100K", 6: "50K", 7: "20K", 8: "10K", 9: "AUTO"}}, + {Alias.alias_measure_v: {0: "正电压", 1: "负电压"}}, + {Alias.alias_switch_type : {0: "总开关", 1: "分开关"}}, + {"alias_work_mode": {0: "水位模式", 1: "磁力模式", 2: "正反转模式", 3: "联控模式"}}, + {"alias_us_phase": {0: "Uab_ABC", 1: "Uab_ACB", 2: "Ubc_ABC", 3: "Ubc_ACB", 4: "Uca_ABC", 5: "Uca_ACB"}}, + {Alias.alias_sensor : {0: "10 A", 1: "30 A", 2: "50 A", 3: "75 A", 4: "80 A", 5: "100 A", + 6: "120 A", 7: "150 A", 8: "200 A", 9: "300 A", 10: "315 A", 11: "400 A", + 12: "500 A", 13: "600 A", 14: "630 A", 15: "800 A", 16: "1000 A", 17: "1250 A", + 18: "1300 A", 19: "1600 A", 20: "160 A", 21: "250 A", 22: "750 A", 23: "1200 A", + 24: "2500 A", 25: "5 A"}}, + {"alias_table": {0: "0", 1: "1", 2: "2", 3: "3"}}, + {"alias_remote": {0: "就地", 1: "程控"}}, + {"alias_current": {0: "测量模式", 1: "平均模式"}}, + + {Alias.alias_eth : {0: "无", 1: "一代以太网", 2: "二代以太网"}}, + {Alias.alias_lcd : {0: "一体迪文屏", 1: "分体迪文屏", 2: "大彩屏", 3 : "昆仑通态屏"}}, + {Alias.alias_fyj : {0: "无", 1: "FYJ", 2: "FYJ2", 3 : "FYJ4"}}, + {Alias.alias_dev : {0: "馈电", 1: "高爆", 2: "移变", 3 : "双风机", 4 : "磁力", + 5 : "双磁力", 6 : "照明", 7 : "移变高压侧", 8: "双高爆", + 9: "组合馈电", 10: "双照明", 11 : "ZZ630"}}, + {"alias_alarm_type": {0: "试验", 1: "动作"}}, + {"alias_car_position": {0: "小车未到位", 1 : "小车试验位", 2: "小车工作位", 3 : "位置信号异常"}}, +] + +MENU_PROPERTY_READ_ONLY = 0 +MENU_PROPERTY_USER = 1 +MENU_PROPERTY_ENGINEER = 2 +MENU_PROPERTY_WRITE_ONLY = 3 + + +def get_alias_value_str(alias_str, alias_value) : + alias_value_msg = None + compare_value = int(alias_value) + + for alias_dict in alias_table : + for alias_name, value_dict in alias_dict.items(): + if isinstance(alias_name, Enum): + alias_name = alias_name.name + + if alias_name == alias_str : + for each_value_dict in value_dict.items(): + if isinstance(each_value_dict[0], Enum) : + key_value = each_value_dict[0].value[0] + else : + key_value = each_value_dict[0] + key_string = each_value_dict[1] + + if int(key_value) == compare_value : + alias_value_msg = key_string + return alias_value_msg + return alias_value_msg + +def func_adjust_current_menu_item(device : class_modbus_comm_device, menu_item) : + #获得互感器类型, 根据互感器类型来调整 scale, format, 让电流可以正确显示与设置 + sensor_type = device.comm_get_value_from_mqtt_name("Sensor") + sensor_current = 400 + + if sensor_type != None : + value_str = get_alias_value_str("alias_sensor", sensor_type) + if value_str != None : + value_str_no_unit = value_str.replace("A", "") + sensor_current = int(value_str_no_unit) + + #80A 以下电流互感器, 显示一位小数点 + if sensor_current <= 80 : + new_item = menu_item + new_item["scale"] = 0.1 + new_item["format"] = "%1.1f" + return new_item + else : + return menu_item + +def func_caculate_Cosf(device : class_modbus_comm_device, menu_item, mqtt_dict : dict = None) : + format_str = utils.dict_or_object_get_attr(menu_item, "format", "%1.0f") + try : + cosa = device.comm_get_value_from_mqtt_name("Cosa") + cosb = device.comm_get_value_from_mqtt_name("Cosb") + cosc = device.comm_get_value_from_mqtt_name("Cosc") + cosf = (cosa + cosb + cosc) / 3 + except Exception as e : + cosf = 0.0 + Cosf_str = format_str%(cosf) + mqtt_dict["Cosf"] = Cosf_str + +#视频触发 +def func_video_trigger(device : class_modbus_comm_device, menu_item, mqtt_dict : dict = None) : + value = device.comm_get_value_from_menu_item(menu_item) + if value == None : + #通讯未建立 + return + + value = round(value) + in_position = (value >> 10) & 0x1 + out_position = (value >> 9) & 0x1 + now_time = time.time() + if hasattr(device, "MqttVideoActive") == False: + #动态添加视频触发所需的变量 + device.MqttVideoActive = False + device.MqttVideoBaseTime = now_time + device.DI_SwitchInPosition = in_position + device.DI_SwitchQuitPosition = out_position + + if in_position != device.DI_SwitchInPosition or out_position != device.DI_SwitchQuitPosition: + #开关进到位与 开关退到位信号, 当这两个状态发生变化时, 触发20秒的视频 + device.MqttVideoActive = True + device.MqttVideoBaseTime = now_time + mqtt_dict["VideoActive"] = "1" + if device.MqttVideoActive : + #视频延时20后主动关闭视频显示 + if now_time - device.MqttVideoBaseTime > 20.0 : + mqtt_dict["VideoActive"] = "0" + device.MqttVideoActive = False + + device.DI_SwitchInPosition = in_position + device.DI_SwitchQuitPosition = out_position + + +COLOR_RED = "#EE2D2D" +COLOR_GREEN = "#83BF6E" + +def func_process_car_message(device : class_modbus_comm_device, menu_item, mqtt_dict : dict = None) : + car_pos_value = device.comm_get_value_from_menu_item(menu_item) + if car_pos_value == None or mqtt_dict == None: + return + car_msg = get_alias_value_str("alias_car_position", car_pos_value) + if car_msg != None : + mqtt_dict["CarPositionMsg"] = car_msg + +def func_process_alarm_message(device : class_modbus_comm_device, menu_item, mqtt_dict : dict = None) : + alarm_id = device.comm_get_value_from_menu_item(menu_item) + if alarm_id == None or mqtt_dict == None: + #通讯未建立 + return + alarm_msg = get_alias_value_str("alias_alarm", alarm_id) + if alarm_msg != None : + alarm_msg_dict = {} + if alarm_id > 0 : + alarm_msg_dict["value"] = alarm_msg + alarm_msg_dict["color"] = COLOR_RED + else : + alarm_msg_dict["value"] = alarm_msg + alarm_msg_dict["color"] = COLOR_GREEN + + mqtt_dict["AlarmMessage"] = alarm_msg_dict + +mqtt_video_trigger = [ + { + "name": "视频触发", + "addr": "8015", + "call" : func_video_trigger, + }, +] + +mqtt_time_update = [ + { + "name": "Year", + "addr": "2300", + "format" : "%04.0f", + "mqtt" : "Year", + }, + { + "name": "Month", + "addr": "2301", + "format" : "%02.0f", + "mqtt" : "Month", + }, + { + "name": "Day", + "addr": "2302", + "format" : "%02.0f", + "mqtt" : "Day", + }, + { + "name": "Hour", + "addr": "2303", + "format" : "%02.0f", + "mqtt" : "Hour", + }, + { + "name": "Minute", + "addr": "2304", + "format" : "%02.0f", + "mqtt" : "Minute", + }, + { + "name": "Second", + "addr": "2305", + "format" : "%02.0f", + "mqtt" : "Second", + }, + { + "name": "SetupTime", + "addr": "2305", + "mqtt" : "SetupTime", + "action" : "SetupTime", + }, +] + +mqtt_pack_param = [ + #系统设置 + { + "name": "开关类型", + "addr": "8210", + "alias" : "alias_switch_type", + "mqtt" : "SwitchType", + }, + { + "name": "瓦斯触点", + "addr": "8190", + "alias" : "alias_input", + "mqtt" : "Gas", + }, + { + "name": "瓦斯延时", + "addr": "8191", + "scale" : 0.01, + "format": "%06.2f", + "mqtt" : "GasDelay", + }, + { + "name": "瓦斯浓度", + "addr": "8237", + "scale" : 0.01, + "format": "%05.2f", + "mqtt" : "GasConcentration", + }, + { + "name": "风电触点", + "addr": "8248", + "alias" : "alias_input", + "mqtt" : "Fan", + }, + { + "name": "风电延时", + "addr": "8234", + "scale" : 0.01, + "format": "%06.2f", + "mqtt" : "FanDelay", + }, + { + "name": "远近控模式", + "addr": "8558", + "alias" : "alias_remote", + "mqtt" : "FarAndNearMode", + }, + { + "name": "维修模式", + "addr": "8565", + "alias" : "alias_bool", + "mqtt" : "MaintenanceMode", + }, + { + "name": "小车报警延时", + "addr": "8576", + "scale" : 0.1, + "format": "%05.1f", + "mqtt" : "CarAlarmDelay", + }, + + { + "name": "断电延时", + "addr": "8249", + "format": "%06.1f", + "scale" : 0.1, + "mqtt" : "OffVoltageDelay", + }, + { + "name": "真空管粘连延时", + "addr": "8209", + "format": "%05.2f", + "scale" : 0.01, + "mqtt" : "TubeDelay", + }, + { + "name": "负载选择", + "alias": "alias_select", + "addr": "8254", + "mqtt" : "LoadSelect", + }, + { + "name": "温度设定", + "addr": "8423", + "scale" : 1, + "format": "%03.0f", + "mqtt" : "Temperature", + }, + { + "name": "触头温度设定", + "addr": "8496", + "scale" : 0.1, + "format": "%05.1f", + "mqtt" : "TemperatureContact", + }, + #电流参数 + { + "name": "额定电流", + "unit": "A", + "addr": "8200", + "scale" : 1.0, + "format": "%04.0f", + "action": "rate_current", + "mqtt" : "RateCurrent", + "adjust" : func_adjust_current_menu_item, + }, + + { + "name": "短路倍数", + "unit": "Ie", + "scale" : 0.01, + "format": "%05.2f", + "addr": "8201", + "min" : 1.0, + "max" : 10.0, + "mqtt" : "ShortLevel", + }, + { + "name": "短路延时", + "unit": "ms", + "scale" : 1.0, + "format": "%03.0f", + "addr": "8198", + "min" : 0.0, + "max" : 200.0, + "mqtt" : "ShortDelay", + }, + { + "name": "互感器类型", + "alias" :"alias_sensor", + "addr": "8207", + "mqtt" : "Sensor", + }, + { + "name": "不平衡值", + "unit": "%", + "addr": "8213", + "scale" : 1.0, + "format": "%03.0f", + "min" : 10.0, + "max" : 100.0, + "mqtt" : "UnbalanceLevel", + }, + { + "name": "不平衡延时", + "unit": "s", + "addr": "8197", + "scale" : 0.1, + "format": "%04.1f", + "mqtt" : "UnbalanceDelay", + }, + { + "name": "定时限过流", + "unit": "Ie", + "addr": "8239", + "scale" : 0.01, + "format": "%04.2f", + "mqtt" : "TimeOvercurrent", + }, + { + "name": "定时限过流延时", + "unit": "ms", + "addr": "8240", + "scale" : 1, + "format": "%05.0f", + "mqtt" : "TimeOvercurrentDelay", + }, + { + "name": "定时限短路", + "unit": "Ie", + "addr": "8523", + "scale" : 0.01, + "format": "%04.2f", + "mqtt" : "TimeLimitcurrent", + }, + { + "name": "定时限短路延时", + "unit": "ms", + "addr": "8524", + "scale" : 1, + "format": "%03.0f", + "mqtt" : "TimeLimitcurrentDelay", + }, + { + "name": "Ia 校正", + "unit": "%", + "scale" : 0.1, + "format": "%05.1f", + "addr": "8217", + "min" : 60.0, + "max" : 130.0, + "mqtt" : "CorrectIa", + }, + { + "name": "Ib 校正", + "unit": "%", + "scale" : 0.1, + "format": "%05.1f", + "addr": "8218", + "min" : 60.0, + "max" : 130.0, + "mqtt" : "CorrectIb", + }, + { + "name": "Ic 校正", + "unit": "%", + "scale" : 0.1, + "format": "%05.1f", + "addr": "8219", + "min" : 60.0, + "max" : 130.0, + "mqtt" : "CorrectIc", + }, + + + #电压参数 + { + "name": "额定电压", + "addr": "8211", + "alias": "alias_voltage", + "mqtt" : "RateVoltage", + }, + { + "name": "欠压比值", + "unit": "%", + "addr": "8202", + "scale" : 0.1, + "format": "%05.1f", + "min" : 60.0, + "max" : 100.0, + "mqtt" : "PercentLowVoltage", + }, + { + "name": "欠压延时", + "unit": "s", + "addr": "8177", + "scale" : 0.01, + "format": "%05.2f", + "min" : 0.1, + "max" : 60.0, + "mqtt" : "DelayLowVoltage", + }, + { + "name": "过压比值", + "unit": "%", + "addr": "8224", + "scale" : 0.1, + "format": "%05.1f", + "min" : 100.0, + "max" : 150.0, + "mqtt" : "PercentHighVoltage", + }, + { + "name": "过压延时", + "unit": "s", + "addr": "8176", + "scale" : 0.01, + "format": "%05.2f", + "min" : 0.1, + "max" : 60.0, + "mqtt" : "DelayHighVoltage", + }, + { + "name": "Ua校正", + "unit": "%", + "addr": "8220", + "scale" : 0.1, + "format": "%05.1f", + "min" : 60.0, + "max" : 130.0, + "mqtt" : "CorrectUa", + }, + { + "name": "Ub校正", + "unit": "%", + "addr": "8221", + "scale" : 0.1, + "format": "%05.1f", + "min" : 60.0, + "max" : 130.0, + "mqtt" : "CorrectUb", + }, + { + "name": "Uc校正", + "unit": "%", + "addr": "8222", + "scale" : 0.1, + "format": "%05.1f", + "min" : 60.0, + "max" : 130.0, + "mqtt" : "CorrectUc", + }, + { + "name": "Us校正", + "unit": "%", + "addr": "8214", + "scale" : 0.1, + "format": "%05.1f", + "min" : 60.0, + "max" : 130.0, + "mqtt" : "CorrectUs", + }, + + + #漏电参数 + { + "name": "零序电流", + "addr": "2007", + "unit": "mA", + "scale" : 0.1, + "format" : "%05.1f", + "mqtt" : "KD.Io", + "action" : "Visible_KD", #馈电模式所需菜单 + }, + { + "name": "零序电压", + "addr": "2008", + "unit": "V", + "scale" : 1.0, + "format" : "%04.0f", + "mqtt" : "KD.Uo", + "action" : "Visible_KD", #馈电模式菜单有效 + }, + { + "name": "漏电延时", + "addr": "8205", + "unit": "ms", + "scale" : 1.0, + "format" : "%03.0f", + "mqtt" : "LeakageDelay", + "action" : "Visible_KD_GB", #馈电, 高爆模式所需菜单 + }, + { + "name": "串联电阻", + "addr": "8236", + "unit": "K", + "scale" : 0.1, + "format" : "%05.1f", + "mqtt" : "SeriesResistor" + }, + { + "name": "闭锁电阻", + "addr": "8204", + "unit": "K", + "scale" : 0.1, + "format" : "%05.1f", + "action" : "Hide_GB", #高爆模式下隐藏菜单 + "mqtt" : "BlockResistor" + }, + { + "name": "Uo校正值", + "addr": "8187", + "unit": "%", + "scale" : 0.1, + "format" : "%05.1f", + "action" : "Visible_KD_GB", #馈电与高爆模式菜单有效 + "mqtt" : "CorrectUo" + }, + { + "name": "Io校正值", + "addr": "8196", + "unit": "%", + "scale" : 0.1, + "format" : "%05.1f", + "action" : "Visible_KD_GB", #馈电与高爆模式菜单有效 + "mqtt" : "CorrectIo" + }, + { + "name": "检测电压", + "addr": "8253", + "alias": "alias_measure_v", + "mqtt" : "MeasureV" + }, + { + "name": "漏电投入延时", + "addr": "8252", + "unit": "S", + "scale" : 0.1, + "format" : "%04.1f", + "action" : "Hide_GB", #高爆模式下隐藏菜单 + "default": 3.0, + "mqtt" : "DelayLeakageTrip" + }, + + #保护参数投入 + { + "name": "短路保护", + "alias": "alias_bool", + "addr": "8192.1", + "mqtt" : "ProtectShort", + }, + { + "name": "相敏保护", + "alias": "alias_bool", + "addr": "8192.13", + "mqtt" : "ProtectPhaseSensitivity", + }, + { + "name": "过载保护", + "alias": "alias_bool", + "addr": "8192.3", + "mqtt" : "ProtectOverLoad", + }, + { + "name": "电流断相保护", + "alias": "alias_bool", + "addr": "8192.4", + "mqtt" : "ProtectCurrentPhaseOff", + }, + { + "name": "不平衡保护", + "alias": "alias_bool", + "addr": "8192.2", + "mqtt" : "ProtectImbalance", + }, + { + "name": "漏电闭锁保护", + "alias": "alias_bool", + "addr": "8192.10", + "mqtt" : "ProtectLeakage" + }, + { + "name": "定时限过载保护", + "alias": "alias_bool", + "addr": "8192.11", + "mqtt" : "ProtectTimeLimitOverload" + }, + { + "name": "定时限短路保护", + "alias": "alias_bool", + "addr": "8192.12", + "mqtt" : "ProtectTimeLimitShortCircuit", + }, + { + "name": "欠压保护", + "alias": "alias_bool", + "addr": "8174.1", + "mqtt" : "ProtectUnderVoltage", + }, + { + "name": "过压保护", + "alias": "alias_bool", + "addr": "8174.0", + "mqtt" : "ProtectOverVoltage", + }, + { + "name": "电压断相保护", + "alias": "alias_bool", + "addr": "8174.2", + "mqtt" : "ProtectVoltagePhaseOff", + }, + { + "name": "漏电动作保护", + "alias": "alias_bool", + "addr": "8174.6", + "mqtt" : "ProtectLeakageAction", + }, + { + "name": "真空管粘连保护", + "alias": "alias_bool", + "addr": "8174.15", + "mqtt" : "ProtectTubeAdhesion", + }, + { + "name": "温度报警保护", + "alias": "alias_bool", + "addr": "8174.8", + "mqtt" : "ProtectTemperatureAlarm", + }, + + #通讯设置 + { + "name": "485地址", + "addr": "8212", + "format": "%03.0f", + "mqtt" : "485Address", + }, + { + "name": "485波特率", + "alias": "alias_baud", + "addr": "8188", + "mqtt" : "485Baud", + }, + { + "name": "485校验", + "alias": "alias_parity", + "addr": "8194", + "mqtt" : "485Parity", + }, + { + "name": "CAN地址", + "format": "%03.0f", + "addr": "8440", + "mqtt" : "CANAddress", + }, + { + "name": "CAN波特率", + "alias": "alias_can_baud", + "addr": "8439", + "mqtt" : "CANBaud", + }, +] + +mqtt_pack_measure = [ + { + "name": "SwitchStatus", + "addr": "8162.0", + "alias": Alias.alias_onoff, + "remap_addr": remap.COMM_REMAP_MEASURE_SWITCH_STATUS, + "measure" : True, + "mqtt" : "SwitchStatus", + }, + { + "name": "AlarmId", + "addr": "8166", + "alias": Alias.alias_alarm, + "remap_addr": remap.COMM_REMAP_MEASURE_SWITCH_ALARM, + "measure" : True, + "mqtt" : "AlarmId", + }, + { + "name": "AlarmMessage", + "addr": "8166", + "alias": Alias.alias_alarm, + "call" : func_process_alarm_message, + }, + { + "name": "CarPositionMsg", + "addr": "8015.10#2", + "alias": Alias.alias_car_position, + "call" : func_process_car_message, + }, + { + "name": "Ia", + "unit": "A", + "addr": "8156", + "format": "%05.1f", + "remap_addr": remap.COMM_REMAP_MEASURE_FLOAT_IA, + "measure" : True, + "mqtt" : "Ia", + "adjust" : func_adjust_current_menu_item, + }, + { + "name": "Ib", + "unit": "A", + "addr": "8157", + "format": "%05.1f", + "remap_addr": remap.COMM_REMAP_MEASURE_FLOAT_IB, + "measure" : True, + "mqtt" : "Ib", + "adjust" : func_adjust_current_menu_item, + }, + { + "name": "Ic", + "unit": "A", + "addr": "8158", + "format": "%05.1f", + "remap_addr": remap.COMM_REMAP_MEASURE_FLOAT_IC, + "measure" : True, + "mqtt" : "Ic", + "adjust" : func_adjust_current_menu_item, + }, + { + "name": "Voltage", + "unit": "V", + "addr": "8153", + "format": "%05.0f", + "remap_addr": remap.COMM_REMAP_MEASURE_FLOAT_UAB, + "measure" : True, + "mqtt" : "Voltage", + }, + { + "name": "Current", + "unit": "A", + "addr": "8156", + "format": "%05.1f", + "remap_addr": remap.COMM_REMAP_MEASURE_FLOAT_IA, + "measure" : True, + "mqtt" : "Current", + }, + { + "name": "Power", + "unit": "kw", + "addr": "8167", + "format": "%07.0f", + "measure" : True, + "mqtt" : "Power", + }, + { + "name": "Electricity", + "unit": "kwh", + "addr": "8160#2", + "format": "%07.0f", + "measure" : True, + "mqtt" : "Electricity", + }, + { + "name": "Uab", + "unit": "V", + "addr": "8153", + "format": "%05.0f", + "remap_addr": remap.COMM_REMAP_MEASURE_FLOAT_UAB, + "measure" : True, + "mqtt" : "Uab", + }, + { + "name": "Ubc", + "unit": "V", + "addr": "8154", + "format": "%05.0f", + "remap_addr": remap.COMM_REMAP_MEASURE_FLOAT_UBC, + "measure" : True, + "mqtt" : "Ubc", + }, + { + "name": "Uca", + "unit": "V", + "addr": "8155", + "format": "%05.0f", + "remap_addr": remap.COMM_REMAP_MEASURE_FLOAT_UCA, + "measure" : True, + "mqtt" : "Uca", + }, + { + "name": "Uo", + "unit": "V", + "addr": "8152", + "format": "%05.1f", + "remap_addr": remap.COMM_REMAP_MEASURE_FLOAT_UO, + "measure" : True, + "mqtt" : "Uo", + }, + { + "name": "Io", + "unit": "mA", + "addr": "8151", + "format": "%05.1f", + "scale" : 1.0, + "remap_addr" : remap.COMM_REMAP_MEASURE_FLOAT_IO, + "measure" : True, + "mqtt" : "Io", + }, + { + "name": "R", + "unit": "K", + "addr": "8150", + "scale": 0.1, + "format": "%05.1f", + "remap_addr": remap.COMM_REMAP_MEASURE_FLOAT_RO, + "measure" : True, + "mqtt" : "R", + }, + { + "name": "合闸次数", + "addr": "8169", + "format": "%06.0f", + "property": MENU_PROPERTY_READ_ONLY, + "mqtt" : "OnCount", + }, + { + "name": "Cosa", + "addr": "8172#s", + "scale": 0.01, + "format": "%04.2f", + "measure" : True, + "mqtt" : "Cosa", + }, + { + "name": "Cosb", + "addr": "8171#s", + "scale": 0.01, + "format": "%04.2f", + "measure" : True, + "mqtt" : "Cosb", + }, + { + "name": "Cosc", + "addr": "8170#s", + "scale": 0.01, + "format": "%04.2f", + "measure" : True, + "mqtt" : "Cosc", + }, + { + "name": "Cosf", + "addr": "8171#s", + "scale": 0.01, + "format": "%04.2f", + "measure" : True, + "call" : func_caculate_Cosf, + }, + { + "name": "AngleUoIo", + "addr": "8149", + "format": "%04.2f", + "measure" : True, + "mqtt" : "AngleUoIo", + }, + { + "name": "A相上", + "addr": "8056#s", + "format": "%05.1f", + "measure" : True, + "mqtt" : "Aup", + }, + + { + "name": "B相上", + "addr": "8057#s", + "format": "%05.1f", + "measure" : True, + "mqtt" : "Bup", + }, + { + "name": "C相上", + "addr": "8058#s", + "format": "%05.1f", + "measure" : True, + "mqtt" : "Cup", + }, + { + "name": "A相下", + "addr": "8059#s", + "format": "%05.1f", + "measure" : True, + "mqtt" : "Adown", + }, + { + "name": "B相下", + "addr": "8060#s", + "format": "%05.1f", + "measure" : True, + "mqtt" : "Bdown", + }, + { + "name": "C相下", + "addr": "8061#s", + "format": "%05.1f", + "measure" : True, + "mqtt" : "Cdown", + }, +] + +mqtt_pack_alarm = [ + { + "name": "AlarmCode", + "addr": "8121", + "alias": "alias_alarm", + "mqtt" : "AlarmCode", + }, + { + "name": "AlarmTest", + "addr": "8138", + "alias": "alias_alarm_type", + "format" : "%1.0f", + "mqtt" : "AlarmTest", + }, + { + "name": "Ua", + "addr": "8122", + "format" : "%1.0f", + "mqtt" : "AlarmUa", + }, + { + "name": "Ub", + "addr": "8123", + "format" : "%1.0f", + "mqtt" : "AlarmUb", + }, + { + "name": "Uc", + "addr": "8124", + "format" : "%1.0f", + "mqtt" : "AlarmUc", + }, + { + "name": "Ia", + "addr": "8125", + "format" : "%1.0f", + "mqtt": "AlarmIa", + "adjust" : func_adjust_current_menu_item, + }, + { + "name": "Ib", + "addr": "8126", + "format" : "%1.0f", + "mqtt": "AlarmIb", + "adjust" : func_adjust_current_menu_item, + }, + { + "name": "Ic", + "addr": "8127", + "format" : "%1.0f", + "mqtt": "AlarmIc", + "adjust" : func_adjust_current_menu_item, + }, + { + "name": "Ro", + "addr": "8128", + "format" : "%5.1f", + "scale" : 0.1, + "mqtt": "AlarmRo", + }, + { + "name": "Io", + "addr": "8129", + "format" : "%1.1f", + "scale" : 0.1, + "mqtt": "AlarmIo", + }, + { + "name": "Uo", + "addr": "8130", + "format" : "%5.0f", + "scale" : 1.0, + "mqtt": "AlarmUo", + }, + { + "name": "Year", + "addr": "8131", + "format" : "%4.0f", + "mqtt": "AlarmYear", + }, + { + "name": "Month", + "addr": "8132", + "format" : "%2.0f", + "mqtt": "AlarmMonth", + }, + { + "name": "Day", + "addr": "8133", + "format" : "%2.0f", + "mqtt": "AlarmDay", + }, + { + "name": "Hour", + "addr": "8134", + "format" : "%2.0f", + "mqtt": "AlarmHour", + }, + { + "name": "Minute", + "addr": "8135", + "format" : "%2.0f", + "mqtt": "AlarmMinute", + }, + { + "name": "Second", + "addr": "8136", + "format" : "%2.0f", + "mqtt": "AlarmSecond", + }, +] + +menu_data = [ + { + "name": "数据页面", + "sub_menu": "menu_curve_physical", + }, +] + +#曲线菜单 +menu_curve_physical = [ + { + "name": "物理曲线", + "property" : MENU_PROPERTY_READ_ONLY, + "action" : "Idle", + }, +] + +#曲线菜单 +menu_curve_adc = [ + { + "name": "ADC曲线", + "property" : MENU_PROPERTY_READ_ONLY, + "action" : "Idle", + }, +] + +#谐波菜单 +menu_curve_harmonic = [ + { + "name": "谐波曲线", + "property" : MENU_PROPERTY_READ_ONLY, + "action" : "Idle", + }, +] + +# modbus_comm_table = [ +# {"name":"alarm", "reg_addr":8120, "reg_count":25, "cycle": 0, "mqtt_pack": mqtt_pack_alarm}, +# {"name":"measure", "reg_addr":8000, "reg_count":62, "cycle": 500, "mqtt_pack": mqtt_video_trigger}, +# {"name":"measure", "reg_addr":8139, "reg_count":35, "cycle": 200, "mqtt_pack": mqtt_pack_measure}, +# {"name":"comm_cmd", "reg_addr":100618, "reg_count":32, "cycle": 100}, +# #{"name":"key_group", "reg_addr":1000, "reg_count":18, "cycle": 20}, +# # {"name":"para_group", "reg_addr":2000, "reg_count":32, "cycle": 0}, +# {"name":"time_group", "reg_addr":2300, "reg_count":6, "cycle": 1000}, +# {"name":"param/info", "reg_addr":8174, "reg_count":96, "cycle": 3000, "mqtt_pack" : mqtt_pack_param}, +# {"name":"param/info", "reg_addr":8180, "reg_count":60, "cycle": 3000}, +# {"name":"param/info", "reg_addr":8550, "reg_count":30, "cycle": 3000}, +# {"name":"param/info", "reg_addr":8435, "reg_count":10, "cycle": 3000}, +# {"name":"param/info", "reg_addr":2000, "reg_count":32, "cycle": 3000}, +# #{"name":"global_status", "reg_addr":3200, "reg_count":6, "cycle": 400}, +# ] +modbus_comm_table = [ + {"name":"alarm", "reg_addr":8120, "reg_count":25, "cycle": 0, "mqtt_pack": mqtt_pack_alarm}, + {"name":"measure", "reg_addr":8000, "reg_count":62, "cycle": 500, "mqtt_pack": mqtt_video_trigger}, + {"name":"measure", "reg_addr":8139, "reg_count":35, "cycle": 200, "mqtt_pack": mqtt_pack_measure}, + {"name":"comm_cmd", "reg_addr":100618, "reg_count":32, "cycle": 100}, + #{"name":"key_group", "reg_addr":1000, "reg_count":18, "cycle": 20}, + # {"name":"para_group", "reg_addr":2000, "reg_count":32, "cycle": 0}, + {"name":"time_group", "reg_addr":2300, "reg_count":6, "cycle": 1000}, + {"name":"param/info", "reg_addr":8174, "reg_count":96, "cycle": 3000, "mqtt_pack" : mqtt_pack_param}, + {"name":"param/info", "reg_addr":8180, "reg_count":60, "cycle": 3000}, + {"name":"param/info", "reg_addr":8490, "reg_count":10, "cycle": 3000}, + {"name":"param/info", "reg_addr":8520, "reg_count":20, "cycle": 3000}, + {"name":"param/info", "reg_addr":8550, "reg_count":30, "cycle": 3000}, + {"name":"param/info", "reg_addr":8435, "reg_count":10, "cycle": 3000}, + {"name":"param/info", "reg_addr":8420, "reg_count":10, "cycle": 3000}, + {"name":"param/info", "reg_addr":2000, "reg_count":32, "cycle": 3000}, + #{"name":"global_status", "reg_addr":3200, "reg_count":6, "cycle": 400}, +] + +def func_comm_table_trigger_check(object_comm_table, comm_table_item) : + if not hasattr(comm_table_item, "name"): + return False + return False + +menu_current = [ #菜单表[menu_current],存储菜单项属性字典<> + { + "name": "额定电流", + "unit": "A", + "addr": "8200", + "action": "rate_current", + "adjust" : func_adjust_current_menu_item, + }, + { + "name": "短路倍数", + "unit": "Ie", + "scale" : 0.01, + "format": "%05.2f", + "addr": "8201", + "min" : 1.0, + "max" : 10.0, + }, + { + "name": "短路延时", + "unit": "ms", + "scale" : 1.0, + "format": "%03.0f", + "addr": "8198", + "min" : 0.0, + "max" : 200.0, + #"mqtt" : "ShortDelay", + }, + { + "name": "互感器类型", + "alias" : Alias.alias_sensor, + "addr": "8207", + }, + # { + # "name": "反时限表", + # "alias" :"alias_table", + # "addr": "8178", + # }, + { + "name": "不平衡值", + "unit": "%", + "addr": "8213", + "scale" : 1.0, + "format": "%03.0f", + "min" : 10.0, + "max" : 100.0, + #"mqtt" : "UnbalanceLevel", + }, + { + "name": "不平衡延时", + "unit": "s", + "addr": "8197", + "scale" : 0.1, + "format": "%04.1f", + #"mqtt" : "UnbalanceDelay", + }, + { + "name": "B相电流", + "alias": "alias_current", + "addr": "8244", + }, + { + "name": "Ia 校正", + "unit": "%", + "scale" : 0.1, + "format": "%05.1f", + "addr": "8217", + "min" : 60.0, + "max" : 130.0, + #"mqtt" : "CorrectIa", + }, + { + "name": "Ib 校正", + "unit": "%", + "scale" : 0.1, + "format": "%05.1f", + "addr": "8218", + "min" : 60.0, + "max" : 130.0, + #"mqtt" : "CorrectIb", + }, + { + "name": "Ic 校正", + "unit": "%", + "scale" : 0.1, + "format": "%05.1f", + "addr": "8219", + "min" : 60.0, + "max" : 130.0, + #"mqtt" : "CorrectIc", + }, + { + "name": "退出", + "action": "exit", + }, +] + +menu_voltage = [ + { + "name": "额定电压", + "addr": "8211", + "alias": "alias_voltage", + "default": 0, + }, + { + "name": "欠压比值", + "unit": "%", + "addr": "8202", + "scale" : 0.1, + "format": "%05.1f", + "min" : 60.0, + "max" : 100.0, + #"mqtt" : "PercentLowVoltage", + }, + { + "name": "欠压延时", + "unit": "s", + "addr": "8177", + "scale" : 0.01, + "format": "%05.2f", + "min" : 0.1, + "max" : 60.0, + #"mqtt" : "DelayLowVoltage", + }, + { + "name": "过压比值", + "unit": "%", + "addr": "8224", + "scale" : 0.1, + "format": "%05.1f", + "min" : 100.0, + "max" : 150.0, + #"mqtt" : "PercentHighVoltage", + }, + { + "name": "过压延时", + "unit": "s", + "addr": "8176", + "scale" : 0.01, + "format": "%05.2f", + "min" : 0.1, + "max" : 60.0, + #"mqtt" : "DelayHighVoltage", + }, + { + "name": "Ua校正", + "unit": "%", + "addr": "8220", + "scale" : 0.1, + "format": "%05.1f", + "min" : 60.0, + "max" : 130.0, + #"mqtt" : "CorrectUa", + }, + { + "name": "Ub校正", + "unit": "%", + "addr": "8221", + "scale" : 0.1, + "format": "%05.1f", + "min" : 60.0, + "max" : 130.0, + #"mqtt" : "CorrectUb", + }, + { + "name": "Uc校正", + "unit": "%", + "addr": "8222", + "scale" : 0.1, + "format": "%05.1f", + "min" : 60.0, + "max" : 130.0, + #"mqtt" : "CorrectUc", + }, + { + "name": "Us校正", + "unit": "%", + "addr": "8214", + "scale" : 0.1, + "format": "%05.1f", + "min" : 60.0, + "max" : 130.0, + #"mqtt" : "CorrectUs", + }, + { + "name": "退出", + "action": "exit", + }, +] + +menu_alarm_display = [ + { + "name": "当前故障", + "action" : "Idle", + }, +] + +menu_alarm_curve = [ + { + "name": "录波曲线", + "action" : "Idle", + }, +] + +menu_alarm_history = [ + { + "name": "故障查询", + "action" : "Idle", + }, +] + +menu_alarm = [ + { + "name": "故障查询", + "sub_menu": "menu_alarm_history", + }, + { + "name": "故障清除", + "action": "alarm_clear", + }, + { + "name": "合闸清零", + "action": "clear_oncount", + }, + { + "name": "合闸次数", + "addr": "8169", + "format": "%05.0f", + "property": MENU_PROPERTY_READ_ONLY, + #"mqtt" : "OnCount", + }, + { + "name": "电量清除", + "action": "clear_kwh", + }, + { + "name": "累计时间清除", + "action": "clear_time", + }, + { + "name": "累计合闸时间", + "addr": "8016#2", + "unit": "Min", + "scale": 0.0166666666666667, + "format": "%07.0f", + "property": MENU_PROPERTY_READ_ONLY, + }, + { + "name": "退出", + "action": "exit", + }, +] + +menu_test = [ + { + "name": "短路试验", + "action": "test", + "addr" : "100628", + "mqtt" : "TestShort", + }, + { + "name": "漏电试验", + "action": "test", + "addr" : "100629", + "mqtt" : "TestIsolation", + }, + { + "name": "过压试验", + "action": "test", + "addr" : "100641", + "mqtt" : "TestOverVoltage", + }, + { + "name": "电流断相试验", + "action": "test", + "addr" : "100642", + "mqtt" : "TestBreak", + }, + + { + "name": "复归", + "action": "test", + "addr" : "100649", + "mqtt" : "TestRecover", + }, + + { + "name": "漏电试验配置", + "alias" : "alias_test", + "addr": "8265", + }, + { + "name": "退出", + "action": "exit", + }, +] + +menu_protect_set = [ + { + "name": "短路保护", + "alias": Alias.alias_bool, + "addr": "8192.1", + #"mqtt" : "ProtectShort", + }, + { + "name": "不平衡保护", + "alias": "alias_bool", + "addr": "8192.2", + #"mqtt" : "ProtectUnbalance", + }, + { + "name": "反时限过流", + "alias": "alias_bool", + "addr": "8192.3", + #"mqtt" : "ProtectOverCurrent", + }, + { + "name": "过压保护", + "alias": "alias_bool", + "addr": "8174.0", + #"mqtt" : "ProtectHighVotage", + }, + { + "name": "欠压保护", + "alias": "alias_bool", + "addr": "8174.1", + #"mqtt" : "ProtectLowVotage", + }, + # { + # "name": "电压断相", + # "alias": "alias_bool", + # "addr": "8174.2", + # }, + # { + # "name": "相序保护", + # "alias": "alias_bool", + # "addr": "8174.3", + # }, + { + "name": "漏电闭锁", + "alias": "alias_bool", + "addr": "8192.10", + #"mqtt" : "ProtectIsolationLock", + }, + { + "name": "漏电动作", + "alias": "alias_bool", + "addr": "8174.6", + #"mqtt" : "ProtectIsolationAction", + }, + # { + # "name": "选漏保护", + # "alias": "alias_bool", + # "addr": "8174.7", + # }, + { + "name": "风电闭锁", + "alias": "alias_bool", + "addr": "8175.0", + #"mqtt" : "ProtectWindMotor", + }, + { + "name": "瓦斯闭锁", + "alias": "alias_bool", + "addr": "8174.13", + #"mqtt" : "ProtectGasLock", + }, + { + "name": "退出", + "action": "exit", + }, +] + +menu_comm_set = [ + { + "name": "485地址", + "addr": "8212", + "format": "%03.0f", + }, + { + "name": "485波特率", + "alias": "alias_baud", + "addr": "8188", + }, + { + "name": "485校验", + "alias": "alias_parity", + "addr": "8194", + }, + # { + # "name": "CAN地址", + # }, + # { + # "name": "CAN波特率", + # "alias": "alias_can_baud", + # }, + { + "name": "IP设置", + "addr": "8271#2", + "action": "ip", + }, + { + "name": "IP网关", + "addr": "8273#2", + "action": "ip", + }, + { + "name": "IP掩码", + "addr": "8275#2", + "action": "ip", + }, + { + "name": "退出", + "action": "exit", + }, +] + +menu_system = [ + { + "name": "工作模式", + "addr": "8231", + "alias": "alias_work_mode", + "property": MENU_PROPERTY_ENGINEER, + "action" : "Visible_CL", #磁力模式显示菜单 + }, + { + "name": "开关类型", + "alias": "alias_switch_type", + "addr": "8210", + "action" : "Visible_KD", #馈电模式显示菜单 + }, + { + "name": "控制模式", + "alias": "alias_remote", + "addr": "8262", + }, + { + "name": "瓦斯触点", + "alias": "alias_input", + "addr": "8190", + }, + { + "name": "瓦斯延时", + "unit": "S", + "addr": "8191", + "scale": 0.01, + "format": "%05.2f", + }, + { + "name": "风电触点", + "alias": "alias_input", + "addr": "8248", + }, + { + "name": "风电延时", + "unit": "S", + "addr": "8234", + "scale": 0.01, + "format": "%05.2f", + }, + { + "name": "高水位延时", + "addr": "8267", + "unit": "S", + "scale": 0.1, + "format": "%04.1f", + "action" : "Visible_CL_WATER", #水位磁力模式显示 + }, + { + "name": "正反转延时", + "addr": "8266", + "unit": "ms", + "scale": 1.0, + "format": "%04.0f", + "action" : "Visible_CL_INV", #正反转磁力模式显示 + }, + { + "name": "维修模式", + "addr": "8260", + "alias": "alias_bool", + }, + # { + # "name": "温度设定", + # "unit": "℃", + # "addr": "2030", + # "format": "%+03.0f", + # }, + { + "name": "系统日期", + "addr": "2300#3", + "action": "date", + }, + { + "name": "当前时间", + "addr": "2303#3", + "action": "time", + }, + { + "name": "软件版本", + "addr": "8180", + "unit" : VERSION_DATE, + "scale": 0.01, + "format": "%.2f", + "property": MENU_PROPERTY_READ_ONLY, + }, + { + "name": "系统密码", + "addr": "8181", + "format": "%04.0f", + "action": "password", + }, + { + "name": "Us相位", + "alias": "alias_us_phase", + "addr": "8269", + }, + { + "name": "重启", + "action": "restart", + }, + { + "name": "退出", + "action": "exit", + }, +] + +menu_switch_cmd = [ + { + "name": "通讯合闸", + "action": "write_bit", + "addr" : "100618", + "mqtt" : "SwitchOn", + }, + { + "name": "通讯分闸", + "action": "write_bit", + "addr" : "100619", + "mqtt" : "SwitchOff", + }, + { + "name": "通讯复位", + "action": "write_bit", + "addr" : "100620", + "mqtt" : "SwitchRst", + }, + { + "name": "小车进控制", + "addr" : "100636", + "mqtt" : "CarForward", + }, + { + "name": "小车退控制", + "addr" : "100637", + "mqtt" : "CarRetreat", + }, + +] + +memu_setting = [ + { + "name": "电流设置", + "sub_menu": Menu.menu_current, + }, + { + "name": "电压设置", + "sub_menu": "menu_voltage" + }, + { + "name": "漏电设置", + "sub_menu": "menu_leak" + }, + { + "name": "系统设置", + "sub_menu": "menu_system" + }, + { + "name": "保护设置", + "sub_menu": "menu_protect_set" + }, + { + "name": "通讯设置", + "sub_menu": "menu_comm_set" + }, + { + "name": "保护试验", + "sub_menu": Menu.menu_test #"menu_test" + }, + { + "name": "故障查询", + "sub_menu": "menu_alarm" + }, + { + "name": "退出", + "action": "exit", + }, +] + +menu_leak = [ + { + "name": "漏电延时", + "addr": "8205", + "unit": "ms", + "scale" : 1.0, + "format" : "%03.0f", + "action" : "Visible_KD_GB", #馈电, 高爆模式所需菜单 + }, + { + "name": "零序电流", + "addr": "2007", + "unit": "mA", + "scale" : 0.1, + "format" : "%05.1f", + #"mqtt" : "KD.Io", + "action" : "Visible_KD", #馈电模式所需菜单 + }, + { + "name": "零序电压", + "addr": "2008", + "unit": "V", + "scale" : 1.0, + "format" : "%04.0f", + #"mqtt" : "KD.Uo", + "action" : "Visible_KD", #馈电模式菜单有效 + }, + { + "name": "零序电流", + "addr": "2007", + "unit": "A", + "scale" : 0.01, + "format" : "%05.2f", + "mqtt" : "GB.Io", + "action" : "Visible_GB", #高爆模式菜单有效 + }, + { + "name": "零序电压", + "addr": "2008", + "unit": "V", + "scale" : 1.0, + "format" : "%05.2f", + "mqtt" : "GB.Uo", + "action" : "Visible_GB", #高爆模式菜单有效 + }, + { + "name": "Uo校正值", + "addr": "8187", + "unit": "%", + "scale" : 0.1, + "format" : "%05.1f", + "action" : "Visible_KD_GB", #馈电与高爆模式菜单有效 + }, + { + "name": "Io校正值", + "addr": "8196", + "unit": "%", + "scale" : 0.1, + "format" : "%05.1f", + "action" : "Visible_KD_GB", #馈电与高爆模式菜单有效 + }, + { + "name": "闭锁电阻", + "addr": "8204", + "unit": "K", + "scale" : 0.1, + "format" : "%05.1f", + "action" : "Hide_GB", #高爆模式下隐藏菜单 + }, + { + "name": "串联电阻", + "addr": "8236", + "unit": "K", + "scale" : 0.1, + "format" : "%05.1f", + }, + { + "name": "漏电投入延时", + "addr": "8252", + "unit": "S", + "scale" : 0.1, + "format" : "%04.1f", + "action" : "Hide_GB", #高爆模式下隐藏菜单 + "default": 3.0 + }, + { + "name": "检测电压", + "addr": "8253", + "alias": Alias.alias_measure_v, + }, + { + "name": "退出", + "action": "exit", + }, +] + +calibrate = [ + { + "name": "999校正", + "action": "calibrate999", + "mqtt" : "Calibrate999", + }, + { + "name": "0欧校正", + "action": "calibrate0", + "mqtt" : "Calibrate0", + }, + { + "name": "以太网模块", + "alias": Alias.alias_eth, + "addr" : "8280", + }, + { + "name": "LCD模块", + "alias": Alias.alias_lcd, + "addr" : "8281", + }, + { + "name": "防越级模块", + "alias": Alias.alias_fyj, + "addr" : "8282", + }, + { + "name": "设备型号", + "alias": Alias.alias_dev, + "addr" : "8270", + }, + { + "name": "退出", + "action": "exit", + }, +] + +startup = [ + { + "name": "退出", + }, +] + +menu_main = [ + { + "name": "设置", + "sub_menu": "memu_setting", + }, + { + "name": "数据", + "sub_menu": "menu_data", + }, + { + "name": "报警", + "sub_menu": "menu_alarm_display", + }, + { + "name": "标定", + "sub_menu": "calibrate", + "property": MENU_PROPERTY_ENGINEER, + }, +] + +mqtt_topic_subscribe = None + +MENU_STARTUP_PAGE = 150 +MENU_DISPLAY_PAGE = 2 +MENU_ALARM_PAGE = 3 +MENU_DATA_PAGE = 120 +MENU_CL_MAIN_PAGE = 90 +MENU_KD_MAIN_PAGE = 20 +MENU_KD_MAIN_PAGE_LANGUAGE = 21 +MENU_CURVE_PAGE = 10 +MENU_ADC_CURVE_PAGE = 18 +MENU_HARNOMIC_CURVE_PAGE = 19 + +menu_caption = [ + {"name" : "startup", "menu" : startup, "caption": "启动页面", "page" : MENU_STARTUP_PAGE, "max_items" : 1}, + {"name" : "menu_main", "menu" : menu_main, "caption": "主菜单", "page" : MENU_KD_MAIN_PAGE, "max_items" : 4}, + {"name" : "menu_leak", "menu" : menu_leak, "caption": "漏电设置", "page" : MENU_DISPLAY_PAGE, "max_items" : 8}, + {"name" : "memu_setting", "menu" : memu_setting, "caption": "参数设置", "page" : MENU_DISPLAY_PAGE, "max_items" : 8}, + {"name" : "menu_current", "menu" : menu_current, "caption": "电流设置", "page" : MENU_DISPLAY_PAGE, "max_items" : 8}, + {"name" : "menu_voltage", "menu" : menu_voltage, "caption": "电压设置", "page" : MENU_DISPLAY_PAGE, "max_items" : 8}, + {"name" : "menu_system", "menu" : menu_system, "caption": "系统设置", "page" : MENU_DISPLAY_PAGE, "max_items" : 8}, + {"name" : "menu_protect_set", "menu" : menu_protect_set, "caption": "保护设置", "page" : MENU_DISPLAY_PAGE, "max_items" : 8}, + {"name" : "menu_comm_set", "menu" : menu_comm_set, "caption": "通讯设置", "page" : MENU_DISPLAY_PAGE, "max_items" : 8}, + {"name" : "menu_alarm", "menu" : menu_alarm, "caption": "故障查询", "page" : MENU_DISPLAY_PAGE, "max_items" : 8}, + {"name" : "menu_alarm_display", "menu" : menu_alarm_display, "caption": "当前故障", "page" : MENU_ALARM_PAGE, "prev": menu_alarm_curve, "next": menu_alarm_curve, "max_items" : 1}, + {"name" : "menu_alarm_curve", "menu" : menu_alarm_curve, "caption": "曲线故障", "page" : MENU_ADC_CURVE_PAGE, "prev": menu_alarm_display, "next": menu_alarm_display, "max_items" : 1}, + + {"name" : "menu_alarm_history", "menu" : menu_alarm_history, "caption": "故障查询", "page" : MENU_ALARM_PAGE, "max_items" : 1}, + {"name" : "menu_test", "menu" : menu_test, "caption": "保护试验", "page" : MENU_DISPLAY_PAGE, "max_items" : 8}, + {"name" : "mqtt_pack_alarm", "menu" : mqtt_pack_alarm, "caption": "报警数据", "page" : MENU_DISPLAY_PAGE, "max_items" : 8}, + {"name" : "mqtt_pack_measure", "menu" : mqtt_pack_measure, "caption": "测量数据", "page" : MENU_DISPLAY_PAGE, "max_items" : 8}, + {"name" : "menu_data", "menu" : menu_data, "caption": "数据页面", "page" : MENU_DATA_PAGE, "max_items" : 8}, + {"name" : "menu_curve_physical", "menu" : menu_curve_physical, "caption": "物理曲线", "page" : MENU_CURVE_PAGE, "prev": menu_curve_harmonic, "next": menu_curve_adc, "max_items" : 8}, + {"name" : "menu_curve_adc", "menu" : menu_curve_adc, "caption": "ADC曲线", "page" : MENU_ADC_CURVE_PAGE, "prev": menu_curve_physical, "next": menu_curve_harmonic, "max_items" : 8}, + {"name" : "menu_curve_harmonic", "menu" : menu_curve_harmonic, "caption": "谐波曲线", "page" : MENU_HARNOMIC_CURVE_PAGE, "prev": menu_curve_adc, "next": menu_curve_physical, "max_items" : 8}, + {"name" : "calibrate", "menu" : calibrate, "caption": "标定页面", "page" : MENU_DISPLAY_PAGE, "max_items" : 8}, + {"name" : "menu_switch_cmd", "menu" : menu_switch_cmd, "caption": "通讯操作", "page" : MENU_DISPLAY_PAGE, "max_items" : 8}, + {"name" : "mqtt_pack_param", "menu" : mqtt_pack_param, "caption": "参数刷新", "page" : MENU_DISPLAY_PAGE, "max_items" : 8}, + {"name" : "mqtt_video_trigger", "menu" : mqtt_video_trigger, "caption": "视频触发", "page" : MENU_DISPLAY_PAGE, "max_items" : 8}, + + {"name" : "mqtt_time_update", "menu" : mqtt_time_update, "caption": "时钟设置", "page" : MENU_DISPLAY_PAGE, "max_items" : 8}, +] + + +# mqtt 故障查询函数 +def func_alarm_query(device : class_modbus_comm_device, alarm_index = 0) : + result = device.protocol.modbus_write_regs(device.comm_addr, 8120, 1, alarm_index) + time.sleep(0.05) + alarm_regs = device.protocol.modbus_read_regs(device.comm_addr, 8121, 20) + if alarm_regs != None : + device.modbus_regs_flush(8121, 20, alarm_regs) + + msg_alarm_dict = device.comm_item_trigger_mqtt_pack(mqtt_pack_alarm) + + err_code = utils.dict_or_object_get_attr(msg_alarm_dict, "AlarmCode", 0) + err_code = int(err_code) + test_code = utils.dict_or_object_get_attr(msg_alarm_dict, "AlarmTest", 0) + test_code = int(test_code) + + + test_value_str = get_alias_value_str("alias_alarm_type", test_code) + if test_value_str == None: + test_value_str = " " + err_code_str = get_alias_value_str("alias_alarm", err_code) + if err_code_str == None: + err_code_str = " " + msg_alarm_dict["AlarmCodeMsg"] = err_code_str + msg_alarm_dict["AlarmTestMsg"] = test_value_str + msg_alarm_dict["index"] = alarm_index + msg : str = json.dumps(msg_alarm_dict, ensure_ascii=False) + + if device.mqtt_thread : + device.mqtt_thread.publish("response/alarm/" + device.unique_name, msg) + +# mqtt action处理函数 +def func_action_process(device : class_modbus_comm_device, menu_item = None, value_str : str = None) : + action_str = utils.dict_or_object_get_attr(menu_item, "action", " ") + mqtt_name = utils.dict_or_object_get_attr(menu_item, "mqtt", " ") + result = None + + if action_str == "SetupTime" : #设置保护器时间 + numbers = re.findall(r'\d+', value_str) + if len(numbers) == 6 : + year = int(numbers[0]) + month = int(numbers[1]) + day = int(numbers[2]) + hour = int(numbers[3]) + minute = int(numbers[4]) + second = int(numbers[5]) + device.protocol.modbus_write_regs(device.comm_addr, 2300, 6, (year, month, day, hour, minute, second)) + pass + if action_str == "calibrate999" : + result = device.protocol.modbus_write_regs(device.comm_addr, 8255, 0x2007) + if action_str == "calibrate0" : + result = device.protocol.modbus_write_regs(device.comm_addr, 8255, 0x2006) + if action_str == "write_bit" or action_str == "test": + comm_str = utils.dict_or_object_get_attr(menu_item, "addr", " ") + reg_addr, bit, reg_count = utils.comm_str_unpack(comm_str) + if reg_count == 1: + command_value = [1] + result = device.write_bit_register(reg_addr, command_value) + + if device.mqtt_thread and result != None: + device.mqtt_thread.publish("response/action/" + device.unique_name, '{"name" : "%s", "result" : %d}'%(mqtt_name, result)) + + +class class_menu_comm_device_config(class_comm_device_config): + def __init__(self): + pass + + #@override get_alias_table + def get_alias_table(self) : + return alias_table + + #@override get_comm_table + def get_comm_table(self) : + return modbus_comm_table, func_comm_table_trigger_check + + #@override get_menu_top + def get_menu_top(self) : + return menu_main + + def get_menu_caption_info(self) : + return menu_caption + + #@override get_mqtt_table + def get_mqtt_table(self) : + return mqtt_topic_subscribe + + def is_gb2312(self) : + return False + +#comm_device_config的名字不要更改, 创建通讯配置类 +comm_device_config = class_menu_comm_device_config() diff --git a/menu_page.py b/menu_page.py new file mode 100644 index 0000000..bb8b38e --- /dev/null +++ b/menu_page.py @@ -0,0 +1,6 @@ +#昆仑通态组合页面设计 + +KUNLUN_GRAOUP_PAGE_MAIN = 20 #主选择菜单页面 +KUNLUN_GRAOUP_PAGE_MENU = 5 #文字菜单页面 +KUNLUN_GRAOUP_PAGE_ALARM = 7 #报警页面 +KUNLUN_GRAOUP_PAGE_DEVICE_KD = 21 #馈电页面 diff --git a/menu_utils.py b/menu_utils.py new file mode 100644 index 0000000..e85dd43 --- /dev/null +++ b/menu_utils.py @@ -0,0 +1,109 @@ +import re +import sys +import inspect +import enum as Enum + +def find_variable_name(value): + frame = inspect.currentframe() + try: + for var_name, var_value in frame.f_back.f_locals.items(): + if var_value is value: + return var_name + finally: + del frame + + +def dict_or_object_get_attr(dict_or_object, name, default_attr) : + if dict_or_object == None or name == None: + return default_attr + + if type(dict_or_object) == dict : + if name in dict_or_object.keys(): + return dict_or_object[name] + return default_attr + + return getattr(dict_or_object, name, default_attr) + +def value_u64_to_s64(value) : + svalue = value + if (svalue >> 63) & 0x1 : + svalue = -((svalue ^ 0xFFFFFFFFFFFFFFFF) + 1) + return svalue + +def value_u32_to_s32(value) : + svalue = value + if (svalue & 0x80000000) : + svalue = -((svalue ^ 0xFFFFFFFF) + 1) + return svalue + +def value_u16_to_s16(value) : + svalue = value + if (svalue & 0x8000) : + svalue = -((svalue ^ 0xFFFF) + 1) + return svalue + +''' +正则表达式 +^:匹配字符串开头。 +$:匹配字符串结尾。 +.:匹配任意字符。 +*:匹配前面的字符零次或多次。 ++:匹配前面的字符一次或多次。 +?:匹配前面的字符零次或一次。 +[]:匹配括号中列举的任意一个字符。 +[^]:匹配除了括号中列举的字符以外的任意一个字符。 +():标记一个子表达式的开始和结束位置。 +\s: 包含空格 +\S: 不包含空格 +\d: 包含数字 +\D: 不包含数字 +''' +def comm_str_unpack(comm_str) : + pattern_bit_regs = "(\d+)\.(\d+)((\s*)#(\s*)(\d+))*" + pattern_regs = "(\d+)((\s*)#(\s*)(\d+))*" + + reg_addr = 0 + reg_count = 0 + bit = -1 + + bit_reg_result = None + reg_result = None + + if comm_str == None : + return reg_addr, bit, reg_count + + #[8000.2 # 3] or [8000.2] + bit_reg_result = re.search(pattern_bit_regs, comm_str) + if bit_reg_result : + match_str = bit_reg_result.string + bit_result_value = re.findall('\d+', match_str) + item_count = len(bit_result_value) + + if item_count >= 2 : + reg_addr = int(bit_result_value[0]) + bit = int(bit_result_value[1]) + if item_count == 3: + reg_count = int(bit_result_value[2]) + else : + reg_count = 1 + + #[8000 # 2] or [8000] + reg_result = None + if bit_reg_result == None: + reg_result = re.search(pattern_regs, comm_str) + + if reg_result != None: + match_str = reg_result.string + reg_result_value = re.findall('\d+', match_str) + item = len(reg_result_value) + + if item >= 1 : + reg_addr = int(reg_result_value[0]) + + if item == 2 : + reg_count = int(reg_result_value[1]) + else : + reg_count = 1 + + return reg_addr, bit, reg_count + diff --git a/mqtt_device.py b/mqtt_device.py new file mode 100644 index 0000000..84ea794 --- /dev/null +++ b/mqtt_device.py @@ -0,0 +1,218 @@ +import threading +import time +import string +from threading import Thread +import menu_utils as utils +import paho.mqtt.client as mqtt +from print_color import * + + +#mqtt 消息处理基类, mqtt消息处理 +class class_comm_mqtt_interface : + def __init__(self, name = "mqtt handler"): + self.name = name + + def on_message(self, mqtt_thread, topic, message) : + return + + def on_connect(self, mqtt_thread, userdata, flags, rc) : + return + +#mqtt服务器连接处理 +def mqtt_on_connect(client, userdata, flags, rc): + if rc == 0: + # 连接成功 + print_normal_msg("Mqtt 服务器连接成功") + elif rc == 1: + # 协议版本错误 + print_error_msg("协议版本错误") + elif rc == 2: + # 无效的客户端标识 + print_error_msg("无效的客户端标识") + elif rc == 3: + # 服务器无法使用 + print_error_msg("Mqtt 服务器无法使用") + elif rc == 4: + # 错误的用户名或密码 + print_error_msg("错误的用户名或密码") + elif rc == 5: + # 未经授权 + print_error_msg("未经授权") + + #获取线程类 (class_comm_mqtt_thread) + mqtt_thread : class_comm_mqtt_thread= userdata + unique_object : class_comm_mqtt_interface = None + + #遍历所有unique_object, 通知其 on_connect 事件 + for unique_object in mqtt_thread.unique_object_dict.values() : + if isinstance(unique_object, list) : #unique_object 可能是列表, 所以需要遍历 + for each_object in unique_object: + if hasattr(each_object, "on_connect") : + each_object.on_connect(mqtt_thread, userdata, flags, rc) + elif hasattr(unique_object, "on_connect") : + unique_object.on_connect(mqtt_thread, userdata, flags, rc) + +#mqtt接收到主题, 通过主题中包含的 unique_name, 定位到 uniuqe_object, 并调用 unique_object.on_message 函数 +def mqtt_on_message(client, userdata, message): + #print("mqtt message:", message.topic, message.payload.decode("utf-8")) + + #获取线程类 (class_comm_mqtt_thread) + mqtt_thread : class_comm_mqtt_thread= userdata + + unique_object : class_comm_mqtt_interface = None + for unique_name, search_unique_object in mqtt_thread.unique_object_dict.items() : + if unique_name in message.topic : + unique_object = search_unique_object + break + + #mqtt线程本身无法处理对应的主题消息, 由mqtt 所对应的unique_object处理 + if unique_object != None : + if isinstance(unique_object, list) : #unique_object 可能是列表, 所以需要遍历 + for each_object in unique_object: + if hasattr(each_object, "on_message") : + each_object.on_message(mqtt_thread, message.topic, message.payload) + elif hasattr(unique_object, "on_message") : + unique_object.on_message(mqtt_thread, message.topic, message.payload) + +class class_comm_mqtt_thread(Thread): + def __init__(self, user_name = "admin", password = "admin") : + Thread.__init__(self) + self.client = None + + self.subscribe_list = [] + self.publish_list = [] + self.publish_list_lock = threading.Lock() + self.condition = threading.Condition() + self.stop_request = False + + self.server = "127.0.0.1" + self.port = 1883 + self.user_name = user_name + self.password = password + self.keepalive = 60 + + self.unique_object_dict = {} #key = unique_name, value = device_object + return + + #unique_object: 为unique_object_subscribe_name对应的object, 在回调 + def add_unique_object(self, unique_name : string, unique_object : class_comm_mqtt_interface) : + self.unique_object_dict[unique_name] = unique_object + + #topic 中 需要包含 + def subscribe(self, topic : string) : + if self.is_connect() and self.client: + if topic not in self.subscribe_list : + self.subscribe_list.append(topic) + self.client.subscribe(topic) + + def set_mqtt_server(self, server = "127.0.0.1", port = 1883, keep_alive = 60.0, user_name = "admin", password = "admin") : + self.server = server + self.port = port + self.user_name = user_name + self.password = password + self.keepalive = keep_alive + + #用于判断 mqtt_thread是否已经连接 + def is_connect(self) : + if self.client == None : + return False + return self.client.is_connected() + + def open(self): + try : + self.client = mqtt.Client() + self.client.user_data_set(self) + self.client.on_connect = mqtt_on_connect + self.client.on_message = mqtt_on_message + self.client.username_pw_set(self.user_name, self.password) + self.client.connect(self.server, self.port, self.keepalive) + except Exception as e : + print_error_msg(str(e) + "mqtt_server: ip=%s, port = %d"%(self.server, self.port)) + if self.client: # 如果self.client已经被初始化了,则断开连接 + self.client.disconnect() + self.client = None + return None # 添加返回语句,告知调用者连接失败 + return self.client + + def close(self): + self.stop_request = True + + def publish(self, topic, message) : + if self.is_connect : + self.publish_list_lock.acquire() + self.publish_list.append([topic, message]) + self.publish_list_lock.release() + + self.condition.acquire() + self.condition.notify() + self.condition.release() + return + + def publish_wait(self, topic, message, timeout) : + if self.is_connect : + self.publish_list_lock.acquire() + self.publish_list.insert(0, [topic, message]) + self.publish_list_lock.release() + self.condition.acquire() + self.condition.notify() + self.condition.release() + return + + def run(self): + while self.stop_request == False : + print_warning_msg("mqtt 线程启动, 服务器端口:server = %s:%d"%(self.server, self.port)) + + wait_open_timeout = 0 + self.subscribe_list = [] #取消订阅列表, 连接后重新订阅 + + while self.client == None and self.stop_request == False: + if wait_open_timeout == 0 : + print_warning_msg("mqtt连接中, 服务器端口:server = %s:%d"%(self.server, self.port)) + self.open() + + if self.client == None : + time.sleep(0.1) + wait_open_timeout += 0.1 + if wait_open_timeout >= 5.0 : + wait_open_timeout = 0 + + time.sleep(0.2) + + if self.client : + self.client.loop_start() + time.sleep(0.2) + + while self.stop_request == False and self.client.is_connected() : + topic = None + topic_message = None + + self.publish_list_lock.acquire() + if len(self.publish_list) > 0 : + topic_message = self.publish_list.pop(0) + topic = topic_message[0] + message = topic_message[1] + self.publish_list_lock.release() + + if topic != None and message != None : + self.client.publish(topic, message) + + self.condition.acquire() + if len(self.publish_list) == 0: + self.condition.wait(timeout = 0.1) + self.condition.release() + + self.client.loop_stop() + self.client.disconnect() + self.client = None + + print_warning_msg("mqtt连接断开, 准备重启mqtt连接") + print_error_msg("mqtt 线程停止") + +if __name__ == "__main__": + object_mqtt1 = class_comm_mqtt_interface("object1 handler") + object_mqtt2 = class_comm_mqtt_interface("object2 handler") + global_mqtt_thread = class_comm_mqtt_thread() + global_mqtt_thread.set_mqtt_server(server = "127.0.0.1", port = 1883, keep_alive = 60, user_name="admin", password="admin") + global_mqtt_thread.add_unique_object("object1", object_mqtt1) + global_mqtt_thread.add_unique_object("object2", object_mqtt2) + global_mqtt_thread.start() diff --git a/mqtt_object.py b/mqtt_object.py new file mode 100644 index 0000000..fbbd612 --- /dev/null +++ b/mqtt_object.py @@ -0,0 +1,102 @@ +import menu_utils as utils +import json +from mqtt_device import class_comm_mqtt_interface, class_comm_mqtt_thread +from device_conf import class_comm_device_config +import string +from enum import Enum +import math +from print_color import * + +#创建mqtt 消息属性字典, +class class_mqtt_info_object (class_comm_mqtt_interface): + def __init__(self, device_name): + class_comm_mqtt_interface.__init__(self) + self.mqtt_dict = {} + + device_config_info = __import__(device_name) + + self.device_config : class_comm_device_config = device_config_info.comm_device_config + self.menu_description = self.device_config.get_menu_caption_info() + + menu_process_list = [] + for item_dict in self.menu_description : + if "menu" in item_dict.keys() : + menu_object = item_dict["menu"] + self.__create_mqtt_dict__(menu_object, menu_process_list) + + #创建mqtt索引, 用于定位mqtt消息相关的菜单属性, 内部函数, 外部不要调用 + def __create_mqtt_dict__(self, menu_object, menu_process_list: list): #创建菜单对应的各项mqtt信息,包含菜单内部的子菜单的各项mqtt信息 + if menu_object == None : + return + if menu_object in menu_process_list : #防止递归进入无限循环 + return + + menu_process_list.append(menu_object) #添加到处理队列中,可以防止menu_object重复处理 + + for menu_item in menu_object : #遍历菜单内部的各个菜单项 + if "mqtt" in menu_item.keys() : #该项菜单具有mqtt属性 + mqtt_info_name = menu_item["mqtt"] #获取mqtt的信息名称, mqtt的消息中包含该信息时,可快速定位该 菜单项 + if mqtt_info_name not in self.mqtt_dict.keys() : #mqtt信息需要具有唯一性, + self.mqtt_dict[mqtt_info_name] = menu_item #把mqtt信息与 菜单项 进行(key, value)绑定. key = mqtt信息, value为菜单项 + else : + print_error_msg("Error, 菜单项有相同的mqtt字段(%s)"%(mqtt_info_name)) + + if "sub_menu" in menu_item.keys() : + sub_menu_name = menu_item["sub_menu"] + if isinstance(sub_menu_name, Enum): + sub_menu_name = sub_menu_name.name + + sub_menu_object = self.search_menu_group_object(sub_menu_name) + self.__create_mqtt_dict__(sub_menu_object, menu_process_list) #递归调用, 直到处理完所有子菜单项 + + #通过菜单名搜索到菜单项 + def search_menu_item(self, mqtt_info_name : string) : + if mqtt_info_name in self.mqtt_dict.keys() : + return self.mqtt_dict[mqtt_info_name] + else : + return None + + #通过菜单名字来查找菜单组对象, 每个菜单组包含若干个菜单项 + def search_menu_group_object(self, menu_group_name) : + menu_group_object = None + for item_dict in self.menu_description: + menu_item_name = utils.dict_or_object_get_attr(item_dict, "name", None) + if menu_item_name == menu_group_name : + menu_group_object = utils.dict_or_object_get_attr(item_dict, "menu", None) + break + return menu_group_object + + +#定义mqtt信息组:所有设备的mqtt信息 都集中于该类 +class class_mqtt_info_object_group(): + def __init__(self): + self.device_mqtt_info_dict : class_mqtt_info_object = {} + + def create_mqtt_info_object(self, device_name) ->class_mqtt_info_object: + try : + mqtt_info_object = None + + if device_name not in self.device_mqtt_info_dict.keys() : + new_mqtt_info = class_mqtt_info_object(device_name) + self.device_mqtt_info_dict[device_name] = new_mqtt_info + mqtt_info_object = self.get_mqtt_info_object(device_name) + except Exception as e: + print_error_msg(str(e)) + mqtt_info_object = None + finally : + return mqtt_info_object + + def get_mqtt_info_object(self, device_name) ->class_mqtt_info_object: + matched_mqtt_info = None + if device_name in self.device_mqtt_info_dict.keys() : + matched_mqtt_info = self.device_mqtt_info_dict[device_name] + + return matched_mqtt_info + + def search_mqtt_menu_item_info(self, device_name, mqtt_info_name) : + if mqtt_info_name in self.device_mqtt_info_dict.keys() : + mqtt_info_object : class_mqtt_info_object = self.device_mqtt_info_dict[device_name] + menu_item = mqtt_info_object.search_menu_item(mqtt_info_name) + return menu_item + return None + diff --git a/print_color.py b/print_color.py new file mode 100644 index 0000000..3738742 --- /dev/null +++ b/print_color.py @@ -0,0 +1,41 @@ +from colorama import init, Fore, Back, Style +from threading import Lock +import datetime + +init() # 初始化colorama +print_mutex = Lock() + +def print_normal_msg(*args, **kwargs) : + current_time = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S") + print_mutex.acquire() + print(current_time, *args, **kwargs, end='') + print(Style.RESET_ALL) + print_mutex.release() + +def print_warning_msg(*args, **kwargs) : + current_time = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S") + print_mutex.acquire() + print(current_time, Fore.LIGHTMAGENTA_EX, *args, **kwargs, end='') + print(Style.RESET_ALL) + print_mutex.release() + +def print_inform_msg(*args, **kwargs) : + current_time = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S") + print_mutex.acquire() + print(current_time, Fore.YELLOW, *args, **kwargs, end='') + print(Style.RESET_ALL) + print_mutex.release() + +def print_error_msg(*args, **kwargs) : + current_time = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S") + print_mutex.acquire() + print(current_time, Fore.RED, *args, **kwargs, end='') + print(Style.RESET_ALL) + print_mutex.release() + +def print_debug_msg(*args, **kwargs) : + current_time = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S") + print_mutex.acquire() + print(current_time, Fore.BLUE, *args, **kwargs, end='') + print(Style.RESET_ALL) + print_mutex.release() \ No newline at end of file