# smart home
from flask import Flask, request, render_template, url_for, Response # 导入Flask库中的各个模块
from unihiker import GUI
import sqlite3 # 导入sqlite3数据库
import datetime # 导入datetime日期时间模块
import time
import requests
import cv2
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import mpld3
import socket
import csv
from sklearn.preprocessing import LabelEncoder, OrdinalEncoder



# 定义数据库文件名
db_file = "templates/data.db" 

# 定义文件存放路径
jpg_file = 'static/face.jpg'
csv_file0 = 'templates/AN_collected.csv'
csv_file1 = 'templates/AN_original.csv'
csv_file2 = 'templates/AN_dealed.csv'


gui=GUI()  # 实例化GUI类


# web初始化
app = Flask(__name__)#创建Web应用实例 


# 定义全局变量
global data_temp,data_humidity,data_light,data_water,state_lamp,state_fan
data_temp = 30       # 温度数据
data_humidity = 50 # 湿度数据
data_light = 1023    # 光线数据
data_water = "否"    # 水分数据，表示是否漏水
state_lamp = "关闭"  # 灯光状态 eg:关闭/明亮/暖光/睡眠
state_fan = '关闭'   # 风扇状态 eg:关闭/打开
count_lamp = 0  # 计次
flag_state_lamp_on = 0  #开灯标志变量
flag_state_lamp_off = 0 #关灯标志变量
flag_state_fan_on = 0   #风扇打开标志变量
flag_state_fan_off = 0  #风扇关闭标志变量
flag_mode_manual = 0    #手动模式标志变量
flag_mode_auto = 0      #自动模式标志变量



'''定义按钮的回调函数'''
# 打开灯光：明亮、暖黄
def lamp_open():
    global count_lamp,flag_state_lamp_on
    count_lamp = count_lamp + 1
    if count_lamp%2 == 1:
        # 明亮灯光
        text_state_lamp.config(text='明亮')
        flag_state_lamp_on = 1
    else:
        # 暖黄灯光
        text_state_lamp.config(text='暖黄')
        flag_state_lamp_on = 2

# 关闭灯光
def lamp_close():
    text_state_lamp.config(text='关闭')
    global flag_state_lamp_off
    flag_state_lamp_off = 1

# 打开风扇
def fan_open():
    text_state_fan.config(text='开启')
    global flag_state_fan_on
    flag_state_fan_on = 1

# 关闭风扇
def fan_close():
    text_state_fan.config(text='关闭')
    global flag_state_fan_off
    flag_state_fan_off = 1



# 手动模式
def mode_manual():
    button_mode1.config(state = "disabled")
    button_mode2.config(state = "abled")
    global flag_mode_manual
    flag_mode_manual = 1

# 自动模式
def mode_auto():
    button_mode2.config(state = "disabled")
    button_mode1.config(state = "abled")
    global flag_mode_auto
    flag_mode_auto = 1


'''初始化屏幕'''
# 显示标题title
title = gui.draw_text(text="智能家居",x=78,y=5,font_size=16, color="#0000FF")
# 显示温度数据temp
gui.draw_round_rect(x=15,y=40,w=210,h=95,r=5,width=1,color="#0000FF")  # 绘制圆角矩形
gui.draw_image(image="image/温度.png",x=37,y=42,w=20)  # 显示温度icon
gui.draw_text(text="温度",x=60,y=40,font_size=13, color="#FF6600")  # 显示文字“温度”
text_temp = gui.draw_digit(text=data_temp,x=43,y=66,font_size=15, color="#000000")  # 显示温度数据
gui.draw_text(text="℃",x=77,y=62,font_size=12, color="#000000")  # 显示文字“℃”
# 显示湿度数据humidity
gui.draw_image(image="image/湿度.png",x=142,y=42,w=20)
gui.draw_text(text="湿度",x=165,y=40,font_size=13, color="#FF6600")
text_humidity = gui.draw_digit(text=data_humidity,x=148,y=66,font_size=15, color="#000000")
gui.draw_text(text="％",x=182,y=62,font_size=12, color="#000000")
# 显示光照数据light
gui.draw_image(image="image/光照.png",x=37,y=92,w=20)  
gui.draw_text(text="光照",x=60,y=90,font_size=13, color="#FF6600")  
text_light = gui.draw_digit(text=data_light,x=43,y=115,font_size=15, color="#000000") 
# 显示是否漏水数据water
gui.draw_image(image="image/漏水.png",x=142,y=92,w=20)  
gui.draw_text(text="漏水",x=165,y=90,font_size=13, color="#FF6600")  
text_water = gui.draw_text(text=data_water,x=162,y=110,font_size=12, color="#000000") 

