调整版本,实现wifi认证

This commit is contained in:
冯佳
2025-07-25 08:08:11 +08:00
parent 97a7d848d4
commit 50f7531518
20 changed files with 5323 additions and 429 deletions

2
1.py Normal file
View File

@ -0,0 +1,2 @@
from kivy.properties import ObjectProperty, StringProperty
# print("ok")

BIN
assets/_img.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 450 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.7 KiB

After

Width:  |  Height:  |  Size: 870 KiB

99
comm_device.py Normal file
View File

@ -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("<f", bytes)
return fvalue

625
comm_modbus_device.py Normal file
View File

@ -0,0 +1,625 @@
from comm_device import class_comm_device
from comm_thread import class_comm_master_thread
from mqtt_device import class_comm_mqtt_thread, class_comm_mqtt_interface
import time
import comm_device
from threading import Thread
import menu_utils as utils
import struct
import math
import json
import random
from enum import Enum
from comm_protocol import class_protocol
from mqtt_object import class_mqtt_info_object
from print_color import *
from comm_protocol_modbus import class_protocol_modbus
BASE_FAIL_DELAY = 1000 # 通信失败基础延迟(毫秒)
MAX_FAIL_DELAY = 30000 # 最大通信失败延迟(毫秒)
BASE_GLOBAL_FAIL_DELAY = 500 # 全局通信失败基础延迟(毫秒)
MAX_GLOBAL_FAIL_DELAY = 60000 # 最大全局通信失败延迟(毫秒)
DEFAULT_TIMEOUT = 20 # 默认超时时间(毫秒)
# MAX_FAIL_COUNT = 15 # 最大失败次数
class class_comm_table_item() :
def __init__(self, name = "", reg_addr = 0, reg_count = 0, cycle = 0, mqtt_pack = None, remap_addr = 0):
self.reg_addr = reg_addr
self.reg_count = reg_count
self.cycle = cycle
self.name = name
self.remain_cycle = 0
self.comm_fail_delay = 0
self.comm_count = 0
self.comm_fail_count = 0
self.comm_success_count = 0
self.comm_success = False
self.comm_trigger = False
self.mqtt_pack = mqtt_pack
self.remap_addr = remap_addr
self.flush_request_delay = 0
self.base_time = time.time()
self.reg_buf = []
self.comm_fail_delay = 0 # 通信失败延迟
def in_range(self, reg_addr, reg_count) :
reg_addr_begin = self.reg_addr
reg_addr_end = self.reg_addr + self.reg_count
if reg_addr >= 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 # 返回成功计数和总计数

45
comm_protocol.py Normal file
View File

@ -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()

656
comm_protocol_modbus.py Normal file
View File

