# -*- coding:utf-8 -*- """ @File : app @Author : xuxingchen @Version : 0.1.6 @Contact : xuxingchen@sinochem.com @Desc : 将中转mqtt与北向设备端进行连接 """ import base64 import json import os.path import time import traceback from urllib import request import logger from logger import Logger from utils import generate_random_string from devices.meian_db import SQLiteDatabaseEngine, BaseTable, DeviceTable, GatewayTable, UserInfoTable, \ HeartBeatTable, RegisterTable, RecordTable from devices.common_service import auto_service from devices.common_model import UserData from devices.yunfu_model import MeianProduct from misc import create_mqtt_client from config import DB_PATH, BROKER_0_HOST, BROKER_0_PORT, BROKER_0_API_PORT, BROKER_0_API_KEY, BROKER_0_API_SECRET, \ BROKER_0_USERNAME, BROKER_0_PASSWD, BROKER_0_SSL, BROKER_1_HOST, BROKER_1_PORT, BROKER_1_SSL def on_connect(client, userdata, _flags, rc): Logger.connect(f"{userdata.client_id} mqtt connecting: {{rc: {rc}}}") if userdata.topics: _topics = [(topic, 0) for topic in userdata.topics] client.subscribe(_topics) Logger.debug(f"{userdata.client_id} subscribe topics: {userdata.topics}") def on_message(_client, userdata: UserData, message): try: msg = json.loads(message.payload.decode().replace("\x00", "")) if msg.get("dataType", False) and msg.get("dataType") != "heartBeat": Logger.debug(f"💡 {userdata.client_id} 接收到新数据,执行 on_message 中 ...", log_path=None) Logger.info(f"{message.topic}: {msg}") userdata.set_topic(message.topic) if userdata.table_handler is None: _db = SQLiteDatabaseEngine(db_path=DB_PATH) userdata.set_table_handler(BaseTable(_db.connection, _db.cursor)) if userdata.token: if "messageId" in msg.keys() and userdata.token == msg["messageId"]: # 兼容aiot平台 userdata.set_status_add("status", True) userdata.set_status_add("response", msg) if "token" in msg.keys() and userdata.token == msg["token"] and userdata.code == msg["dataType"]: # 兼容美安 userdata.set_status_add("status", True) userdata.set_status_add("response", msg) else: auto_service(msg, userdata) except Exception as e: Logger.error(f"{type(e).__name__}, {e}") if logger.DEBUG: traceback.print_exc() def on_disconnect(_client, _userdata, rc): Logger.disconnect(f"Break mqtt connection! {{rc: {rc}}}") def gen_gateway_info(th): # 构建每个网关应当订阅的主题列表 info = {} # 获取网关信息 gateway_list = GatewayTable.get_registered_gateway(th) assert gateway_list is not None, "请优先完善网关配置表" for gateway_item in gateway_list: sub_aiot_id_list = json.loads(GatewayTable.get_registered_sub_aiot_id(th, gateway_item[0])[0]) # 追加每个网关下每个子设备的主题 if sub_aiot_id_list: info[gateway_item[0]] = { "sct": gateway_item[1], "topics": [ f"/jmlink/{aiot_id}/tml/service/call" for aiot_id in sub_aiot_id_list ] } else: info[gateway_item[0]] = { "sct": gateway_item[1], "topics": [] } return info def check_ids(ids, host, port, key, secret): """检查每个id对应的客户端认证""" assert ids is not None, "数据库中无设备数据" id_list = [_id[0] for _id in ids] url = f"http://{host}:{port}/api/v5/authentication/password_based:built_in_database/users" headers = { "Content-Type": "application/json", "Authorization": "Basic " + base64.b64encode((key + ":" + secret).encode()).decode() } for project_code in id_list: body_data = { "user_id": f"{project_code}@meian", "password": MeianProduct.secret, "is_superuser": False } body_json = json.dumps(body_data).encode('utf-8') req = request.Request(url, data=body_json, headers=headers) try: with request.urlopen(req) as f: response = json.loads(f.read().decode()) status_code = f.getcode() if status_code == 201: if "user_id" in response: Logger.init(f"为项目{project_code}新增用户") except Exception as e: if "HTTP Error 409" in str(e): # 用户已存在 pass else: Logger.error(f"{type(e).__name__}, {e}") if logger.DEBUG: traceback.print_exc() if __name__ == '__main__': db = SQLiteDatabaseEngine(db_path=DB_PATH) # 此时数据表中应当已具备数据 table_handler = BaseTable(db.connection, db.cursor) # 数据库自检初始化 Logger.init("数据库执行初始化校验 ...") GatewayTable.check(table_handler) DeviceTable.check(table_handler) HeartBeatTable.check(table_handler) RegisterTable.check(table_handler) UserInfoTable.check(table_handler) RecordTable.check(table_handler) Logger.init("数据库完成初始化校验 ✅") # 设备mqtt认证客户端初始化,确保每个设备的账户都存在 Logger.init("中转mqtt认证客户端账户执行初始化校验 ...") ids = GatewayTable.get_project_code_list(table_handler) check_ids(ids, BROKER_0_HOST, BROKER_0_API_PORT, BROKER_0_API_KEY, BROKER_0_API_SECRET) Logger.init("中转mqtt认证客户端账户完成初始化校验 ✅") # 1. 获取待订阅的主题 topics = json.loads(DeviceTable.get_topics(table_handler)[1]) # 0: subscribe, 1: publish # 根据现有的设备表信息结合网关表信息构建aiot平台服务调用的主题 gateway_info = gen_gateway_info(table_handler) client_dict = {} # 2. 构建客户端 user_data_0 = UserData() user_data_0.set_topics(topics) # 用于连接时订阅 user_data_0.set_status_add("client", 0) user_data_0.set_client_id(f"mqtt-python-{generate_random_string()}") user_data_0.set_clients(client_dict) client_0 = create_mqtt_client( broker_host=BROKER_0_HOST, broker_port=BROKER_0_PORT, userdata=user_data_0, on_connect=on_connect, on_message=on_message, client_id=user_data_0.client_id, username=BROKER_0_USERNAME, password=BROKER_0_PASSWD, ssl_flag=BROKER_0_SSL ) client_0.loop_start() client_dict["center"] = [client_0, user_data_0] for gateway_id in gateway_info.keys(): user_data_1 = UserData() user_data_1.set_topics(gateway_info[gateway_id]["topics"]) # 用于连接时订阅 user_data_1.set_status_add("client", 1) user_data_1.set_status_add("last_timestamp", time.time()) user_data_1.set_status_add("gateway_id", gateway_id) user_data_1.set_client_id(f"device|1|1|{gateway_id}") client_1 = create_mqtt_client( broker_host=BROKER_1_HOST, broker_port=BROKER_1_PORT, userdata=user_data_1, on_connect=on_connect, on_disconnect=on_disconnect, on_message=on_message, client_id=user_data_1.client_id, username=gateway_id, password=gateway_info[gateway_id]["sct"], ssl_flag=BROKER_1_SSL ) client_1.loop_start() client_dict[gateway_id] = [client_1, user_data_1] user_data_1.set_clients(client_dict) while True: time.sleep(86400) t = str(int((time.time() - 86400) * 1000)) UserInfoTable.delete_redundancy_1(table_handler, t) UserInfoTable.delete_redundancy_0(table_handler, t) # 每7天清除一次日志文件 creation_time = os.path.getctime(logger.LOGGER_PATH) days_since_creation = (time.time() - creation_time) / (60 * 60 * 24) if os.path.exists(logger.LOGGER_PATH) and days_since_creation >= 7: try: f0 = open(logger.LOGGER_PATH, "r", encoding="utf8") f1 = open(f"{logger.LOGGER_PATH}.old", "w", encoding="utf8") f1.write(f0.read()) f1.close() f0.close() os.remove(logger.LOGGER_PATH) print(f"日志文件 {logger.LOGGER_PATH} 完成重置") except Exception as e: print(f"日志文件 {logger.LOGGER_PATH} 重置失败: {e}")