# 显示控制模式
gui.draw_round_rect(x=15,y=142,w=210,h=55,r=5,width=1,color="#0000FF")  
gui.draw_text(text="控制模式",x=90,y=142,font_size=11, color="#FF6600")
button_mode1 = gui.add_button(x=28, y=165, w=75, h=30, text="手动模式", onclick=mode_manual,state = "disabled")
button_mode2 = gui.add_button(x=137, y=165, w=75, h=30, text="自动模式", onclick=mode_auto)# ,state = "disabled"

# 显示灯光状态state_lamp
gui.draw_round_rect(x=15,y=203,w=100,h=100,r=5,width=1,color="#0000FF") 
gui.draw_image(image="image/灯光.png",x=24,y=205,w=20)  
gui.draw_text(text="灯光状态",x=44,y=203,font_size=12, color="#FF6600")  
text_state_lamp = gui.draw_digit(text=state_lamp,x=50,y=225,font_size=11, color="#000000") 
button_light_open = gui.add_button(x=28, y=241, w=75, h=29, text="打开", onclick=lamp_open)
button1_light_close = gui.add_button(x=28, y=271, w=75, h=29, text="关闭", onclick=lamp_close)
 
# 显示风扇状态state_fan
gui.draw_round_rect(x=125,y=203,w=100,h=100,r=5,width=1,color="#0000FF")  
gui.draw_image(image="image/风扇.png",x=130,y=205,w=20)  
gui.draw_text(text="风扇状态",x=148,y=203,font_size=12, color="#FF6600")  
text_state_fan = gui.draw_digit(text=state_fan,x=157,y=225,font_size=11, color="#000000")  
button_fan_open = gui.add_button(x=137, y=241, w=75, h=28, text="打开", onclick=fan_open)
button_fan_close = gui.add_button(x=137, y=271, w=75, h=28, text="关闭", onclick=fan_close)



'''接收智能终端数据'''
# 接收家居环境数据:时间、时段、温度、湿度、光线、是否漏水
@app.route('/input1',methods=['POST'])   #响应终端的post请求，获取实时家居环境状态
def get_data_jiaju(): 
    global data_temp,data_humidity,data_air_pression,data_pm,data_wind_speed,data_wind_direction,data_is_rainy
    nowtime2 = str(request.form.get('时间'))  # 接收智能终端时间数据
    period = str(request.form.get('时段'))  # 接收智能终端时间数据
    data_temp = str(request.form.get('温度'))  # 接收智能终端温度数据
    data_humidity = str(request.form.get('湿度'))  # 接收智能终端温度数据
    data_light = str(request.form.get('光线'))  # 接收智能终端温度数据
    data_water = str(request.form.get('漏水'))  # 接收智能终端温度数据

    # print("时间:",nowtime2,"时段:",period,"温度:",data_temp,"湿度:",data_humidity,"光线:",data_light,"漏水:",data_water)

    '''屏幕更新显示数据'''
    text_temp.config(text=data_temp)
    text_humidity.config(text=data_humidity)
    text_light.config(text=data_light)
    text_water.config(text=data_water)


    '''将家居环境数据存入数据库'''
    connection = sqlite3.connect(db_file)  # 连接数据库
    cursor = connection.cursor()  # 创建游标对象 
    cursor.execute("create table if not exists temp(ID TEXT,Now_Time TEXT,Period TEXT ,Temperature TEXT,Humitidy TEXT,Light Text,Water TEXT,State_lamp TEXT,State_fan)" )  # 新建表temp并设置字段,若该表已存在则忽略，
    cursor.execute("select * from temp") # 查询数据表
    data_all=cursor.fetchall()  # 接收全部的返回结果
    connection.commit()  # 事务提交
    IDD = []
    # 给数据编号
    global ID
    try:
        for i in data_all:
            IDD.append(i[0]) # 
        ID = IDD[-1] 
    except:
        ID  = 0
    ID = int(ID) + 1
    '''将序号、时间、时段、温度、湿度、光线、漏水情况、灯光状态、风扇状态  存入数据库'''
    cursor.execute("insert into temp(ID,Now_Time,Period,Temperature,Humitidy,Light,Water,State_lamp,State_fan) VALUES(?,?,?,?,?,?,?,?,?)" ,(str(ID),nowtime2,period,data_temp,data_humidity,data_light,data_water,state_lamp,state_fan))  # 在temp中插入一条记录
    connection.commit()  # 事务提交，保存修改内容。 
    connection.close()  # 关闭数据库连接

    return ''


