|
@@ -0,0 +1,363 @@
|
|
|
|
+# 主業務邏輯中的視圖和路由的定義
|
|
|
|
+import os
|
|
|
|
+import datetime
|
|
|
|
+from flask import render_template, request, session, Response
|
|
|
|
+# 導入藍圖程序,用於構建路由
|
|
|
|
+from werkzeug.utils import redirect
|
|
|
|
+from . import main
|
|
|
|
+from manage import mqtt
|
|
|
|
+# 導入db,用於操作數據庫
|
|
|
|
+from manage import db
|
|
|
|
+# 導入實體類,用於操作數據庫
|
|
|
|
+from ..models import *
|
|
|
|
+import json
|
|
|
|
+from datetime import datetime as dt, date
|
|
|
|
+from sqlalchemy import text
|
|
|
|
+import socket
|
|
|
|
+import pickle
|
|
|
|
+import cv2
|
|
|
|
+import numpy as np
|
|
|
|
+import math
|
|
|
|
+import threading
|
|
|
|
+import time
|
|
|
|
+from flask_mqtt import Mqtt
|
|
|
|
+import requests
|
|
|
|
+import re
|
|
|
|
+from time import sleep as sl
|
|
|
|
+from concurrent.futures import ThreadPoolExecutor
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+pool = ThreadPoolExecutor(10)
|
|
|
|
+lock = threading.Lock()
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+# 主頁的訪問路徑
|
|
|
|
+@main.route('/')
|
|
|
|
+def main_index():
|
|
|
|
+ # 獲取登入信息
|
|
|
|
+ if 'id' in session and 'uname' in session:
|
|
|
|
+ username = session['uname']
|
|
|
|
+ schedule = Schedule.query.order_by(text('datetime desc')).limit(1).first()
|
|
|
|
+ goals = Goal.query.all()
|
|
|
|
+ return render_template('index.html', params=locals())
|
|
|
|
+ else:
|
|
|
|
+ return render_template('sign_in.html')
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+# 登入頁面的訪問路徑
|
|
|
|
+@main.route('/login', methods=['GET', 'POST'])
|
|
|
|
+def login_views():
|
|
|
|
+ if request.method == 'GET':
|
|
|
|
+ if 'id' in session and 'uname' in session:
|
|
|
|
+ return redirect('/')
|
|
|
|
+ else:
|
|
|
|
+ return render_template('sign_in.html')
|
|
|
|
+ else:
|
|
|
|
+ # 接收前端傳過來的資料
|
|
|
|
+ username = request.form['username']
|
|
|
|
+ password = request.form['password']
|
|
|
|
+ # 使用接收的用戶和密碼到資料庫中查詢
|
|
|
|
+ user = User.query.filter_by(username=username, password=password).first()
|
|
|
|
+ # 如果用戶存在,將信息保存置session並重定向回首頁,否則重定向回登入頁
|
|
|
|
+ if user:
|
|
|
|
+ resp = redirect('/')
|
|
|
|
+ # 判斷是否有記住密碼
|
|
|
|
+ if 'rem' in request.form:
|
|
|
|
+ sn = str(user.sn)
|
|
|
|
+ max_age = 60 * 60 * 24 * 365
|
|
|
|
+ resp.set_cookie("username", username, max_age=max_age)
|
|
|
|
+ resp.set_cookie("sn", sn, max_age=max_age)
|
|
|
|
+
|
|
|
|
+ session['uname'] = user.username
|
|
|
|
+ session['id'] = user.sn
|
|
|
|
+ return resp
|
|
|
|
+ else:
|
|
|
|
+ errMsg = "Wrong login or password"
|
|
|
|
+ return render_template('sign_in.html', errMsg=errMsg)
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+# 註冊頁面的訪問路徑
|
|
|
|
+@main.route('/register', methods=['POST', "GET"])
|
|
|
|
+def register_views():
|
|
|
|
+ if request.method == 'GET':
|
|
|
|
+ return render_template('registration.html')
|
|
|
|
+ else:
|
|
|
|
+ # 獲取文本框的值並賦值給user實體對象
|
|
|
|
+ user = User()
|
|
|
|
+ user.username = request.form['username']
|
|
|
|
+ user.password = request.form['password']
|
|
|
|
+ # 將數據保存進資料庫 - 註冊
|
|
|
|
+ db.session.add(user)
|
|
|
|
+ # 手動提交,目的是為了獲取提交後的user的id
|
|
|
|
+ db.session.commit()
|
|
|
|
+ # 當user成功插入進資料庫之後,程序會自動將所有信息取出來在賦值給user
|
|
|
|
+ # 完成登入的操作
|
|
|
|
+ user = User.query.filter_by(username=user.username).first()
|
|
|
|
+ session['id'] = user.sn
|
|
|
|
+ session['uname'] = user.username
|
|
|
|
+ return redirect('/')
|
|
|
|
+
|
|
|
|
+#驗證username訪問路徑
|
|
|
|
+@main.route('/check_username')
|
|
|
|
+def check_username_views():
|
|
|
|
+ username = request.args['username']
|
|
|
|
+ user = User.query.filter_by(username=username).first()
|
|
|
|
+ if user:
|
|
|
|
+ print("test")
|
|
|
|
+ result = {"errMsg":" "}
|
|
|
|
+ else:
|
|
|
|
+ result = {"pass":" "}
|
|
|
|
+ return json.dumps(result)
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+#AGV控制台的訪問路徑
|
|
|
|
+@main.route('/agv_console', methods=['POST', 'GET'])
|
|
|
|
+def agv_console_views():
|
|
|
|
+ if request.method == 'GET':
|
|
|
|
+ schedule = Schedule.query.order_by(text('datetime desc')).limit(1).first()
|
|
|
|
+ goals = Goal.query.all()
|
|
|
|
+ sonic = Sonic.query.order_by(text('datetime desc')).limit(1).first()
|
|
|
|
+ water_level = WaterLevel.query.order_by(text('datetime desc')).limit(1).first()
|
|
|
|
+ return render_template('agv_console.html', params=locals())
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+#影像串流的頁面訪問路徑
|
|
|
|
+@main.route('/video', methods=['POST', 'GET'])
|
|
|
|
+def video_views():
|
|
|
|
+ if request.method == 'GET':
|
|
|
|
+ username = session['uname']
|
|
|
|
+ return render_template('video.html', params=locals())
|
|
|
|
+
|
|
|
|
+s_sock = 0
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+#影像串流的訪問路徑
|
|
|
|
+@main.route("/video_feed", methods=['POST', 'GET'])
|
|
|
|
+def video_feed_views():
|
|
|
|
+ global s_sock, pool
|
|
|
|
+ if request.method == 'GET':
|
|
|
|
+ #於10分鐘之後,自動關閉socket server
|
|
|
|
+ if s_sock == 0:
|
|
|
|
+ def socket_server_views():
|
|
|
|
+ global s_sock
|
|
|
|
+ print("test")
|
|
|
|
+ print(s_sock)
|
|
|
|
+ sl(600)
|
|
|
|
+ if s_sock != 0:
|
|
|
|
+ s_sock.close()
|
|
|
|
+ s_sock = 0
|
|
|
|
+ print('s_sock is closed')
|
|
|
|
+
|
|
|
|
+ pool.submit(socket_server_views)
|
|
|
|
+
|
|
|
|
+ host = "192.168.50.65"
|
|
|
|
+ port = 8000
|
|
|
|
+ max_length = 65540
|
|
|
|
+ # max_length = 95540
|
|
|
|
+
|
|
|
|
+ print("----------------------")
|
|
|
|
+ s_sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
|
|
|
|
+ s_sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
|
|
|
|
+ s_sock.bind((host, port))
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+ frame_info = None
|
|
|
|
+ buffer = None
|
|
|
|
+ frame = None
|
|
|
|
+ encodedImage = None
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+ print("-> waiting for connection")
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+ # 影像生成器函數,將影像以jpg格式傳給前端
|
|
|
|
+ def generate():
|
|
|
|
+ while True:
|
|
|
|
+ with lock:
|
|
|
|
+ global s_sock, frame_info, buffer, frame, encodedImage
|
|
|
|
+ if s_sock == 0:
|
|
|
|
+ break
|
|
|
|
+ data, address = s_sock.recvfrom(max_length)
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+ if len(data) < 100:
|
|
|
|
+ frame_info = pickle.loads(data)
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+ if frame_info:
|
|
|
|
+ nums_of_packs = frame_info["packs"]
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+ for i in range(nums_of_packs):
|
|
|
|
+ if s_sock == 0:
|
|
|
|
+ break
|
|
|
|
+ data, address = s_sock.recvfrom(max_length)
|
|
|
|
+
|
|
|
|
+ if i == 0:
|
|
|
|
+ buffer = data
|
|
|
|
+ else:
|
|
|
|
+ buffer += data
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+ frame = np.frombuffer(buffer, dtype=np.uint8)
|
|
|
|
+ frame = frame.reshape(frame.shape[0], 1)
|
|
|
|
+
|
|
|
|
+ frame = cv2.imdecode(frame, cv2.IMREAD_UNCHANGED)
|
|
|
|
+
|
|
|
|
+ #如果frame為None就跳過
|
|
|
|
+ if frame is None:
|
|
|
|
+ continue
|
|
|
|
+
|
|
|
|
+ frame = cv2.resize(frame, (640, 360), interpolation=cv2.INTER_AREA)
|
|
|
|
+
|
|
|
|
+ # encode the frame in JPEG format
|
|
|
|
+ (flag, encodedImage) = cv2.imencode(".jpg", frame)
|
|
|
|
+ # ensure the frame was successfully encoded
|
|
|
|
+ if not flag:
|
|
|
|
+ continue
|
|
|
|
+ # yield the output frame in the byte format
|
|
|
|
+ yield (b'--frame\r\n' b'Content-Type: image/jpeg\r\n\r\n' +
|
|
|
|
+ bytearray(encodedImage) + b'\r\n')
|
|
|
|
+
|
|
|
|
+ # return the response generated along with the specific media
|
|
|
|
+ # type (mime type)
|
|
|
|
+ return Response(generate(), mimetype="multipart/x-mixed-replace; boundary=frame")
|
|
|
|
+ else:
|
|
|
|
+ if s_sock != 0:
|
|
|
|
+ s_sock.close()
|
|
|
|
+ s_sock = 0
|
|
|
|
+ print("s_sock is closed")
|
|
|
|
+ return "s_sock is closed"
|
|
|
|
+ print("s_sock is closed")
|
|
|
|
+ return "s_sock is closed"
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+#目標點的訪問路徑
|
|
|
|
+@main.route('/goal', methods=['POST', 'GET'])
|
|
|
|
+def goal_views():
|
|
|
|
+ dict = request.form.to_dict()
|
|
|
|
+ ori_datas = dict['ori_data'].split("\n")
|
|
|
|
+ change_datas = dict['change_data'].split("\n")
|
|
|
|
+ length = len(ori_datas)
|
|
|
|
+ goal_num = 1
|
|
|
|
+ for i in range(length):
|
|
|
|
+ goalObj = Goal.query.filter_by(goal_num=goal_num).first()
|
|
|
|
+ if not goalObj:
|
|
|
|
+ goalObj = Goal()
|
|
|
|
+ goalHisObj = GoalHistory()
|
|
|
|
+ goalObj.goal_num = goal_num
|
|
|
|
+ goalHisObj.goal_num = goal_num
|
|
|
|
+ goalObj.goal_ori_x = ori_datas[i].split(',')[0]
|
|
|
|
+ goalHisObj.goal_ori_x = ori_datas[i].split(',')[0]
|
|
|
|
+ goalObj.goal_change_x = change_datas[i].split(',')[0]
|
|
|
|
+ goalHisObj.goal_change_x = change_datas[i].split(',')[0]
|
|
|
|
+ goalObj.goal_ori_y = ori_datas[i].split(',')[1]
|
|
|
|
+ goalHisObj.goal_ori_y = ori_datas[i].split(',')[1]
|
|
|
|
+ goalObj.goal_change_y = change_datas[i].split(',')[1]
|
|
|
|
+ goalHisObj.goal_change_y = change_datas[i].split(',')[1]
|
|
|
|
+ goalObj.datetime = dt.now()
|
|
|
|
+ goalHisObj.datetime = dt.now()
|
|
|
|
+ db.session.add(goalObj)
|
|
|
|
+ db.session.add(goalHisObj)
|
|
|
|
+ goal_num += 1
|
|
|
|
+ db.session.commit()
|
|
|
|
+
|
|
|
|
+ return "Sent successfully!"
|
|
|
|
+
|
|
|
|
+#移除目標點的訪問路徑
|
|
|
|
+@main.route('/remove_goal', methods=['POST', 'GET'])
|
|
|
|
+def remove_goal_views():
|
|
|
|
+ goal = Goal.query.order_by(text('sn desc')).limit(1).first()
|
|
|
|
+ db.session.delete(goal)
|
|
|
|
+ db.session.commit()
|
|
|
|
+
|
|
|
|
+ return "Removed successfully!"
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+#排程時間的訪問路徑
|
|
|
|
+@main.route('/schedule', methods=['POST', 'GET'])
|
|
|
|
+def schedule_views():
|
|
|
|
+ dict = request.form.to_dict()
|
|
|
|
+ times = dict['data'].split("\n")
|
|
|
|
+ schedule = Schedule()
|
|
|
|
+ try:
|
|
|
|
+ schedule.time1 = times[0]
|
|
|
|
+ schedule.time2 = times[1]
|
|
|
|
+ schedule.time3 = times[2]
|
|
|
|
+ schedule.time4 = times[3]
|
|
|
|
+ schedule.time5 = times[4]
|
|
|
|
+ schedule.time6 = times[5]
|
|
|
|
+ schedule.time7 = times[6]
|
|
|
|
+ except Exception as e:
|
|
|
|
+ pass
|
|
|
|
+ schedule.datetime = dt.now()
|
|
|
|
+ db.session.add(schedule)
|
|
|
|
+ db.session.commit()
|
|
|
|
+
|
|
|
|
+ return "Success"
|
|
|
|
+
|
|
|
|
+#制動器MQTT訪問路徑
|
|
|
|
+@main.route('/mqtt', methods=['POST', 'GET'])
|
|
|
|
+def swh_mqtt_views():
|
|
|
|
+ import json
|
|
|
|
+ dict = request.args.to_dict()
|
|
|
|
+ json = json.dumps(dict)
|
|
|
|
+ # topic = 'AISKY/AppleFarm/MK-G/b8:27:eb:4f:05:65'
|
|
|
|
+ topic = 'AISKY/AppleFarm/MK-G/b8:27:eb:b4:59:3e'
|
|
|
|
+
|
|
|
|
+ #假設MQTT命令為開啟影像串流,在3分鐘後自動發布關閉該影像串流
|
|
|
|
+ if (dict['command'] == 'video1' or dict['command'] == 'video2') and dict['value'] == 'on':
|
|
|
|
+ def close_video_views():
|
|
|
|
+ import json
|
|
|
|
+ sl(180)
|
|
|
|
+ dict['value'] = 'off'
|
|
|
|
+ json = json.dumps(dict)
|
|
|
|
+ print(json)
|
|
|
|
+ mqtt.publish(topic, json)
|
|
|
|
+ print('close video test')
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+ pool.submit(close_video_views)
|
|
|
|
+
|
|
|
|
+ # if dict['command'] == 'fan':
|
|
|
|
+ # topic= 'AISKY/AppleFarm/MK-G/b8:27:eb:b4:59:3e'
|
|
|
|
+ # elif dict['command'] == 'water_bump':
|
|
|
|
+ # topic = 1
|
|
|
|
+ # elif dict['command'] == 'water_charge':
|
|
|
|
+ # topic = 1
|
|
|
|
+ # elif dict['command'] == 'charge':
|
|
|
|
+ # topic = 1
|
|
|
|
+ # elif dict['command'] == 'sonic':
|
|
|
|
+ # topic = 1
|
|
|
|
+ # elif dict['command'] == 'red_light':
|
|
|
|
+ # topic = 1
|
|
|
|
+ # elif dict['command'] == 'yellow_light':
|
|
|
|
+ # topic = 1
|
|
|
|
+ # elif dict['command'] == 'green_light':
|
|
|
|
+ # topic = 1
|
|
|
|
+ print(json)
|
|
|
|
+ mqtt.publish(topic, json)
|
|
|
|
+
|
|
|
|
+ return "Success"
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+#處理mqtt訂閱的信息
|
|
|
|
+@mqtt.on_message()
|
|
|
|
+def handle_mqtt_message(client, userdata, message):
|
|
|
|
+ payload = message.payload.decode()
|
|
|
|
+ print(payload)
|
|
|
|
+
|
|
|
|
+# 退出的訪問路徑
|
|
|
|
+@main.route('/logout')
|
|
|
|
+def logout_views():
|
|
|
|
+ if 'id' in session and 'uname' in session:
|
|
|
|
+ del session['id']
|
|
|
|
+ del session['uname']
|
|
|
|
+ return redirect('/')
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+
|