v1.0.11_web_app_2006.txt 56 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434
  1. # C:\Users\chx_s\AppData\Local\Programs\Python\Python310\Scripts\pip.exe
  2. # C:\Users\chx_s\AppData\Local\Programs\Python\Python310\python.exe
  3. from flask import Flask, render_template, request, redirect, url_for, session, jsonify, send_from_directory
  4. from datetime import datetime
  5. import os
  6. import json
  7. import threading
  8. import paho.mqtt.client as mqtt
  9. import logging
  10. from functools import wraps
  11. import random
  12. import math
  13. import time
  14. import subprocess
  15. import platform as sys_platform
  16. import shutil
  17. # 自定义
  18. from pos_manager import frame
  19. # 应用版本信息
  20. APP_VERSION = '1.0.11'
  21. # 应用日志文件路径
  22. LOG_FILE = 'app.log'
  23. # 配置日志
  24. logging.basicConfig(
  25. level=logging.INFO,
  26. format='%(asctime)s - %(filename)s[line:%(lineno)d] - %(levelname)s: %(message)s',
  27. datefmt='%Y-%m-%d %H:%M:%S',
  28. handlers=[
  29. logging.FileHandler(LOG_FILE, encoding='utf-8'),
  30. logging.StreamHandler()
  31. ]
  32. )
  33. logger = logging.getLogger(__name__)
  34. # 记录应用启动时间
  35. app_start_time = datetime.now()
  36. app = Flask(__name__)
  37. app.secret_key = 'supersecretkey'
  38. app.config['UPLOAD_FOLDER'] = 'static/uploads'
  39. app.config['ALLOWED_EXTENSIONS'] = {'bin', 'zip', 'tar', 'rar'}
  40. app.config['DATA_FOLDER'] = 'data'
  41. app.config['APP_UPGREADE_FOLDER_TEMP'] = 'app_upgrade_temp'
  42. # 关键配置:禁用 ASCII 转义,确保中文正常显示
  43. app.config['JSON_AS_ASCII'] = False
  44. # 确保目录存在
  45. os.makedirs(app.config['UPLOAD_FOLDER'], exist_ok=True)
  46. os.makedirs(app.config['DATA_FOLDER'], exist_ok=True)
  47. # 数据文件路径
  48. USERS_PATH = os.path.join(app.config['DATA_FOLDER'], 'users.json')
  49. FIRMWARES_PATH = os.path.join(app.config['DATA_FOLDER'], 'firmwares.json')
  50. DEVICES_PATH = os.path.join(app.config['DATA_FOLDER'], 'devices.json')
  51. PLATFORMS_PATH = os.path.join(app.config['DATA_FOLDER'], 'platforms.json')
  52. # 全局变量
  53. # MQTT客户端管理
  54. mqtt_clients = {}
  55. mqtt_threads = {}
  56. mqtt_lock = threading.Lock()
  57. mqtt_sub_topic = [("cpyypt/up/2006/#", 0)]
  58. mqtt_pub_topic = "cpyypt/down/2006/0000000101"
  59. @app.route('/favicon.ico')
  60. def favicon():
  61. return send_from_directory("static", "favicon.ico", mimetype='image/vnd.microsoft.icon')
  62. @app.before_request
  63. def update_last_activity():
  64. mqtt_last_activity['last_time'] = time.time()
  65. # logger.info(f"mqtt_last_activity: {mqtt_last_activity}")
  66. # 获取应用运行时长
  67. @app.route('/api/run_time')
  68. def get_run_time():
  69. global app_start_time
  70. current_time = datetime.now()
  71. run_time = current_time - app_start_time
  72. # 计算年、月、日、时、分、秒
  73. days = run_time.days
  74. seconds = run_time.seconds
  75. hours, remainder = divmod(seconds, 3600)
  76. minutes, seconds = divmod(remainder, 60)
  77. # 简化计算,假设每年365天,每月30天
  78. years = days // 365
  79. days_remaining = days % 365
  80. months = days_remaining // 30
  81. days = days_remaining % 30
  82. run_time_str = f"{years}年{months}月{days}日 {hours}时{minutes}分{seconds}秒"
  83. return jsonify({'run_time': run_time_str})
  84. # 上下文处理器
  85. @app.context_processor
  86. def inject_common_data():
  87. return {
  88. 'datetime': datetime,
  89. 'app_version': APP_VERSION
  90. }
  91. # 初始化数据
  92. def init_data():
  93. # 初始化用户数据
  94. if not os.path.exists(USERS_PATH):
  95. with open(USERS_PATH, 'w', encoding='utf-8') as f:
  96. json.dump({'admin': 'admin'}, f, ensure_ascii=False)
  97. # 初始化固件数据
  98. if not os.path.exists(FIRMWARES_PATH):
  99. init_firmwares = [
  100. {'id': 1, 'filename': 'pos.bin', 'upload_time': '2025-08-01 13:08:30', 'remark': '初始版本'},
  101. {'id': 2, 'filename': 'ITSF-POS V2.3.bin', 'upload_time': '2025-08-01 14:10:09', 'remark': '基础功能版'}
  102. ]
  103. with open(FIRMWARES_PATH, 'w', encoding='utf-8') as f:
  104. json.dump(init_firmwares, f, ensure_ascii=False, indent=2)
  105. # 初始化设备数据
  106. if not os.path.exists(DEVICES_PATH):
  107. init_devices = [
  108. {
  109. "id": 1,
  110. "dev_type": "2006",
  111. "dev_sn": "101",
  112. "firmware_ver": "pos12.bin",
  113. "ip": "192.168.1.101",
  114. "status": "正常",
  115. "online": "在线",
  116. "cloud_platform": "阿里云测试",
  117. "network_type": "WiFi",
  118. "wifi_ssid": "ssss_5G",
  119. "wifi_password": "eeeeeeeeee",
  120. "run_time":1000,
  121. "receive_sum":12,
  122. "send_sum":10,
  123. "abnormal_sum":2,
  124. "success_rate":83.33,
  125. "speed":83.33
  126. },
  127. {
  128. "id": 2,
  129. "dev_type": "2006",
  130. "dev_sn": "103",
  131. "firmware_ver": "pos1.bin",
  132. "ip": "192.168.1.102",
  133. "status": "正常",
  134. "online": "离线",
  135. "cloud_platform": "阿里云测试",
  136. "network_type": "WiFi",
  137. "wifi_ssid": "wbjw",
  138. "wifi_password": "2222",
  139. "run_time":1000,
  140. "receive_sum":12,
  141. "send_sum":10,
  142. "abnormal_sum":2,
  143. "success_rate":83.33,
  144. "speed":83.33
  145. }
  146. ]
  147. with open(DEVICES_PATH, 'w', encoding='utf-8') as f:
  148. json.dump(init_devices, f, ensure_ascii=False, indent=2)
  149. # 初始化云平台数据
  150. if not os.path.exists(PLATFORMS_PATH):
  151. platform_d = {
  152. "current_platform": "test_env",
  153. "platform_l": {
  154. "aliyun_prod_env": {
  155. "id": 1,
  156. "name": "阿里云生产",
  157. "status": "connected",
  158. "ip": "mqtt.cpyypt.cn",
  159. "port": 9000,
  160. "sub_topic ": mqtt_sub_topic,
  161. "pub_topic ": mqtt_pub_topic,
  162. "client_id": f"pos_mng_{random.randint(0, 100)}",
  163. "username": "cpyypt",
  164. "password": "1SvTlvm1VCawSzS"
  165. },
  166. "test_env": {
  167. "id": 2,
  168. "name": "阿里云测试",
  169. "status": "disconnected",
  170. "ip": "test-mqtt.cpyypt.cn",
  171. "port": 9000,
  172. "sub_topic ": mqtt_sub_topic,
  173. "pub_topic ": mqtt_pub_topic,
  174. "client_id": f"pos_mng_{random.randint(0, 100)}",
  175. "username": "admin",
  176. "password": "houjianwei"
  177. },
  178. "localize_a_env": {
  179. "id": 3,
  180. "name": "本地化A云平台",
  181. "status": "disconnected",
  182. "ip": "mqtt.localize_a_env.com",
  183. "port": 1883,
  184. "sub_topic ": mqtt_sub_topic,
  185. "pub_topic ": mqtt_pub_topic,
  186. "client_id": f"pos_mng_{random.randint(0, 100)}",
  187. "username": "localize_a_env_user",
  188. "password": "localize_a_env_pass"
  189. }
  190. }
  191. }
  192. # platform_d["platform_l"]["aliyun_prod_env"]["sub_topic"] = [("cpyypt/up/2006/#", 0)]
  193. # platform_d["platform_l"]["aliyun_prod_env"]["pub_topic"] = [("cpyypt/down/2006/#", 0)]
  194. # platform_d["platform_l"]["aliyun_prod_env"]["client_id"] = f"pos_mng_{random.randint(0, 100)}"
  195. with open(PLATFORMS_PATH, 'w', encoding='utf-8') as f:
  196. json.dump(platform_d, f, ensure_ascii=False, indent=2)
  197. # 数据加载函数
  198. def load_users():
  199. with open(USERS_PATH, 'r', encoding='utf-8') as f:
  200. return json.load(f)
  201. def load_firmwares():
  202. with open(FIRMWARES_PATH, 'r', encoding='utf-8') as f:
  203. return json.load(f)
  204. load_devices_first = True # 标记第1次加载
  205. def load_devices():
  206. if not os.path.exists(DEVICES_PATH):
  207. logger.info(f"设备配置文件不存在,初始化数据: {DEVICES_PATH}")
  208. init_data()
  209. with open(DEVICES_PATH, 'r', encoding='utf-8') as f:
  210. devices = json.load(f)
  211. # 程序启动时,强制将所有所有统计信息为0
  212. global load_devices_first
  213. if load_devices_first :
  214. load_devices_first = False # 第1次加载完成,标记为False
  215. for device in devices:
  216. device['online'] = '离线'
  217. device['realtime'] = 0
  218. device['run_time'] = 0
  219. device['receive_sum'] = 0
  220. device['send_sum'] = 0
  221. device['abnormal_sum'] = 0
  222. device['success_rate'] = 0
  223. device['speed'] = 0
  224. # 保存强制更新后的状态
  225. save_devices(devices)
  226. return devices
  227. load_platforms_first = True # 标记第1次加载
  228. def load_platforms():
  229. try:
  230. if not os.path.exists(PLATFORMS_PATH):
  231. logger.info(f"云平台配置文件不存在,初始化数据: {PLATFORMS_PATH}")
  232. init_data()
  233. # logger.info(f"加载云平台配置文件: {PLATFORMS_PATH}")
  234. with open(PLATFORMS_PATH, 'r', encoding='utf-8') as f:
  235. file_content = f.read().strip()
  236. if not file_content:
  237. logger.error("云平台配置文件为空")
  238. # 如果文件为空,重新初始化
  239. init_data()
  240. with open(PLATFORMS_PATH, 'r', encoding='utf-8') as f_new:
  241. platform_d = json.load(f_new)
  242. else:
  243. try:
  244. platform_d = json.loads(file_content)
  245. except json.JSONDecodeError as e:
  246. logger.error(f"解析云平台配置文件失败: {e}")
  247. # 尝试重新初始化
  248. init_data()
  249. with open(PLATFORMS_PATH, 'r', encoding='utf-8') as f_new:
  250. platform_d = json.load(f_new)
  251. # 程序启动时,强制将所有云平台状态设置为已断开
  252. global load_platforms_first
  253. if load_platforms_first and 'platform_l' in platform_d:
  254. load_platforms_first = False # 第1次加载完成,标记为False
  255. for platform_id in platform_d['platform_l']:
  256. platform_d['platform_l'][platform_id]['status'] = 'disconnected'
  257. platform_d['platform_l'][platform_id]['client_id'] = "pos_mng_%s_%d" % (os.environ['COMPUTERNAME'], random.randint(0, 100))
  258. # 保存强制更新后的状态
  259. save_platforms(platform_d)
  260. return platform_d
  261. except Exception as e:
  262. logger.error(f"加载云平台配置时发生异常: {e}")
  263. # 尝试返回一个默认值或重新初始化
  264. init_data()
  265. with open(PLATFORMS_PATH, 'r', encoding='utf-8') as f:
  266. return json.load(f)
  267. return platform_d
  268. def load_platforms_c():
  269. platform_d = load_platforms()
  270. platform_c = {}
  271. for platform_id in platform_d['platform_l']:
  272. platform_c[platform_id] = {}
  273. platform_c[platform_id]['name'] = platform_d['platform_l'][platform_id]['name']
  274. platform_c[platform_id]['status'] = platform_d['platform_l'][platform_id]['status']
  275. platform_c[platform_id]['ip'] = platform_d['platform_l'][platform_id]['ip']
  276. return platform_c
  277. # 数据保存函数
  278. def save_users(users):
  279. with open(USERS_PATH, 'w', encoding='utf-8') as f:
  280. json.dump(users, f, ensure_ascii=False, indent=2)
  281. def save_firmwares(firmwares):
  282. with open(FIRMWARES_PATH, 'w', encoding='utf-8') as f:
  283. json.dump(firmwares, f, ensure_ascii=False, indent=2)
  284. def save_devices(devices):
  285. with open(DEVICES_PATH, 'w', encoding='utf-8') as f:
  286. json.dump(devices, f, ensure_ascii=False, indent=2)
  287. def save_platforms(platform_d):
  288. with open(PLATFORMS_PATH, 'w', encoding='utf-8') as f:
  289. json.dump(platform_d, f, ensure_ascii=False, indent=2)
  290. # MQTT回调函数
  291. def on_connect(client, userdata, flags, rc):
  292. platform_id = userdata['platform_id']
  293. logger.info(f"MQTT连接成功 - 云平台: {platform_id}, 结果代码: {rc}")
  294. pub_topic = userdata['pub_topic']
  295. client.subscribe(mqtt_sub_topic)
  296. logger.info(f"已订阅主题: {mqtt_sub_topic}")
  297. with mqtt_lock:
  298. platform_d = load_platforms()
  299. if platform_id in platform_d['platform_l']:
  300. platform_d['platform_l'][platform_id]['status'] = 'connected'
  301. save_platforms(platform_d)
  302. def on_disconnect(client, userdata, rc):
  303. platform_id = userdata['platform_id']
  304. logger.info(f"MQTT断开连接 - 云平台: {platform_id}, 结果代码: {rc}")
  305. with mqtt_lock:
  306. platform_d = load_platforms()
  307. if platform_id in platform_d['platform_l']:
  308. platform_d['platform_l'][platform_id]['status'] = 'disconnected'
  309. save_platforms(platform_d)
  310. def on_message(client, userdata, msg):
  311. platform_id = userdata['platform_id']
  312. try:
  313. payload_str = msg.payload.decode('gb2312')
  314. except UnicodeDecodeError:
  315. try:
  316. payload_str = msg.payload.decode('utf-8')
  317. except UnicodeDecodeError:
  318. payload_str = msg.payload.hex().upper()
  319. # logger.info(f"收到消息 - 云平台: {platform_id}, 主题: {msg.topic}, 内容: {payload_str}")
  320. # 如果长时间未操作,自动断开MQTT连接
  321. platform_d = load_platforms()
  322. # """
  323. get_platform_status = next((value['status'] for key, value in platform_d['platform_l'].items() if key == platform_id), None)
  324. if get_platform_status == 'connected':
  325. mqtt_disconnect_time_out = 5*60
  326. if time.time() - mqtt_last_activity['last_time'] > mqtt_disconnect_time_out: # 5分钟无操作
  327. logger.info(f"云平台 {platform_id} 长时间未操作,超过{mqtt_disconnect_time_out}秒,自动断开连接")
  328. platform_d['platform_l'][platform_id]['status'] = 'disconnected'
  329. stop_mqtt_client(platform_id)
  330. logger.info(f"云平台 {platform_id} 自动断开连接,已完成")
  331. return
  332. # """
  333. dev_type = msg.topic.split("/")[-2]
  334. dev_sn = msg.topic.split("/")[-1].lstrip('0')
  335. devices = load_devices()
  336. device = next((item for item in devices if item['dev_sn'] == dev_sn), None)
  337. if device:
  338. if(device['online'] != '在线'):
  339. logger.info(f"云平台:{platform_id}, 设备 {dev_sn} 上线")
  340. device['online'] = '在线'
  341. device['realtime'] = int(time.time())
  342. platform_name = next((value['name'] for key, value in platform_d['platform_l'].items() if key == platform_id), None)
  343. device['cloud_platform'] = platform_name
  344. save_devices(devices)
  345. if type(msg.payload)== bytes and len(msg.payload) > 4 and payload_str[0:4] == "FEFE":
  346. # if type(msg.payload)== bytes and len(msg.payload) > 4 and msg.payload[0:2] == 0xFEFE:
  347. try:
  348. header, body = frame.parse_data(client, msg.payload, msg.topic)
  349. if len(header) > 0 and len(body) > 0:
  350. pro_ver = header.get('pro_ver', None)
  351. msg_type1 = header.get('msg_type1', None)
  352. msg_type2 = header.get('msg_type2', None)
  353. r_body = {}
  354. r_body['master_type'] = body.get('master_type', None)
  355. r_body['master_sn'] = body.get('master_sn', None)
  356. r_body['app_version'] = body.get('app_version', None)
  357. r_body['reset_times'] = body.get('reset_times', None)
  358. r_body['last_reset_type'] = body.get('last_reset_type', None)
  359. r_body['uuid'] = body.get('uuid', None)
  360. r_body['run_time'] = body.get('run_time', None)
  361. r_body['receive_sum'] = body.get('receive_sum', None)
  362. r_body['send_sum'] = body.get('send_sum', None)
  363. r_body['network_type'] = body.get('network_type', None)
  364. r_body['cloud_platform'] = body.get('cloud_platform', None)
  365. r_body['sim_status'] = body.get('sim_status', None)
  366. r_body['free_fifo'] = body.get('free_fifo', None)
  367. r_body['ip'] = body.get('ip', None)
  368. # 至少有1个不为None,则继续
  369. if any(r_body.values()):
  370. # 先整理一下字段
  371. if r_body['network_type'] is not None:
  372. if r_body['network_type'] == 0:
  373. r_body['network_type'] = 'WiFi'
  374. elif r_body['network_type'] == 1:
  375. r_body['network_type'] = '有线网'
  376. else:
  377. r_body['network_type'] = '未知'
  378. if r_body['cloud_platform'] is not None:
  379. if r_body['cloud_platform'] == 0:
  380. r_body['cloud_platform'] = '阿里云生产'
  381. elif r_body['cloud_platform'] == 1:
  382. r_body['cloud_platform'] = '阿里云测试'
  383. else:
  384. r_body['cloud_platform'] = '未知'
  385. # 有变化,才更新
  386. is_changed = False
  387. for k, v in r_body.items():
  388. if v is not None:
  389. if k not in device or device[k] != v:
  390. is_changed = True
  391. device[k] = v
  392. if is_changed:
  393. logger.info(f"云平台:{platform_id}, 设备 {dev_sn} 接收到MQTT消息,状态更新")
  394. save_devices(devices)
  395. except Exception as e:
  396. logger.error(f"解析数据失败 - 云平台: {platform_id}, 错误: {str(e)}")
  397. # MQTT客户端线程
  398. def mqtt_client_thread(platform_id):
  399. platform_d = load_platforms()
  400. if platform_id not in platform_d['platform_l']:
  401. logger.error(f"云平台 {platform_id} 不存在")
  402. return
  403. platform = platform_d['platform_l'][platform_id]
  404. client = mqtt.Client(client_id=platform['client_id'])
  405. client.username_pw_set(platform['username'], platform['password'])
  406. # platform['pub_topic'],
  407. # platform['sub_topic']
  408. client.user_data_set({
  409. 'platform_id': platform_id,
  410. 'pub_topic': mqtt_pub_topic,
  411. 'sub_topic': mqtt_sub_topic
  412. })
  413. client.on_connect = on_connect
  414. client.on_disconnect = on_disconnect
  415. client.on_message = on_message
  416. with mqtt_lock:
  417. mqtt_clients[platform_id] = client
  418. try:
  419. client.connect(platform['ip'], platform['port'], 60)
  420. client.loop_forever()
  421. except Exception as e:
  422. logger.error(f"MQTT线程错误 - 云平台: {platform_id}, 错误: {str(e)}")
  423. finally:
  424. # 确保客户端已断开连接
  425. try:
  426. client.disconnect()
  427. except Exception as e:
  428. logger.error(f"MQTT线程结束时断开连接失败 - 云平台: {platform_id}, 错误: {str(e)}")
  429. with mqtt_lock:
  430. if platform_id in mqtt_clients:
  431. del mqtt_clients[platform_id]
  432. if platform_id in mqtt_threads:
  433. del mqtt_threads[platform_id]
  434. try:
  435. client.disconnect()
  436. except:
  437. pass
  438. # MQTT控制函数
  439. def start_mqtt_client(platform_id):
  440. with mqtt_lock:
  441. if platform_id in mqtt_threads and mqtt_threads[platform_id].is_alive():
  442. logger.info(f"MQTT客户端已运行 - 云平台: {platform_id}")
  443. return True
  444. thread = threading.Thread(target=mqtt_client_thread, args=(platform_id,), daemon=True)
  445. with mqtt_lock:
  446. mqtt_threads[platform_id] = thread
  447. thread.start()
  448. logger.info(f"启动MQTT客户端线程 - 云平台: {platform_id}")
  449. return True
  450. mqtt_last_activity = {'last_time':time.time()}
  451. # 定期检查所有MQTT连接状态
  452. def check_all_mqtt_connections():
  453. global mqtt_status_check_timer
  454. try:
  455. with mqtt_lock:
  456. platform_d = load_platforms()
  457. if 'platform_l' not in platform_d:
  458. return
  459. for platform_id in platform_d['platform_l']:
  460. # 检查MQTT线程是否存活
  461. # is_connected = platform_id in mqtt_threads and mqtt_threads[platform_id].is_alive()
  462. is_connected = platform_id in mqtt_clients and mqtt_clients[platform_id].is_connected()
  463. current_status = platform_d['platform_l'][platform_id].get('status', '')
  464. # logger.info(f"检查云平台状态 - 云平台: {platform_id}, 当前状态: {current_status}, 是否连接: {is_connected}")
  465. # 如果状态不一致,则更新
  466. if (is_connected and current_status != 'connected') or (not is_connected and current_status != 'disconnected'):
  467. logger.info(f"参数:platform_id={platform_id}, is_connected={is_connected}, current_status ={current_status}")
  468. platform_d['platform_l'][platform_id]['status'] = 'connected' if is_connected else 'disconnected'
  469. logger.info(f"更新云平台状态 - 云平台: {platform_id}, 新状态: {platform_d['platform_l'][platform_id]['status']}")
  470. save_platforms(platform_d)
  471. except Exception as e:
  472. logger.error(f"检查MQTT连接状态出错: {str(e)}")
  473. finally:
  474. # 设置下一次检查(2秒后)
  475. mqtt_status_check_timer = threading.Timer(2, check_all_mqtt_connections)
  476. mqtt_status_check_timer.daemon = True
  477. mqtt_status_check_timer.start()
  478. # 启动定时检查
  479. mqtt_status_check_timer = None
  480. def start_mqtt_status_check():
  481. global mqtt_status_check_timer
  482. if mqtt_status_check_timer is not None:
  483. mqtt_status_check_timer.cancel()
  484. check_all_mqtt_connections()
  485. def stop_mqtt_client(platform_id):
  486. logger.info(f"断开MQTT连接 - 云平台: {platform_id}")
  487. client = None
  488. # 先获取客户端引用,尽量缩短锁的持有时间
  489. with mqtt_lock:
  490. if platform_id in mqtt_clients:
  491. client = mqtt_clients[platform_id]
  492. # 从字典中移除,避免其他线程访问
  493. del mqtt_clients[platform_id]
  494. # 在锁外执行disconnect,避免死锁
  495. if client:
  496. try:
  497. client.disconnect()
  498. logger.info(f"断开MQTT连接 - 云平台: {platform_id}")
  499. except Exception as e:
  500. logger.error(f"断开MQTT连接失败 - 云平台: {platform_id}, 错误: {str(e)}")
  501. with mqtt_lock:
  502. if platform_id in mqtt_threads:
  503. logger.info(f"等待MQTT线程结束 - 云平台: {platform_id}")
  504. return True
  505. def publish_mqtt_message(platform_id, message, pub_topic=mqtt_pub_topic):
  506. with mqtt_lock:
  507. if platform_id not in mqtt_clients:
  508. logger.error(f"MQTT客户端未连接 - 云平台: {platform_id}")
  509. return False
  510. client = mqtt_clients[platform_id]
  511. platform_d = load_platforms()
  512. # platform = platform_d['platform_l'].get(platform_id, {})
  513. # pub_topic = mqtt_pub_topic # platform.get('pub_topic', '/device/data')
  514. try:
  515. result = client.publish(pub_topic, message, qos=0)
  516. result.wait_for_publish()
  517. if(type(message) == str):
  518. logger.info(f"发布消息成功 - 云平台: {platform_id}, 主题: {pub_topic}, 消息: {message}")
  519. else:
  520. logger.info(f"发布消息成功 - 云平台: {platform_id}, 主题: {pub_topic}, 消息: {message.hex().upper()}")
  521. return True
  522. except Exception as e:
  523. logger.error(f"发布消息失败 - 云平台: {platform_id}, 错误: {str(e)}")
  524. return False
  525. # 辅助函数
  526. def allowed_file(filename):
  527. return '.' in filename and \
  528. filename.rsplit('.', 1)[1].lower() in app.config['ALLOWED_EXTENSIONS']
  529. # 登录验证装饰器
  530. def login_required(f):
  531. @wraps(f)
  532. def decorated_function(*args, **kwargs):
  533. if 'logged_in' not in session:
  534. return redirect(url_for('login', next=request.url))
  535. return f(*args, **kwargs)
  536. return decorated_function
  537. # 路由
  538. @app.route('/')
  539. @login_required
  540. def index():
  541. platform_d = load_platforms()
  542. return render_template(
  543. 'index.html',
  544. title='主页',
  545. # cccc platform_l=platform_d['platform_l'],
  546. platform_c=load_platforms_c(),
  547. current_platform=platform_d['current_platform']
  548. )
  549. @app.route('/api/device_detail/<dev_sn>')
  550. @login_required
  551. def get_device_detail(dev_sn):
  552. devices = load_devices()
  553. device = next((d for d in devices if d['dev_sn'] == dev_sn), None)
  554. if not device:
  555. return jsonify({'error': '设备不存在'}), 404
  556. device['abnormal_sum'] = int(device['receive_sum']) - int(device['send_sum'])
  557. device['success_rate'] = 0 if int(device['receive_sum']) == 0 else int(device['send_sum']) / int(device['receive_sum'])
  558. device['speed'] = 0 if int(device['run_time']) == 0 else int(device['send_sum']) / int(device['run_time'] * 100)
  559. return jsonify(device)
  560. @app.route('/login', methods=['GET', 'POST'])
  561. def login():
  562. if request.method == 'POST':
  563. username = request.form['username']
  564. password = request.form['password']
  565. users = load_users()
  566. if username in users and users[username] == password:
  567. session['logged_in'] = True
  568. session['username'] = username
  569. next_page = request.args.get('next', url_for('index'))
  570. return redirect(next_page)
  571. else:
  572. error = '用户名或密码错误'
  573. return render_template('login.html', error=error)
  574. return render_template('login.html', title='用户登录')
  575. @app.route('/logout')
  576. def logout():
  577. session.pop('logged_in', None)
  578. session.pop('username', None)
  579. return redirect(url_for('login'))
  580. @app.route('/app_manage')
  581. @login_required
  582. def app_manage():
  583. logger.info("进入应用管理页面")
  584. return render_template('app_manage.html', title='应用管理')
  585. # 应用管理相关API
  586. @app.route('/api/restart_app', methods=['POST'])
  587. @login_required
  588. def restart_app():
  589. try:
  590. logger.info("接收到重启应用请求")
  591. # 在实际应用中,这里需要实现安全的重启逻辑
  592. # 这里只是模拟重启
  593. time.sleep(2) # 模拟重启过程
  594. s_p = sys_platform.system().upper()
  595. if s_p == 'WINDOWS':
  596. os.system('python restart_app.py')
  597. elif s_p == 'LINUX':
  598. os.system('./sh_web_app_2006_Svr.sh restart')
  599. return jsonify({"status": "restarting"}), 200
  600. # logger.info("应用重启成功")
  601. # return jsonify({'success': True, 'message': '应用重启成功,请等待服务重新启动...'})
  602. except Exception as e:
  603. logger.error(f"应用重启失败: {str(e)}")
  604. return jsonify({'success': False, 'message': f'重启失败: {str(e)}'})
  605. @app.route('/api/update_app', methods=['POST'])
  606. @login_required
  607. def update_app():
  608. try:
  609. logger.info("接收到更新应用请求")
  610. if 'app_file' not in request.files:
  611. return jsonify({'success': False, 'message': '没有文件被上传'})
  612. file = request.files['app_file']
  613. if file.filename == '':
  614. return jsonify({'success': False, 'message': '没有选择文件'})
  615. # 检查文件类型
  616. if not (file.filename.endswith('.py') or (file.filename.endswith('.html') or file.filename.endswith('.zip'))):
  617. return jsonify({'success': False, 'message': '只支持Python文件(.py)、HTML文件(.html)或压缩包(.zip)'})
  618. # 保存文件到临时目录
  619. temp_dir = app.config['APP_UPGREADE_FOLDER_TEMP']
  620. os.makedirs(temp_dir, exist_ok=True)
  621. bk_dir = 'app_upgrade_bk'
  622. os.makedirs(bk_dir, exist_ok=True)
  623. if file.filename == 'web_app_2006.py':
  624. shutil.copy2(file.filename, os.path.join(bk_dir, file.filename+f'_{APP_VERSION}_bk_{time.time()}'))
  625. file.save(file.filename)
  626. elif file.filename == 'frame.py':
  627. shutil.copy2(os.path.join('pos_manager', file.filename), os.path.join(bk_dir, file.filename+f'_bk_{time.time()}'))
  628. file.save(os.path.join('pos_manager', file.filename))
  629. elif file.filename.endswith('.html') :
  630. shutil.copy2(os.path.join('templates', file.filename), os.path.join(bk_dir, file.filename+f'_bk_{time.time()}'))
  631. file.save(os.path.join('templates', file.filename))
  632. elif file.filename.endswith('.zip') :
  633. file.save(os.path.join(temp_dir, file.filename))
  634. '''
  635. # 解压文件
  636. with zipfile.ZipFile(os.path.join(temp_dir, file.filename), 'r') as zip_ref:
  637. zip_ref.extractall(temp_dir)
  638. # 移动解压后的文件到目标目录
  639. for item in os.listdir(temp_dir):
  640. s = os.path.join(temp_dir, item)
  641. d = os.path.join(app.config['APP_UPGREADE_FOLDER'], item)
  642. if os.path.isdir(s):
  643. shutil.move(s, d)
  644. else:
  645. shutil.copy2(s, d)
  646. # 删除临时文件
  647. os.remove(os.path.join(temp_dir, file.filename))
  648. '''
  649. # 在实际应用中,这里需要实现安全的更新逻辑
  650. # 例如验证文件完整性、备份原有文件、应用更新等
  651. logger.info(f"文件 {file.filename} 上传成功")
  652. return jsonify({'success': True, 'message': f'文件上传成功,重启后生效. {file.filename}, {datetime.now().strftime("%Y-%m-%d %H:%M:%S")}'})
  653. except Exception as e:
  654. logger.error(f"应用更新失败: {str(e)}")
  655. return jsonify({'success': False, 'message': f'更新失败: {str(e)}'})
  656. @app.route('/api/get_logs')
  657. @login_required
  658. def get_logs():
  659. try:
  660. lines = request.args.get('lines', '50')
  661. # logger.info(f"请求获取日志,行数: {lines}")
  662. get_log_file = LOG_FILE
  663. s_p = sys_platform.system().upper()
  664. '''
  665. if s_p == 'WINDOWS':
  666. get_log_file = LOG_FILE
  667. elif s_p == 'LINUX':
  668. get_log_file = 'log.log'
  669. '''
  670. # 检查日志文件是否存在
  671. if not os.path.exists(get_log_file):
  672. return f"日志文件 {get_log_file} 不存在"
  673. # 读取日志文件
  674. with open(get_log_file, 'r', encoding='utf-8') as f:
  675. if lines == 'all':
  676. logs = ''.join(f.readlines()[-500:]) # 最大500行
  677. else:
  678. try:
  679. line_count = int(lines)
  680. logs = ''.join(f.readlines()[-line_count:])
  681. except ValueError:
  682. logs = ''.join(f.readlines()[-50:]) # 默认50行
  683. return logs
  684. except Exception as e:
  685. logger.error(f"获取日志失败: {str(e)}")
  686. return f"获取日志失败: {str(e)}"
  687. @app.route('/firmware', methods=['GET', 'POST'])
  688. @login_required
  689. def firmware():
  690. firmwares = load_firmwares()
  691. if request.method == 'POST':
  692. if 'file' not in request.files:
  693. error = '未选择文件'
  694. return render_template('firmware.html', firmwares=firmwares, error=error)
  695. file = request.files['file']
  696. remark = request.form.get('remark', '').strip()
  697. if file.filename == '':
  698. error = '未选择文件'
  699. return render_template('firmware.html', firmwares=firmwares, error=error)
  700. if not remark:
  701. error = '备注信息不能为空'
  702. return render_template('firmware.html', firmwares=firmwares, error=error)
  703. if file and allowed_file(file.filename):
  704. filename = file.filename
  705. # 检查文件名是否重复
  706. if any(f['filename'] == filename for f in firmwares):
  707. error = f'文件名 "{filename}" 已存在,请更改文件名后再上传'
  708. return render_template('firmware.html', firmwares=firmwares, error=error)
  709. # 检查文件大小
  710. file.seek(0, os.SEEK_END) # 移动指针到文件末尾
  711. file_size = file.tell() # 获取指针位置(即文件大小)
  712. file.seek(0) # 重置指针到文件开头
  713. # 打印文件大小
  714. logger.info(f"文件名: {filename}, 文件大小: {file_size} 字节")
  715. if file_size > 1 * 1024 * 1024: # 1MB
  716. error = f'文件大小不能超过1MB, 当前文件大小: {file_size} 字节'
  717. return render_template('firmware.html', firmwares=firmwares, error=error)
  718. if file_size < 1 * 1024: # 1KB
  719. error = f'文件大小不能小于1KB, 当前文件大小: {file_size} 字节'
  720. return render_template('firmware.html', firmwares=firmwares, error=error)
  721. file.save(os.path.join(app.config['UPLOAD_FOLDER'], filename))
  722. new_id = max(f['id'] for f in firmwares) + 1 if firmwares else 1
  723. firmwares.append({
  724. 'id': new_id,
  725. 'filename': filename,
  726. 'filesize': '%d 字节' % file_size,
  727. 'upload_time': datetime.now().strftime('%Y-%m-%d %H:%M:%S'),
  728. 'remark': remark
  729. })
  730. save_firmwares(firmwares)
  731. return redirect(url_for('firmware'))
  732. # 处理编辑操作
  733. edit_id = request.args.get('edit')
  734. if edit_id:
  735. new_remark = request.args.get('remark', '').strip()
  736. if new_remark:
  737. for f in firmwares:
  738. if f['id'] == int(edit_id):
  739. f['remark'] = new_remark
  740. break
  741. save_firmwares(firmwares)
  742. return redirect(url_for('firmware'))
  743. # 处理删除操作
  744. delete_id = request.args.get('delete')
  745. if delete_id:
  746. firmwares = [f for f in firmwares if f['id'] != int(delete_id)]
  747. save_firmwares(firmwares)
  748. return redirect(url_for('firmware'))
  749. # 处理下载操作
  750. download_id = request.args.get('download')
  751. if download_id:
  752. firmware = next((f for f in firmwares if f['id'] == int(download_id)), None)
  753. if firmware:
  754. filename = firmware['filename']
  755. file_path = os.path.join(app.config['UPLOAD_FOLDER'], filename)
  756. if os.path.exists(file_path):
  757. return send_from_directory(app.config['UPLOAD_FOLDER'], filename, as_attachment=True)
  758. else:
  759. error = f'文件 "{filename}" 不存在'
  760. return render_template('firmware.html', firmwares=firmwares, error=error)
  761. else:
  762. error = f'未找到ID为 {download_id} 的固件'
  763. return render_template('firmware.html', firmwares=firmwares, error=error)
  764. return render_template('firmware.html', firmwares=firmwares, title='固件包管理')
  765. @app.route('/devices', methods=['GET', 'POST'])
  766. @login_required
  767. def devices():
  768. devices = load_devices()
  769. platform_l = load_platforms()['platform_l']
  770. firmwares = load_firmwares()
  771. if request.method == 'POST':
  772. # 处理设备配置保存
  773. if 'save_config' in request.form:
  774. config_type = request.form['config_type']
  775. dev_sn = request.form['dev_sn']
  776. target_device = next((device for device in devices if device['dev_sn'] == dev_sn), None)
  777. if target_device:
  778. if config_type == 'all':
  779. # 处理保存所有配置
  780. target_device_wifi_ssid = request.form.get('ssid', '')
  781. target_device_wifi_password = request.form.get('wifi_password', '')
  782. target_device_cloud_platform = request.form.get('cloud-platform', '')
  783. target_device_network_type = request.form.get('network-type', '')
  784. log_msg = f"{target_device['dev_type'].zfill(4)}-{target_device['dev_sn'].zfill(10)}:"
  785. log_msg = log_msg + f"WiFi参数:{target_device_wifi_ssid},{target_device_wifi_password};"
  786. log_msg = log_msg + f"云平台参数:{target_device_cloud_platform};"
  787. log_msg = log_msg + f"网络类型参数:{target_device_network_type}"
  788. logger.info(log_msg)
  789. logger.info("TODO: 发送保存所有配置到设备")
  790. elif config_type == 'wifi':
  791. target_device['wifi_ssid'] = request.form.get('ssid', '')
  792. target_device['wifi_password'] = request.form.get('wifi_password', '')
  793. log_msg = f"WiFi参数:{target_device['dev_type'].zfill(4)}-{target_device['dev_sn'].zfill(10)},{target_device['wifi_ssid']},{target_device['wifi_password']}"
  794. logger.info(log_msg)
  795. logger.info("TODO: 发送WiFi配置到设备")
  796. # platform_id = next((platform_id for platform_id in platform_l if platform_l[platform_id]['name'] == target_device['cloud_platform']), None)
  797. platform_id = load_platforms()['current_platform']
  798. pub_data = frame.get_msg_config_wifi(device=target_device,data="")
  799. pub_topic = f"cpyypt/down/{target_device['dev_type'].zfill(4)}/{target_device['dev_sn'].zfill(10)}"
  800. success = publish_message_dev(platform_id=platform_id, data=pub_data, pub_topic=pub_topic)
  801. if not success: return render_template(
  802. 'device_config.html',
  803. device=target_device,
  804. # cccc platform_l=platform_l.values(),
  805. platform_c=load_platforms_c().values(),
  806. success=False,
  807. message=f"消息发布失败,MQTT客户端未连接 -- {platform_id},请返回主页 【连接】!"
  808. )
  809. elif config_type == 'platform':
  810. log_msg = f"云平台参数:{target_device['dev_type'].zfill(4)}-{target_device['dev_sn'].zfill(10)},{target_device['cloud_platform']}"
  811. logger.info(log_msg)
  812. logger.info("TODO: 发送云平台配置到设备")
  813. # platform_id = next((platform_id for platform_id in platform_l if platform_l[platform_id]['name'] == target_device['cloud_platform']), None)
  814. platform_id = load_platforms()['current_platform']
  815. pub_data = frame.get_msg_config_platform(device=target_device,data=request.form.get('cloud-platform', ''))
  816. pub_topic = f"cpyypt/down/{target_device['dev_type'].zfill(4)}/{target_device['dev_sn'].zfill(10)}"
  817. success = publish_message_dev(platform_id=platform_id, data=pub_data, pub_topic=pub_topic)
  818. if not success: return render_template(
  819. 'device_config.html',
  820. device=target_device,
  821. # cccc platform_l=platform_l.values(),
  822. platform_c=load_platforms_c().values(),
  823. success=False,
  824. message=f"消息发布失败,MQTT客户端未连接 -- {platform_id},请返回主页 【连接】!"
  825. )
  826. target_device['cloud_platform'] = request.form.get('cloud-platform', '')
  827. elif config_type == 'network':
  828. target_device['network_type'] = request.form.get('network-type', '')
  829. log_msg = f"网络类型参数:{target_device['dev_type'].zfill(4)}-{target_device['dev_sn'].zfill(10)},{target_device['network_type']}"
  830. logger.info(log_msg)
  831. logger.info("TODO: 发送网络类型配置到设备")
  832. # platform_id = next((platform_id for platform_id in platform_l if platform_l[platform_id]['name'] == target_device['cloud_platform']), None)
  833. platform_id = load_platforms()['current_platform']
  834. pub_data = frame.get_msg_config_network(device=target_device,data="")
  835. pub_topic = f"cpyypt/down/{target_device['dev_type'].zfill(4)}/{target_device['dev_sn'].zfill(10)}"
  836. success = publish_message_dev(platform_id=platform_id, data=pub_data, pub_topic=pub_topic)
  837. if not success: return render_template(
  838. 'device_config.html',
  839. device=target_device,
  840. # cccc platform_l=platform_l.values(),
  841. platform_c=load_platforms_c().values(),
  842. success=False,
  843. message=f"消息发布失败,MQTT客户端未连接 -- {platform_id},请返回主页 【连接】!"
  844. )
  845. save_devices(devices)
  846. return render_template(
  847. 'device_config.html',
  848. device=target_device,
  849. # cccc platform_l=platform_l.values(),
  850. platform_c=load_platforms_c().values(),
  851. success=True,
  852. message="操作成功,配置已下发!"
  853. )
  854. # 处理批量操作
  855. action = request.form.get('action')
  856. selected_sns = [dev_sn for dev_sn in request.form.getlist('dev_sn')]
  857. if action == 'restart':
  858. # 实际应用中这里会发送重启命令到设备
  859. logger.info(f"批量重启设备: {selected_sns}")
  860. for dev_sn in selected_sns:
  861. target_device = next((device for device in devices if device['dev_sn'] == dev_sn), None)
  862. if target_device:
  863. platform_id = load_platforms()['current_platform']
  864. pub_data = frame.get_msg_restart(device=target_device,data="")
  865. pub_topic = f"cpyypt/down/{target_device['dev_type'].zfill(4)}/{target_device['dev_sn'].zfill(10)}"
  866. success = publish_message_dev(platform_id=platform_id, data=pub_data, pub_topic=pub_topic)
  867. if not success:
  868. logger.error(f"消息发布失败,MQTT客户端未连接 -- {load_platforms_c()[platform_id]['name']},请返回主页 【连接】!")
  869. else:
  870. logger.info(f"消息发布成功 -- dev_sn:{dev_sn}")
  871. elif action == 'upgrade':
  872. firmware_id = request.form.get('firmware_id')
  873. firmware = next((fm for fm in firmwares if fm['id'] == int(firmware_id)), None)
  874. logger.info(f"批量升级设备 {selected_sns} 到固件 {firmware['filename']}")
  875. if not firmware:
  876. return jsonify({'status': 'error', 'message': '固件信息不存在'})
  877. # 获取文件大小
  878. bin_file=os.path.join(app.config['UPLOAD_FOLDER'], firmware['filename'])
  879. try:
  880. file_size = os.path.getsize(bin_file)
  881. logger.info(f"文件大小: {file_size} 字节")
  882. except FileNotFoundError:
  883. logger.error(f"错误: 文件 '{bin_file}' 不存在")
  884. return jsonify({'status': 'error', 'message': f'文件{bin_file}名不存在'})
  885. # 找到第1个被升级设备
  886. device = next((device for device in devices if device['dev_sn'] == selected_sns[0]), None)
  887. dev_platform = device.get('cloud_platform', '')
  888. platform_id = next((platform_id for platform_id in platform_l if platform_l[platform_id]['name'] == dev_platform), None)
  889. with open(bin_file, 'rb') as f:
  890. bin_data = f.read()
  891. dev_upgrade_dict['status'] = 'idel'
  892. dev_upgrade_dict['current_sn'] = ''
  893. dev_upgrade_dict['dev_type'] = device.get('dev_type', '2006')
  894. dev_upgrade_dict['dev_upgrade_sns'] = selected_sns
  895. dev_upgrade_dict['interval'] = 1
  896. dev_upgrade_dict['bin_data'] = bin_data
  897. dev_upgrade_dict['bin_len'] = file_size
  898. dev_upgrade_dict['pkg_size'] = 512
  899. dev_upgrade_dict['pkg_total'] = math.ceil(dev_upgrade_dict['bin_len'] / dev_upgrade_dict['pkg_size']) # 向上取整
  900. dev_upgrade_dict['pkg_cnt'] = 0
  901. dev_upgrade_dict['platform_id'] = platform_id
  902. start_dev_upgrade_check()
  903. return jsonify({'status': 'success', 'message': '升级任务已启动'})
  904. elif action == 'config':
  905. logger.info(f"批量配置设备: {selected_sns}")
  906. # 显示设备配置页面
  907. dev_sn = request.args.get('dev_sn')
  908. if dev_sn:
  909. target_device = next((device for device in devices if device['dev_sn'] == dev_sn), None)
  910. if target_device:
  911. return render_template(
  912. 'device_config.html',
  913. device=target_device,
  914. # cccc platform_l=platform_l.values(),
  915. platform_c=load_platforms_c().values(),
  916. title='设备配置'
  917. )
  918. return render_template('devices.html', devices=devices, title='解密机配置')
  919. @app.route('/device_detail/<dev_sn>')
  920. @login_required
  921. def device_detail(dev_sn):
  922. devices = load_devices()
  923. device = next((d for d in devices if d['dev_sn'] == dev_sn), None)
  924. if not device:
  925. return '设备不存在', 404
  926. return render_template('device_detail.html', device=device, title='设备详情')
  927. @app.route('/api/get_firmwares')
  928. @login_required
  929. def get_firmwares():
  930. firmwares = load_firmwares()
  931. return jsonify(firmwares)
  932. # 云平台相关API
  933. @app.route('/api/select_platform', methods=['POST'])
  934. @login_required
  935. def select_platform():
  936. platform_id = request.json.get('platform_id')
  937. if not platform_id:
  938. return jsonify({'status': 'error', 'message': '请选择云平台'})
  939. platform_d = load_platforms()
  940. if platform_id not in platform_d['platform_l']:
  941. return jsonify({'status': 'error', 'message': '云平台不存在'})
  942. platform_d['current_platform'] = platform_id
  943. save_platforms(platform_d)
  944. selected_platform = platform_d['platform_l'][platform_id]
  945. return jsonify({
  946. 'status': 'success',
  947. 'platform': {
  948. 'id': platform_id,
  949. 'name': selected_platform['name'],
  950. 'status': selected_platform['status']
  951. }
  952. })
  953. @app.route('/api/toggle_connection', methods=['POST'])
  954. @login_required
  955. def toggle_connection():
  956. platform_id = request.json.get('platform_id')
  957. if not platform_id:
  958. return jsonify({'status': 'error', 'message': '请指定云平台'})
  959. platform_d = load_platforms()
  960. if platform_id not in platform_d['platform_l']:
  961. return jsonify({'status': 'error', 'message': '云平台不存在'})
  962. current_platform = platform_d['platform_l'][platform_id]
  963. if current_platform['status'] == 'connected':
  964. stop_mqtt_client(platform_id)
  965. new_status = 'disconnected'
  966. logger.info(f"云平台 {platform_id} 已断开")
  967. else:
  968. start_mqtt_client(platform_id)
  969. new_status = 'connected'
  970. logger.info(f"云平台 {platform_id} 已连接")
  971. current_platform['status'] = new_status
  972. save_platforms(platform_d)
  973. return jsonify({
  974. 'status': 'success',
  975. 'new_status': new_status,
  976. 'platform_name': current_platform['name']
  977. })
  978. @app.route('/api/get_platform_status', methods=['GET'])
  979. @login_required
  980. def get_platform_status():
  981. platform_id = request.args.get('platform_id')
  982. if not platform_id:
  983. return jsonify({"status": "error", "message": "未指定云平台ID"}), 400
  984. platform_d = load_platforms()
  985. if platform_id not in platform_d['platform_l']:
  986. return jsonify({"status": "error", "message": "云平台不存在"}), 404
  987. return jsonify({
  988. "status": "success",
  989. "platform": platform_d['platform_l'][platform_id]
  990. })
  991. @app.route('/api/publish_message', methods=['POST'])
  992. @login_required
  993. def publish_message():
  994. data = request.json
  995. platform_id = data.get('platform_id')
  996. message = data.get('message')
  997. if not platform_id or not message:
  998. return jsonify({"status": "error", "message": "云平台ID和消息内容都不能为空"}), 400
  999. success = publish_mqtt_message(platform_id, message)
  1000. return jsonify({
  1001. "status": "success" if success else "error",
  1002. "message": "消息发布成功" if success else "消息发布失败"
  1003. })
  1004. # .......................
  1005. def publish_message_dev(platform_id, data, pub_topic):
  1006. if not platform_id or not data or not pub_topic:
  1007. # return jsonify({"status": "error", "message": "云平台ID、消息内容、发布主题 都不能为空"}), 400
  1008. logger.error(f"publish_message_dev: platform_id={platform_id}, data={data}, pub_topic={pub_topic}")
  1009. return False
  1010. success = publish_mqtt_message(platform_id, data, pub_topic=pub_topic)
  1011. # if not success:
  1012. # logger.error(f"publish_message_dev: platform_id={platform_id}, data={data}, pub_topic={pub_topic}, success={success}")
  1013. return success
  1014. # 定期检查设备状态
  1015. dev_upgrade_dict = {
  1016. 'status' : 'idel',
  1017. 'current_sn' : '',
  1018. 'dev_type' : '2006',
  1019. 'interval' : 1,
  1020. 'bin_data' : None,
  1021. 'bin_len' : 0,
  1022. 'pkg_size' : 512,
  1023. 'pkg_total' : 0,
  1024. 'pkg_cnt':0,
  1025. 'remain':0,
  1026. 'platform_id' : None,
  1027. 'dev_upgrade_sns' : []
  1028. }
  1029. def dev_upgrade_dict_clr():
  1030. dev_upgrade_dict['status'] = 'idel'
  1031. dev_upgrade_dict['current_sn'] = ''
  1032. dev_upgrade_dict['dev_type'] = '2006'
  1033. dev_upgrade_dict['interval'] = 1
  1034. dev_upgrade_dict['bin_data'] = None
  1035. dev_upgrade_dict['bin_len'] = 0
  1036. dev_upgrade_dict['pkg_size'] = 512
  1037. dev_upgrade_dict['pkg_total'] = 0
  1038. dev_upgrade_dict['pkg_cnt'] = 0
  1039. dev_upgrade_dict['remain'] = 0
  1040. dev_upgrade_dict['dev_upgrade_sns'] = []
  1041. def dev_upgrade_check():
  1042. devices = load_devices()
  1043. if dev_upgrade_dict['current_sn'] == '':
  1044. if len(dev_upgrade_dict['dev_upgrade_sns']) > 0:
  1045. dev_upgrade_dict['current_sn'] = dev_upgrade_dict['dev_upgrade_sns'][0]
  1046. else:
  1047. dev_upgrade_dict['current_sn'] = ''
  1048. else:
  1049. device = next((device for device in devices if device['dev_sn'] == dev_upgrade_dict['current_sn']), None)
  1050. # 此处为状态机,需要完善一下,可用。dev_upgrade_dict['status']
  1051. # 升级 状态
  1052. if dev_upgrade_dict['status'] == 'idel':
  1053. logger.info(f'idel,do somethime,{device["dev_sn"]}')
  1054. dev_upgrade_dict['status'] = 'reqeuest'
  1055. dev_upgrade_dict['pkg_cnt'] = 0
  1056. dev_upgrade_dict['remain'] = dev_upgrade_dict['bin_len']
  1057. # 请求 状态
  1058. elif dev_upgrade_dict['status'] == 'reqeuest':
  1059. logger.info(f'request,do somethime,{device["dev_sn"]}')
  1060. dev_upgrade_dict['status'] = 'upgrade'
  1061. pub_data = frame.get_msg_upgrade_1001(device=device)
  1062. pub_topic = f"cpyypt/down/{device['dev_type'].zfill(4)}/{device['dev_sn'].zfill(10)}"
  1063. platform_id = load_platforms()['current_platform']
  1064. success = publish_message_dev(platform_id=platform_id, data=pub_data, pub_topic=pub_topic)
  1065. if not success:
  1066. dev_upgrade_dict_clr()
  1067. logger.error(f"dev update failed: platform_id={device['cloud_platform']}, sn={device['dev_sn']}")
  1068. return
  1069. else:
  1070. # time.sleep(2)
  1071. dev_upgrade_dict['status'] = 'upgrade'
  1072. dev_upgrade_dict['interval'] = 0.1
  1073. # 升级 状态
  1074. elif dev_upgrade_dict['status'] == 'upgrade':
  1075. logger.info(f'upgrade,do somethime,{device["dev_sn"]},{dev_upgrade_dict["pkg_cnt"]}/{dev_upgrade_dict["pkg_total"]}')
  1076. if dev_upgrade_dict['remain'] > 0:
  1077. data = dev_upgrade_dict['bin_data'][
  1078. dev_upgrade_dict['pkg_cnt'] * dev_upgrade_dict['pkg_size']: (dev_upgrade_dict['pkg_cnt'] + 1) *
  1079. dev_upgrade_dict['pkg_size']]
  1080. pub_data = frame.get_msg_upgrade_1030(device=device,
  1081. fileSum=dev_upgrade_dict['bin_len'],
  1082. maxPkgId=dev_upgrade_dict['pkg_total'] - 1,
  1083. curPkgId=dev_upgrade_dict['pkg_cnt'],
  1084. curPkgsize=len(data),
  1085. data=data)
  1086. pub_topic = f"cpyypt/down/{device['dev_type'].zfill(4)}/{device['dev_sn'].zfill(10)}"
  1087. platform_id = load_platforms()['current_platform']
  1088. success = publish_message_dev(platform_id=platform_id, data=pub_data, pub_topic=pub_topic)
  1089. if not success:
  1090. dev_upgrade_dict_clr()
  1091. logger.error(f"dev update failed: platform_id={device['cloud_platform']}, sn={device['dev_sn']}")
  1092. return
  1093. # time.sleep(0.1)
  1094. dev_upgrade_dict['status'] = 'upgrade'
  1095. else:
  1096. dev_upgrade_dict['status'] = 'finish'
  1097. dev_upgrade_dict['pkg_cnt'] += 1
  1098. dev_upgrade_dict['remain'] = dev_upgrade_dict['bin_len'] - dev_upgrade_dict['pkg_cnt'] * dev_upgrade_dict[
  1099. 'pkg_size']
  1100. # 完成 状态
  1101. elif dev_upgrade_dict['status'] == 'finish':
  1102. logger.info(f'finish,do somethime,{device["dev_sn"]}')
  1103. dev_upgrade_dict['status'] = 'idel'
  1104. dev_upgrade_dict['pkg_cnt'] = 0
  1105. dev_upgrade_dict['remain'] = 0
  1106. dev_upgrade_dict['interval'] = 1
  1107. dev_upgrade_dict['dev_upgrade_sns'].remove(dev_upgrade_dict['current_sn'])
  1108. if len(dev_upgrade_dict['dev_upgrade_sns']) > 0:
  1109. dev_upgrade_dict['current_sn'] = dev_upgrade_dict['dev_upgrade_sns'][0]
  1110. else:
  1111. dev_upgrade_dict['current_sn'] = ''
  1112. save_devices(devices)
  1113. if dev_upgrade_dict['current_sn'] == '' and len(dev_upgrade_dict['dev_upgrade_sns']) == 0:
  1114. logger.info('dev_upgrade_check exit')
  1115. else:
  1116. dev_upgrade_check_timer = threading.Timer(dev_upgrade_dict['interval'], dev_upgrade_check)
  1117. dev_upgrade_check_timer.daemon = True
  1118. dev_upgrade_check_timer.start()
  1119. # 启动定时检查 升级状态
  1120. dev_upgrade_check_timer = None
  1121. def start_dev_upgrade_check():
  1122. global dev_upgrade_check_timer
  1123. if dev_upgrade_check_timer is not None:
  1124. dev_upgrade_check_timer.cancel()
  1125. dev_upgrade_check()
  1126. # 启动定时检查 在线状态
  1127. dev_online_check_timer = None
  1128. def dev_online_check():
  1129. devices = load_devices()
  1130. for device in devices:
  1131. if device['online'] == '在线':
  1132. if(int(time.time()) - device.get('realtime',0)) > 10:
  1133. device['online'] = '离线'
  1134. logger.warning(f"设备离线: 云平台:{device['cloud_platform']}, sn={device['dev_sn']}")
  1135. dev_online_check_timer = threading.Timer(5, dev_online_check)
  1136. dev_online_check_timer.daemon = True
  1137. dev_online_check_timer.start()
  1138. save_devices(devices)
  1139. def start_dev_online_check():
  1140. global dev_online_check_timer
  1141. if dev_online_check_timer is not None:
  1142. dev_online_check_timer.cancel()
  1143. dev_online_check()
  1144. # 启动定时检查 长时间未有客户端登录,MQTT连接自动断开
  1145. mqtt_disconnect_check_timer = None
  1146. def mqtt_disconnect_check():
  1147. logger.info(f"mqtt_last_activity: {mqtt_last_activity}")
  1148. def start_mqtt_disconnect_check():
  1149. global mqtt_disconnect_check_timer
  1150. if mqtt_disconnect_check_timer is not None:
  1151. mqtt_disconnect_check_timer.cancel()
  1152. mqtt_disconnect_check()
  1153. if __name__ == '__main__':
  1154. logger.info(f"APP_VERSION: {APP_VERSION}")
  1155. init_data()
  1156. # 启动 MQTT连接状态定时检查
  1157. start_mqtt_status_check()
  1158. # 启动 设备状态定时检查
  1159. # start_dev_upgrade_check()
  1160. # 启动 设备在线状态定时检查
  1161. start_dev_online_check()
  1162. # 启动 MQTT自动断开检查
  1163. # start_mqtt_disconnect_check()
  1164. app.run(host='0.0.0.0', port=9082, debug=False, threaded=True, use_reloader=False) # 启用多线程提高性能