# 接收自动模式下灯光状态信息
@app.route('/state_lamp',methods=['POST'])   #响应终端的post请求，获取实时灯光状态
def get_state_lamp(): 
    state_lamp_lamp = str(request.form.get('灯光状态'))  # 接收智能终端灯光状态数据
    if state_lamp_lamp == '明亮':
        text_state_lamp.config(text='明亮')
    elif state_lamp_lamp == '暖黄':
        text_state_lamp.config(text='暖黄')
    elif state_lamp_lamp == '关闭':
        text_state_lamp.config(text='关闭')
    return ''

# 接收自动模式下风扇状态信息
@app.route('/state_fan',methods=['POST'])   #响应终端的post请求，获取实时灯光状态
def get_state_fan(): 
    state_fan_fan = str(request.form.get('风扇状态'))  # 接收智能终端灯光状态数据
    if state_fan_fan == '开启':
        text_state_fan.config(text='开启')
    elif state_fan_fan == '关闭':
        text_state_fan.config(text='关闭')
    return ''



'''屏幕按钮发送消息至智能终端'''
# 打开灯光：明亮、暖黄
@app.route('/lamp_on',methods=['GET'])
def read_button_lamp_on():
    global flag_state_lamp_on
    if flag_state_lamp_on == 1:
        flag_state_lamp_on = 0
        return 'bright'
    elif flag_state_lamp_on == 2:
        flag_state_lamp_on = 0
        return 'warm'
    else:
        return ''
# 关闭灯光
@app.route('/lamp_off',methods=['GET'])
def read_button_lamp_off():
    global flag_state_lamp_off
    if flag_state_lamp_off == 1:
        flag_state_lamp_off = 0
        return 'close'
    else:
        return ''
# 打开风扇
@app.route('/fan_on',methods=['GET'])
def read_button_fan_on():
    global flag_state_fan_on
    if flag_state_fan_on == 1:
        flag_state_fan_on = 0
        return 'on'
    else:
        return ''
# 关闭风扇
@app.route('/fan_off',methods=['GET'])
def read_button_fan_off():
    global flag_state_fan_off
    if flag_state_fan_off == 1:
        flag_state_fan_off = 0
        return 'off'
    else:
        return '' 
# 手动模式
@app.route('/mode_manual',methods=['GET'])
def read_button_mode_manual():
    global flag_mode_manual
    if flag_mode_manual == 1:
        flag_mode_manual = 0
        text_state_lamp.config(text = "关闭")
        text_state_fan.config(text = "关闭")
        return 'manual'
    else:
        return '' 
# 自动模式
@app.route('/mode_auto',methods=['GET'])
def read_button_mode_auto():
    global flag_mode_auto
    if flag_mode_auto == 1:
        flag_mode_auto = 0
        return 'auto'
    else:
        return ''