@ -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("<f", bytes)
elif read_reg_count == 4:
low_part = read_value & 0xFFFFFFFF
low_bytes = struct.pack('BBBB', low_part & 0xFF, (low_part >> 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("<d", bytes)
else :
fvalue = float(read_value)
if fvalue != math.nan :
fvalue = fvalue * scale + offset
except Exception as e:
fvalue = math.nan #无效浮点数
print_error_msg(f"modbus读浮点数{str(e)} 读保持寄存器(addr={device_addr}, reg={reg_addr}, count={read_reg_count})")
finally:
return fvalue
def modbus_read_float_arr(self, device_addr, comm_str :string, count) : # -> 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("<f", bytes)
elif read_reg_count == 4:
low_part = read_value & 0xFFFFFFFF
low_bytes = struct.pack('BBBB', low_part & 0xFF, (low_part >> 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("<d", bytes)
else :
fvalue = float(read_value)
float_result_arr.append(fvalue)
except Exception as e:
float_result_arr = []
print_error_msg(f"modbus读浮点数数组{str(e)},读保持寄存器(addr={device_addr}, reg={reg_addr}, count={total_reg_count})")
finally:
return float_result_arr
def modbus_write_float(self, device_addr, comm_str, fvalue, scale = 1.0, offset = 0.0) -> 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 = "<d" if write_reg_count == 4 else "<f"
format_unpack = "HHHH" if write_reg_count == 4 else "HH"
bytes = struct.pack(format_pack, fvalue_origin)
write_reg_values = struct.unpack(format_unpack, bytes)
else :
modify_value = round(fvalue_origin)
write_reg_values = self.modbus_value_to_list(modify_value, write_reg_count, is_big_endian)
is_writing = True #以下真正开始写寄存器
self.modbus_write_regs(device_addr, reg_addr, write_reg_count, write_reg_values)
is_writing = False
success = True
except Exception as e:
success = False
if is_reading : #正在读时发送异常
print_error_msg(f"正在读浮点数时异常{str(e)}, 读保持寄存器(addr={device_addr}, reg={reg_addr}, count={write_reg_count})")
elif is_writing : #正在写时发送异常
print_error_msg(f"正在写浮点数时异常{str(e)}, 写保持寄存器(addr={device_addr}, reg={reg_addr}, count={write_reg_count})")
else :
print_error_msg(f"其他错误: {str(e)}")
finally:
return success
def modbus_write_float_arr(self, device_addr, comm_str, fvalue_arr) -> 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 = "<d" if write_reg_count == 4 else "<f"
format_unpack = "HHHH" if write_reg_count == 4 else "HH"
bytes = struct.pack(format_pack, fvalue_origin)
write_reg_values += struct.unpack(format_unpack, bytes)
else :
modify_value = round(fvalue_origin)
write_reg_values += self.modbus_value_to_list(modify_value, write_reg_count, is_big_endian)
is_writing = True #以下真正开始写寄存器
self.modbus_write_regs(device_addr, reg_addr, len(write_reg_values), write_reg_values)
is_writing = False
success = True
except Exception as e:
success = False
if is_read : #正在读时发送异常
print_error_msg(f"正在读浮点数时异常{str(e)}, 读保持寄存器(addr={device_addr}, reg={reg_addr}, count={write_reg_count})")
elif is_writing : #正在写时发送异常
print_error_msg(f"正在写浮点数时异常{str(e)}, 写保持寄存器(addr={device_addr}, reg={reg_addr}, count={write_reg_count})")
else :
print_error_msg(f"其他错误: {str(e)}")
finally:
return success
#@override
def set_timeout(self, timeout = 5.0) :
self.timeout = timeout
if self.master != None :
self.master.set_timeout(self.timeout)
def get_timeout(self) :
return self.timeout
#@override 返回 math.nan 表示读取失败, 否则读取成功
def read_float(self, device_addr, comm_str, scale = 1.0, offset = 0.0) -> 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()

19
comm_remap.py Normal file
View File

@ -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

78
comm_thread.py Normal file
View File

@ -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("与保护器连接超时, 重新进行连接")

View File

@ -1,4 +1,3 @@
Student_Name,Student_PRN,Student_Email,Student_Whatsapp_no,Student_pass,Branch,Semester 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 Ravikiran Yavalkar,12444444,abcd.efgh24@gmail.com,9607111115,India,AIML-F,III,NULL
jfen,55775577,test@example.com,NULL,China,AIML-F,III 测试用户,11111111,test@example.com,NULL,0000,测试,III,zhizhan-2

1 Student_Name Student_PRN Student_Email Student_Whatsapp_no Student_pass Branch Semester ssid
2 Ravikiran Yavalkar 12444444 abcd.efgh24@gmail.com 9607111115 India AIML-F III NULL
3 jfen 测试用户 55775577 11111111 test@example.com NULL China 0000 AIML-F 测试 III zhizhan-2

127
device_conf.py Normal file
View File

@ -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

769
kv/app.kv

File diff suppressed because it is too large Load Diff

315
main.py
View File

@ -1,5 +1,5 @@
from kivy.properties import ObjectProperty from kivy.properties import ObjectProperty, StringProperty
from datetime import datetime, timedelta from datetime import datetime, timedelta
from kivy.core.text import LabelBase from kivy.core.text import LabelBase
from kivy.metrics import dp from kivy.metrics import dp
@ -24,16 +24,21 @@ from kivymd.uix.dialog import MDDialog
from kivymd.uix.label import MDLabel from kivymd.uix.label import MDLabel
from kivy.uix.label import Label 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="MPoppins", fn_regular="fonts/Chinese/msyh.ttf")
LabelBase.register(name="BPoppins", 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="RRubik", fn_regular="fonts/Chinese/msyh.ttf")
LabelBase.register(name="RCro", 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") LabelBase.register(name="RPac", fn_regular="fonts/Chinese/msyh.ttf")
def get_books(): def get_books():
pass pass
class MainScreen(Screen): class MainScreen(Screen):
pass pass
@ -41,7 +46,7 @@ class LoginScreen(Screen):
login = ObjectProperty(None) login = ObjectProperty(None)
class SignUpScreen(Screen): class SignUpScreen(Screen):
signup: ObjectProperty = ObjectProperty(None) signup = ObjectProperty(None)
class ForgetPassScreen(Screen): class ForgetPassScreen(Screen):
forget_Password = ObjectProperty(None) forget_Password = ObjectProperty(None)
@ -82,7 +87,10 @@ class RenewBookScreen(Screen):
class HistoryScreen(Screen): class HistoryScreen(Screen):
history = ObjectProperty(None) history = ObjectProperty(None)
# Window.size = (dp(360), dp(680))
class app(MDApp): class app(MDApp):
wifi_status_text = StringProperty("")
def __init__(self): def __init__(self):
super().__init__() super().__init__()
self.signup_sem = None self.signup_sem = None
@ -140,6 +148,19 @@ class app(MDApp):
self.password = None self.password = None
self.username = 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): def build(self):
Builder.load_file('kv/app.kv') 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(RenewBookScreen(name="renew_book"))
screen_manager.add_widget(HistoryScreen(name="history")) screen_manager.add_widget(HistoryScreen(name="history"))
Clock.schedule_once(self.update_wifi_status, 2)
return screen_manager return screen_manager
#############################################ALL INPUT TEXT############################################################ #############################################ALL INPUT TEXT############################################################
def on_start(self): def on_start(self):
login_screen = self.root.get_screen("login")
self.username = login_screen.ids.username
self.password = login_screen.ids.password
# 从根窗口中获取名为"login"的屏幕(登录界面)
login_screen = self.root.get_screen("login")
# 通过界面ID获取登录界面中的用户名输入框组件
# self.username = login_screen.ids.username
# 通过界面ID获取登录界面中的密码输入框组件
# self.password = login_screen.ids.password
# 从根窗口中获取名为"home"的屏幕(主界面)
home_screen = self.root.get_screen("home") home_screen = self.root.get_screen("home")
# 通过界面ID获取主界面中的用户信息组件
self.user = home_screen.ids.user self.user = home_screen.ids.user
# 通过界面ID获取主界面中的书架位置名称组件
self.book_pos_name = home_screen.ids.book_pos_name self.book_pos_name = home_screen.ids.book_pos_name
# 通过界面ID获取主界面中的书架位置组件
self.book_pos = home_screen.ids.book_pos self.book_pos = home_screen.ids.book_pos
# 从根窗口中获取名为"forgot_pass"的屏幕(忘记密码界面)
forgot_pass_screen = self.root.get_screen("forgot_pass") forgot_pass_screen = self.root.get_screen("forgot_pass")
# 通过界面ID获取忘记密码界面中的手机号输入框组件
self.mobile_no = forgot_pass_screen.ids.forgotPass_no self.mobile_no = forgot_pass_screen.ids.forgotPass_no
# 从根窗口中获取名为"reset_pass"的屏幕(重置密码界面)
reset_pass_screen = self.root.get_screen("reset_pass") reset_pass_screen = self.root.get_screen("reset_pass")
# 通过界面ID获取重置密码界面中的新密码输入框组件
self.new_pass = reset_pass_screen.ids.new_pass self.new_pass = reset_pass_screen.ids.new_pass
# 通过界面ID获取重置密码界面中的确认新密码输入框组件
self.new_pass1 = reset_pass_screen.ids.new_pass1 self.new_pass1 = reset_pass_screen.ids.new_pass1
# 从根窗口中获取名为"otp"的屏幕(验证码界面)
otp_screen = self.root.get_screen("otp") otp_screen = self.root.get_screen("otp")
# 通过界面ID获取验证码界面中的"验证"按钮组件
self.otp_button = otp_screen.ids.otp_button self.otp_button = otp_screen.ids.otp_button
# 通过界面ID获取验证码界面中的"返回"按钮组件
self.back_button = otp_screen.ids.back_button self.back_button = otp_screen.ids.back_button
# 通过界面ID获取验证码界面中的第一个数字输入框组件
self.d1 = otp_screen.ids.d1 self.d1 = otp_screen.ids.d1
# 通过界面ID获取验证码界面中的第二个数字输入框组件
self.d2 = otp_screen.ids.d2 self.d2 = otp_screen.ids.d2
# 通过界面ID获取验证码界面中的第三个数字输入框组件
self.d3 = otp_screen.ids.d3 self.d3 = otp_screen.ids.d3
# 通过界面ID获取验证码界面中的第四个数字输入框组件
self.d4 = otp_screen.ids.d4 self.d4 = otp_screen.ids.d4
# 从根窗口中获取名为"signup"的屏幕(注册界面)
signup_screen = self.root.get_screen("signup") signup_screen = self.root.get_screen("signup")
# 通过界面ID获取注册界面中的用户名输入框组件
self.user_name = signup_screen.ids.signup_name self.user_name = signup_screen.ids.signup_name
# 通过界面ID获取注册界面中的PRN可能是学号/身份标识)输入框组件
self.user_prn = signup_screen.ids.signup_prn self.user_prn = signup_screen.ids.signup_prn
# 通过界面ID获取注册界面中的邮箱输入框组件
self.user_email = signup_screen.ids.signup_email self.user_email = signup_screen.ids.signup_email
# 通过界面ID获取注册界面中的手机号输入框组件
self.user_no = signup_screen.ids.signup_no self.user_no = signup_screen.ids.signup_no
# 通过界面ID获取注册界面中的密码输入框组件
self.user_pass = signup_screen.ids.signup_pass self.user_pass = signup_screen.ids.signup_pass
# 通过界面ID获取注册界面中的所属部门/专业选择组件
self.signup_branch = signup_screen.ids.signup_branch self.signup_branch = signup_screen.ids.signup_branch
# 通过界面ID获取注册界面中的年级/学期选择组件
self.signup_sem = signup_screen.ids.signup_sem self.signup_sem = signup_screen.ids.signup_sem
# 从根窗口中获取名为"profile"的屏幕(个人资料界面)
profile_screen = self.root.get_screen("profile") profile_screen = self.root.get_screen("profile")
# 通过界面ID获取个人资料界面中的姓名展示组件
self.profile_name = profile_screen.ids.profile_name self.profile_name = profile_screen.ids.profile_name
# 通过界面ID获取个人资料界面中的邮箱展示组件
self.profile_email = profile_screen.ids.profile_email self.profile_email = profile_screen.ids.profile_email
# 通过界面ID获取个人资料界面中的PRN身份标识展示组件
self.profile_prn = profile_screen.ids.profile_prn self.profile_prn = profile_screen.ids.profile_prn
# 通过界面ID获取个人资料界面中的手机号展示组件
self.profile_number = profile_screen.ids.profile_number self.profile_number = profile_screen.ids.profile_number
# 通过界面ID获取个人资料界面中的所属部门/专业展示组件
self.profile_branch = profile_screen.ids.profile_branch self.profile_branch = profile_screen.ids.profile_branch
# 通过界面ID获取个人资料界面中的年级/学期展示组件
self.profile_semester = profile_screen.ids.profile_semester self.profile_semester = profile_screen.ids.profile_semester
# 从根窗口中获取名为"profile_edit"的屏幕(个人资料编辑界面)
self.profile_edit_screen = self.root.get_screen("profile_edit") self.profile_edit_screen = self.root.get_screen("profile_edit")
# 通过界面ID获取资料编辑界面中的姓名编辑输入框组件
self.edit_name = self.profile_edit_screen.ids.edit_name self.edit_name = self.profile_edit_screen.ids.edit_name
# 通过界面ID获取资料编辑界面中的邮箱编辑输入框组件
self.edit_email = self.profile_edit_screen.ids.edit_email self.edit_email = self.profile_edit_screen.ids.edit_email
# 通过界面ID获取资料编辑界面中的PRN身份标识编辑输入框组件
self.edit_prn = self.profile_edit_screen.ids.edit_prn self.edit_prn = self.profile_edit_screen.ids.edit_prn
# 通过界面ID获取资料编辑界面中的手机号编辑输入框组件
self.edit_number = self.profile_edit_screen.ids.edit_no self.edit_number = self.profile_edit_screen.ids.edit_no
# 通过界面ID获取资料编辑界面中的所属部门/专业编辑选择组件
self.edit_branch = self.profile_edit_screen.ids.edit_branch self.edit_branch = self.profile_edit_screen.ids.edit_branch
# 通过界面ID获取资料编辑界面中的年级/学期编辑选择组件
self.edit_semester = self.profile_edit_screen.ids.edit_sem self.edit_semester = self.profile_edit_screen.ids.edit_sem
# 从根窗口中获取名为"borrow_book"的屏幕(借书界面)
borrow_screen = self.root.get_screen("borrow_book") borrow_screen = self.root.get_screen("borrow_book")
# 通过界面ID获取借书界面中的ISBN图书编号输入框组件
self.isbn = borrow_screen.ids.isbn self.isbn = borrow_screen.ids.isbn
# 从根窗口中获取名为"return_book"的屏幕(还书界面)
return_book_screen = self.root.get_screen("return_book") return_book_screen = self.root.get_screen("return_book")
# 通过界面ID获取还书界面中的图片与标签网格布局组件
self.image_label_grid = return_book_screen.ids.image_label_grid self.image_label_grid = return_book_screen.ids.image_label_grid
# 从根窗口中获取名为"renew_book"的屏幕(图书续借界面)
renew_book_screen = self.root.get_screen("renew_book") renew_book_screen = self.root.get_screen("renew_book")
# 通过界面ID获取续借界面中的网格布局组件
self.renew_grid = renew_book_screen.ids.renew_grid self.renew_grid = renew_book_screen.ids.renew_grid
# 从根窗口中获取名为"history"的屏幕(借阅历史界面)
history_screen = self.root.get_screen("history") history_screen = self.root.get_screen("history")
# 通过界面ID获取借阅历史界面中的网格布局组件
self.history_grid = history_screen.ids.history_grid self.history_grid = history_screen.ids.history_grid
# 从根窗口中获取名为"searchBook"的屏幕(图书搜索界面)
search_book_screen = self.root.get_screen("searchBook") search_book_screen = self.root.get_screen("searchBook")
# 通过界面ID获取图书搜索界面中的搜索输入框组件
self.search_field = search_book_screen.ids.search_field self.search_field = search_book_screen.ids.search_field
# 通过界面ID获取图书搜索界面中的搜索结果网格布局组件
self.search_grid = search_book_screen.ids.search_grid self.search_grid = search_book_screen.ids.search_grid
# 从根窗口中获取名为"recommend"的屏幕(图书推荐界面)
self.recommend_screen = self.root.get_screen("recommend") self.recommend_screen = self.root.get_screen("recommend")
# 通过界面ID获取推荐界面中的图书输入框组件
self.book_input = self.recommend_screen.ids.book_input self.book_input = self.recommend_screen.ids.book_input
# 通过界面ID获取推荐界面中的推荐结果网格布局组件
self.rec_grid = self.recommend_screen.ids.rec_grid self.rec_grid = self.recommend_screen.ids.rec_grid
# 通过界面ID获取推荐界面中的推荐信息标签组件
self.rec_label = self.recommend_screen.ids.rec_label self.rec_label = self.recommend_screen.ids.rec_label
#############################################@Frequently used functions################################################## #############################################@Frequently used functions##################################################
@staticmethod @staticmethod
def change_cursor(is_enter): def change_cursor(is_enter):
"""改变鼠标指针样式"""
# 如果is_enter为True鼠标进入目标区域将鼠标指针改为手型
if is_enter: if is_enter:
Window.set_system_cursor('hand') Window.set_system_cursor('hand')
# 否则(鼠标离开目标区域),将鼠标指针改回默认箭头样式
else: else:
Window.set_system_cursor('arrow') Window.set_system_cursor('arrow')
@staticmethod @staticmethod
def toggle_password_visibility(password_field, icon_button): def toggle_password_visibility(password_field, icon_button):
"""切换密码输入框的可见性状态"""
# 检查密码输入框当前是否处于密码隐藏状态
if password_field.password: if password_field.password:
# 如果是隐藏状态,切换为可见状态
password_field.password = False password_field.password = False
# 更新图标为"eye"(眼睛图标,表示当前可见)
icon_button.icon = "eye" icon_button.icon = "eye"
else: else:
# 如果是可见状态,切换为隐藏状态(显示为圆点/星号)
password_field.password = True password_field.password = True
# 更新图标为"eye-off"(闭眼图标,表示当前隐藏)
icon_button.icon = "eye-off" icon_button.icon = "eye-off"
def clear_text(self): def clear_text(self):
"""清空界面中所有输入框和文本组件的内容"""
# 清空图书搜索框内容
self.search_field.text = "" self.search_field.text = ""
# 清空图书推荐输入框内容
self.book_input.text = "" self.book_input.text = ""
self.username.text = "" # 清空登录界面的用户名输入框内容
self.password.text = "" # self.username.text = ""
# 清空登录界面的密码输入框内容
# self.password.text = ""
# 清空忘记密码界面的手机号输入框内容
self.mobile_no.text = "" self.mobile_no.text = ""
# 清空验证码界面的第一个输入框内容
self.d1.text = "" self.d1.text = ""
# 清空验证码界面的第二个输入框内容
self.d2.text = "" self.d2.text = ""
# 清空验证码界面的第三个输入框内容
self.d3.text = "" self.d3.text = ""
# 清空验证码界面的第四个输入框内容
self.d4.text = "" self.d4.text = ""
# 清空书架位置名称文本内容
self.book_pos_name.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 = "" self.mobile_no.text = ""
# 清空借书界面的ISBN输入框内容
self.isbn.text = '' self.isbn.text = ''
def on_press_back_arrow(self): def on_press_back_arrow(self):
self.username.text = "" # self.username.text = ""
self.password.text = "" # self.password.text = ""
self.mobile_no.text = "" self.mobile_no.text = ""
self.d1.text = "" self.d1.text = ""
self.d2.text = "" self.d2.text = ""
@ -340,54 +443,154 @@ class app(MDApp):
###################################LoginPageWork-Start################################################# ###################################LoginPageWork-Start#################################################
# login Verification function "verify" # # login Verification function "verify"
def verify(self, obj): # def verify(self, obj):
status = False # status = False
if self.username.text == "" or self.password.text == "": # if self.username.text == "" or self.password.text == "":
check = "请输入账户和密码" # check = "请输入账户和密码"
return self.dialog1(check) # return self.dialog1(check)
else: # else:
name = "" # name = ""
with open('data/Users.csv', "r", encoding="utf-8") as file: # with open('data/Users.csv', "r", encoding="utf-8") as file:
csv_reader = csv.reader(file, delimiter=",") # csv_reader = csv.reader(file, delimiter=",")
for line in csv_reader: # for line in csv_reader:
try: # try:
if not obj: # if not obj:
a = (line[1] == self.username.text or line[2] == self.username.text) # a = (line[1] == self.username.text or line[2] == self.username.text)
else: # else:
a = (line[1] == self.edit_prn.text or line[2] == self.edit_email.text) # a = (line[1] == self.edit_prn.text or line[2] == self.edit_email.text)
if a and line[4] == self.password.text: # if a and line[4] == self.password.text:
status = True # status = True
name = line[0] # name = line[0]
prn = line[1] # prn = line[1]
email = line[2] # email = line[2]
number = line[3] # number = line[3]
branch = line[5] # branch = line[5]
semester = line[6] # semester = line[6]
break # break
except IndexError: # except IndexError:
pass # pass
except Exception as e: # except Exception as e:
print(e) # print(e)
if status: # if status:
if not obj: # if not obj:
check = "Login Successful" + "\nHello! " + name # check = "Login Successful" + "\nHello! " + name
else: # else:
check = "Profile Updated Successfully!" # check = "Profile Updated Successfully!"
self.secure_profile() # self.secure_profile()
self.root.current = "home" # self.root.current = "home"
self.user.text = f"""[b]Hey! {name}[/b]""" # self.user.text = f"""[b]Hey! {name}[/b]"""
self.profile_name.text = self.edit_name.text = name # self.profile_name.text = self.edit_name.text = name
self.profile_email.text = self.edit_email.text = email # self.profile_email.text = self.edit_email.text = email
self.profile_prn.text = self.edit_prn.text = prn # self.profile_prn.text = self.edit_prn.text = prn
self.profile_number.text = self.edit_number.text = number # self.profile_number.text = self.edit_number.text = number
self.profile_semester.text = self.edit_semester.text = semester # self.profile_semester.text = self.edit_semester.text = semester
self.profile_branch.text = self.edit_branch.text = branch # 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: else:
check = "Login Failed!. " + "Enter correct username and password." ssid_display = "SSID Unknown"
self.dialog1(check)
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 click_count = 0
def login_mode(self, username_text, button_text): def login_mode(self, username_text, button_text):

2536
menu_mine.py Normal file

File diff suppressed because it is too large Load Diff

6
menu_page.py Normal file
View File

@ -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 #馈电页面

109
menu_utils.py Normal file
View File

@ -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

218
mqtt_device.py Normal file
View File

@ -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()

102
mqtt_object.py Normal file
View File

@ -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

41
print_color.py Normal file
View File

@ -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()