'''网页显示'''
# 主页            
@app.route('/',methods=['GET','POST']) # 响应网页的get请求，返回主页信息
def table(): 
    show="" 
    connection = sqlite3.connect(db_file) # 连接数据库data_test2.db，若数据库不存在则创建
    cursor = connection.cursor() # 创建游标对象 
    cursor.execute("select * from temp")
    # 在表temp中查询数据 
    data=cursor.fetchall()  # 查询所有数据 
    connection.commit()  # 事务提交，保存修改内容 
    connection.close()  # 关闭数据库连接 
    for i in range(len(data)): 
        show="序号："+data[i][0]+"，时间："+data[i][1]+ "，时段："+data[i][2]+ "，温度："+data[i][3]+ "，湿度："+data[i][4]+"，光照："+data[i][5]+"，漏水："+data[i][6]+" <br/>"+show
    return render_template('index.html',data = data)

# 分页1--实时图表           
@app.route('/chart',methods=['GET','POST'])#响应网页的get请求，返回主页信息
def chart(): 
    # 创建空列表
    x_data = []
    y_data = []
    y_data2 = []
    y_data3 = []  

    connection = sqlite3.connect(db_file)
    cursor = connection.cursor()
    cursor.execute("select * from temp")
    data=cursor.fetchall()
    connection.commit()
    connection.close()

    for i in range(len(data)):
        x_data.append(i) # 时间次序
        y_data.append(int(data[i][3])) # 温度
        y_data2.append(int(data[i][4])) # 湿度
        y_data3.append(int(data[i][5])) # 光照

    '''温度图'''
    # 创建图形对象
    fig = plt.figure()
    plt.title("温度--时序")
    plt.xlabel("时序")
    plt.ylabel("温度")
    plt.plot(x_data, y_data)
    '''湿度图'''
    # 创建图形对象
    fig2 = plt.figure()
    plt.title("湿度--时序")
    plt.xlabel("时序")
    plt.ylabel("湿度")
    plt.plot(x_data, y_data2)
    '''光照图'''
    # 创建图形对象
    fig3 = plt.figure()
    plt.title("光照--时序")
    plt.xlabel("时序")
    plt.ylabel("光照")
    plt.plot(x_data, y_data3)

    html = mpld3.fig_to_html(fig)+mpld3.fig_to_html(fig2)+mpld3.fig_to_html(fig3)
    plt.close()

    return render_template('chart.html',html = html)


try:
    # 加载人脸检测分类器 
    detector=cv2.CascadeClassifier(cv2.data.haarcascades+ 'haarcascade_frontalface_default.xml')  # 加载人脸检测分类器 
    '''创建socket连接'''
    # IP地址'0.0.0.0'为等待客户端连接
    address = ('xxx.xxx.xxx.xxx', 8088)  # 填写服务器IP
    # 建立socket对象
    # socket.AF_INET：服务器之间网络通信 
    # socket.SOCK_STREAM：流式socket , for TCP
    s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    s.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1)  # 设置给定套接字选项的值
    # 将套接字绑定到地址, 在AF_INET下,以元组（host,port）的形式表示地址.
    s.bind(address)
    # 开始监听TCP传入连接。参数指定在拒绝连接之前，操作系统可以挂起的最大连接数量。该值至少为1，大部分应用程序设为5就可以了。
    s.listen(5)
except:
    print(Exception)


def recvall(sock, count):  # 定义功能函数
    buf = b''#buf是一个byte类型
    while count:
        #接受TCP套接字的数据。数据以字符串形式返回，count指定要接收的最大数据量.
        newbuf = sock.recv(count)
        if not newbuf: return None
        buf += newbuf
        count -= len(newbuf)
    return buf


try:
    '''建立客户端连接'''    
    # 接受TCP连接并返回（conn,address）,其中conn是新的套接字对象，可以用来接收和发送数据。addr是连接客户端的地址。
    # 没有连接则等待有连接
    conn, addr = s.accept()
    print('connect from:'+str(addr))
except:
    print(Exception)


# 定义函数--接收视频流并返回
def ReceiveVideo(): 
    count = 0 # 定义一个计数标记count
    while True: 
        nowtime = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
        start = time.time()  # 用于计算帧率信息
        length = recvall(conn,16)  # 获得图片文件的长度,16代表获取长度
        stringData = recvall(conn, int(length))  # 根据获得的文件长度，获取图片文件
        data = np.frombuffer(stringData, np.uint8)  # 将获取到的字符流数据转换成1维数组
        decimg=cv2.imdecode(data,cv2.IMREAD_COLOR)  # 将数组解码成图像
        # cv2.imshow('SERVER',decimg)  # 显示图像
        # 进行下一步处理
        '''人脸检测'''
        image=decimg
        h,w,c=image.shape
        gray=cv2.cvtColor(image,cv2.COLOR_BGR2GRAY)
        faces = detector.detectMultiScale(gray, 1.2, 5)  # 检测,获取人脸识别数据
        for (x, y, w, h) in faces:
            cv2.rectangle(image, (x - 30, y - 30), (x + w + 30, y + h + 30), (0, 255, 0), 2)  # 在人脸上绘制方框矩形
            image2 = image[y:y + h, x:x + w]
            if count == 5: # 第六帧
                cv2.imwrite('static/face/face_{}.jpg'.format(nowtime),image2)  # 存储人脸图片
                cv2.imwrite(jpg_file,image2) 
                count = 0 # 计次归零
            else:
                count = count + 1 # 帧数+1

        # 将帧率信息回传，主要目的是测试可以双向通信
        end = time.time()
        seconds = end - start
        fps  = 1/seconds;
        conn.send(bytes(str(int(fps)),encoding='utf-8'))  # 回应发送fps

        if cv2.waitKey(1) == ord('b'):  # 点击小写字母b 退出程序
            break

        ret, jpeg = cv2.imencode('.jpg', image) 
        frame = jpeg.tobytes()  # Image.tobytes()将图像作为字节对象返回

        yield (b'--frame\r\n' b'Content-Type: image/jpeg\r\n\r\n' + frame + b'\r\n')
    # conn.close()
    # cv2.destroyAllWindows()
'''
cv2.imdecode()函数从指定的内存缓存中读取数据，并把数据转换(解码)成图像格式;主要用于从网络传输数据中恢复出图像。
cv2.imencode()函数是将图片格式转换(编码)成流数据，赋值到内存缓存中;主要用于图像数据格式的压缩，方便网络传输。
Image.tobytes()将图像作为字节对象返回
'''


#配置视频获取路由(相机喂流)
@app.route('/video_feed')
def video_feed():
    return Response(ReceiveVideo(),mimetype='multipart/x-mixed-replace; boundary=frame')
'''mimetype媒体类型'''

# 分页2--远程监控
@app.route('/cur_camera')
def cur_camera():
    return render_template('cur_camer.html')



# 分页3--远程控制页面
@app.route('/remote_control')
def remote_control():
    return render_template('remote_control.html')


# 分页3.1--打开明亮灯光
@app.route('/lamp_bright_1',methods=['POST'])
def readposlb():
    text_state_lamp.config(text='明亮')
    global flag_state_lamp_on
    flag_state_lamp_on = 1
    return ''

# 分页3.2--打开暖黄灯光
@app.route('/lamp_warm_1',methods=['POST'])
def readposlw():
    text_state_lamp.config(text='暖黄')
    global flag_state_lamp_on
    flag_state_lamp_on = 2
    return ''

# 分页3.3--关闭灯光
@app.route('/lamp_close_1',methods=['POST'])
def readposlc():
    text_state_lamp.config(text='关闭')
    global flag_state_lamp_off
    flag_state_lamp_off = 1
    return ''

# 分页3.4--打开风扇
@app.route('/fan_on_1',methods=['POST']) # fan_on_1
def readposL():
    text_state_fan.config(text='开启')
    global flag_state_fan_on
    flag_state_fan_on = 1
    return ''

# 分页3.5--关闭风扇
@app.route('/fan_off_1',methods=['POST'])
def readposR():
    text_state_fan.config(text='关闭')
    global flag_state_fan_off
    flag_state_fan_off = 1
    return ''




# 分页4--数据分析页面
@app.route('/analyze')
def analyze():
    return render_template('analyze.html')


# 分页4.1--数据处理页面
@app.route('/analyze_csv',methods=['GET','POST'])
def analyze_csv():
    # 将数据库中的数据写入csv中
    connection = sqlite3.connect(db_file)  # 连接数据库,若数据库不存在则创建
    cursor = connection.cursor()  # 创建游标对象 
    cursor.execute("select * from temp")
    data=cursor.fetchall()  # 查询所有数据
    connection.commit()  # 事务提交 
    connection.close()  # 关闭数据库连接
    xuhao = []  # 创建空列表存储
    shijian = []
    shiduan = []
    wendu = []
    shidu = []
    guangzhao = []
    loushui = []
    dengguangzhuangtai = []
    fengshanzhuangtai = []

    for i in data:
        xuhao.append(i[0]) 
        shijian.append(i[1]) 
        shiduan.append(i[2]) 
        wendu.append(i[3])
        shidu.append(i[4]) 
        guangzhao.append(i[5]) 
        loushui.append(i[6]) 
        dengguangzhuangtai.append(i[7]) 
        fengshanzhuangtai.append(i[8]) 
    biaotou = ['序号','时间','时段','温度','湿度','光照','漏水','灯光状态','风扇状态']
    r = zip(xuhao, shijian, shiduan, wendu, shidu,guangzhao,loushui,dengguangzhuangtai,fengshanzhuangtai)# 压缩列表
    with open(csv_file0, "w") as s:
        w = csv.writer(s)
        w.writerow(biaotou)
        for row in r:
            w.writerow(row)
    return render_template('analyze_csv.html')


# 分页4.2--数据处理页面
@app.route('/analyze_process',methods=['GET','POST'])
def analyze_process():
    '''先进行数据清洗（删除空值）'''
    df = pd.read_csv(csv_file1,encoding = 'gb2312')
    df = df.replace(r'^\s*$', np.nan, regex=True)
    df = pd.DataFrame(df)
    df=df.dropna()
    df=df.dropna(axis=0)
    df=df.dropna().reset_index(drop=True) # Reset index after drop
    df.to_csv(csv_file2, index=False)
    '''读取清洗后的文件进行数据处理（删除多余列，编码灯光状态为012）'''
    HAR_df = pd.read_csv(csv_file2,usecols=[2,3,4,5,7])
    # 处理灯光状态
    state_light2 = HAR_df.iloc[:][['灯光状态']].values
    ordinalencoder2 = LabelEncoder()
    state_light2 = ordinalencoder2.fit_transform(state_light2.ravel())
    HAR_df['灯光状态'] = state_light2
    # 保存csv文件
    HAR_df.to_csv(csv_file2, index=False)
    print('数据处理完成！')
    return render_template('analyze_process.html')

# 分页4.3--数据可视化页面
@app.route('/analyze_visual')
def analyze_visual():
    # 设置中文字体、标题等
    plt.style.use('seaborn')# to get seaborn scatter plot
    plt.rcParams['font.sans-serif'] = ['HYQiHei']
    plt.rcParams['axes.unicode_minus']=False
    # 读取csv文件
    data = pd.read_csv(csv_file2)# read the csv file to extract data
    # data = pd.read_csv(csv_file2,encoding='gb2312')# read the csv file to extract data
    '''图1'''
    # 创建图形对象
    fig = plt.figure()
    光照 = data['光照']
    灯光状态 = data['灯光状态']
    plt.scatter(光照, 灯光状态, s=100, alpha=0.6, edgecolor='black', linewidth=1)
    plt.title('探究灯光状态与光照的关系')
    plt.xlabel('光照')
    plt.ylabel('灯光状态')
    plt.tight_layout()

    '''图2'''
    # 创建图形对象
    fig2 = plt.figure()
    时段 = data['时段']
    灯光状态 = data['灯光状态']
    plt.scatter(时段, 灯光状态, s=100, alpha=0.6, edgecolor='black', linewidth=1)
    plt.title('探究灯光状态与时段的关系')
    plt.xlabel('时段')
    plt.ylabel('灯光状态')
    plt.tight_layout()   

    html = mpld3.fig_to_html(fig)+mpld3.fig_to_html(fig2)
    plt.close()
    return render_template('analyze_visual.html',html=html)



# 主程序启动
if __name__ == '__main__': 
    app.run(host="0.0.0.0",port=8089) 