Qpython+Flask监控添加发送语音中文信息功能

Qpython+Flask实现对小孩学习的监控-CSDN博客中html页面进行改造,利用Ajax,提交一段文字,发送到数据库,再在服务器,发送该段文件给手机端,然手机端TTS朗读出来,增加了父母监控小孩学习,自定义提醒小孩的功能。

一、index.html的更改。

html 复制代码
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>实时**学习情况图</title>
    <script type="text/javascript">
        // 设置定时器,每20秒(20000毫秒)刷新一次页面
        setInterval(function(){ 
            location.reload(); 
        }, 20000);
    </script>    
    <script>
        function submitForm() {
            // 获取文本输入框的值
            var text = document.getElementById('textInput').value;

            // 创建一个XMLHttpRequest对象
            var xhr = new XMLHttpRequest();

            // 配置请求:POST方法,请求的URL,以及是否异步
            xhr.open("POST", "/submit", true);

            // 设置请求头,指定内容类型为表单数据
            xhr.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");

            // 定义当请求完成并成功时的回调函数
            xhr.onreadystatechange = function () {
                if (xhr.readyState === XMLHttpRequest.DONE && xhr.status === 200) {
                    // 解析并处理服务器的响应
                    var response = xhr.responseText;
                    document.getElementById('response').innerText = response;
                }
            };

            // 编码表单数据并发送
            var formData = "textInput=" + encodeURIComponent(text);
            xhr.send(formData);

            // 阻止表单的默认提交行为
            return false;
        }
    </script>
</head>
<body>
    <h1>学习情况,每20秒刷新一次页面</h1>
    <div>
        <img src="{
  
  { image_url }}" alt="Latest Image">
    </div>
    <h1>状态:{
  
  { qk }} </h1>
    <form onsubmit="return submitForm();">
        <label for="textInput">输入文字发给手机说:</label><br><br>
        <textarea id="textInput" name="textInput" rows="1" required></textarea><br><br>
        <input type="submit" value="提交">
    </form>
    <p id="response"></p>
<p>
<a href="http://192.168.99.235:5000/qk" target="_self">查看实时学习情况</a>
</p>
</body>
</html>

二、flask服务器端

python 复制代码
# -*- coding: utf-8 -*-
"""
Created on Wed Jan 15 22:39:02 2025

@author: Ybk
"""
from flask import Flask, jsonify, send_from_directory, render_template, request
import time
import os
from pathlib import Path
import sqlite3
import datetime
import torch
import torch.nn as nn
from torchvision import transforms, models
from PIL import Image
import broadlink
import plotly.graph_objects as go
# 加载模型
# 加载模型并设置为评估模式
model = models.resnet18(pretrained=False)  # 假设使用的是resnet18
num_ftrs = model.fc.in_features  # 获取输入特征数量
model.fc = nn.Linear(num_ftrs, 6)  # 将输出特征数量改为4
model.load_state_dict(torch.load('best66_resnet18_model.pth'))  # 加载模型参数
model.to('cuda' if torch.cuda.is_available() else 'cpu')  # 移动到设备上
model.eval()  # 设置为评估模式

# 定义预处理步骤
preprocess = transforms.Compose([
    transforms.Resize(224),
    transforms.CenterCrop(224),
    transforms.ToTensor(),
    transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225]),
])
app = Flask(__name__)
db_filepath = Path(__file__).joinpath("../jk.db").resolve()

# 设置上传文件的目录和允许的文件扩展名
now = datetime.datetime.now()
date_str = now.strftime("%Y%m%d")
UPLOAD_FOLDER = fr'd:\upload\{date_str}'
ALLOWED_EXTENSIONS = {'png', 'jpg', 'jpeg', 'gif'}
app.config['UPLOAD_FOLDER'] = UPLOAD_FOLDER

# 创建上传目录如果不存在
if not os.path.exists(UPLOAD_FOLDER):
    os.makedirs(UPLOAD_FOLDER)
def insertdb(sj,furl,xl,zt,qk): #插入一行数据zt1为有人0为无人
    conn = sqlite3.connect(db_filepath, timeout=10, check_same_thread=False)
    c = conn.cursor()
    insert_query = "INSERT INTO jkme(sj,furl,xl,zt,qk) VALUES(?,?,?,?,?);"
    insert_data = (sj,furl,xl,zt,qk)
    c.execute(insert_query,insert_data)
    conn.commit()
    c.close()
    conn.close    
def insertmsg(msg): #插入一行数据zt1为有人0为无人
    conn = sqlite3.connect(db_filepath, timeout=10, check_same_thread=False)
    c = conn.cursor()
    insert_query = "INSERT INTO jkmsg(sj,msg,zt) VALUES(?,?,0);"
    now = datetime.datetime.now()
    sj = now.strftime("%Y-%m-%d %H:%M:%S")
    insert_data = (sj,msg)
    c.execute(insert_query,insert_data)
    conn.commit()
    c.close()
    conn.close 
# 检查文件是否是允许的类型
def allowed_file(filename):
    # 检查文件名是否包含点,并且文件扩展名(转换为小写后)是否在允许的扩展名集合中
    if '.' in filename:
        # 获取文件扩展名,并转换为小写
        file_extension = filename.rsplit('.', 1)[-1].lower()
        # 检查扩展名是否在允许的集合中
        return file_extension in ALLOWED_EXTENSIONS
    else:
        # 如果文件名不包含点,则扩展名不存在或不合法
        return False

def sc_xxqkpng():    
    conn = sqlite3.connect(db_filepath, timeout=10, check_same_thread=False)
    c = conn.cursor()
    #lastxxid,bsj,esj
    cursor = c.execute("SELECT id,sj FROM jkme WHERE qk = 4 and sj > datetime('now', 'localtime', '-120 minute') order by id desc")
    row = cursor.fetchone()
    if row:
        lastxxid = row[0]
        lastxxsj = row[1]
        cursor = c.execute(f"SELECT count(id) FROM jkme WHERE id > {lastxxid}")
        row = cursor.fetchone()
        if row[0] > 50:
            fig = go.Figure()
            # 定义颜色列表
            colors = ['cyan','blue','yellow', 'green']
            cursor = c.execute(f"SELECT qk FROM jkme WHERE id > {lastxxid}")
            rows = cursor.fetchall()
            list0 = [row[0] for row in rows]
            # 初始化一个空列表来存储结果
            result = []    
            # 初始化变量来跟踪当前数字及其起始位置
            current_number = None
            start_index = None    
            alltime=0
            rztime=0
            lktime=0
            # 遍历数字序列
            for i, number in enumerate(list0):
                # 如果当前数字与之前的数字不同(或者是第一个数字)
                if number == 0:
                    lktime = lktime + 1
                if number in (1,2):
                    alltime = alltime + 1
                    if number == 1:
                        rztime = rztime + 1
                if current_number is None or number != current_number:
                    # 如果之前已经有过数字,则记录其范围
                    if current_number is not None:
                        result.append([current_number, start_index, i - 1])
                    # 更新当前数字和起始位置
                    current_number = number
                    start_index = i
            
            # 处理序列中的最后一个数字范围
            if current_number is not None:
                result.append([current_number, start_index, len(list0) - 1])
            all_data_tj = len(list0)
            for xlist in result:    
                # print(xlist)
                # 添加矩形   
                # 添加一个矩形形状,没有边框
                fig.add_shape(
                    type="rect",
                    x0=1+xlist[1]*2,  # 矩形左下角的x坐标
                    y0=1,  # 矩形左下角的y坐标
                    x1=1+(xlist[2]+1)*2,  # 矩形右上角的x坐标
                    y1=6,  # 矩形右上角的y坐标        
                    fillcolor=colors[xlist[0]],  # 矩形的填充颜色
                    line=dict(width=0)  # 设置边框宽度为0,即没有边框
                )
                i = i + 1
            
            xxtime = int(alltime / 6)
            rztime = round(rztime / alltime * 100,2)
            lktime = round(lktime / all_data_tj * 100,2)
            all_time = int(all_data_tj / 6)
            # 设置图的标题和坐标轴的限制
            cursor = c.execute(f"SELECT sj FROM jkme WHERE id > {lastxxid} order by id asc")
            row = cursor.fetchone()
            bsj = row[0]
            cursor = c.execute("SELECT sj FROM jkme order by id desc")
            row = cursor.fetchone()
            esj = row[0]
            sjstr = bsj.split()[0] + ' ' + bsj.split()[1] + '-' + esj.split()[1]

            # 设置布局以隐藏背景和坐标轴
            # 更新图形的布局,隐藏坐标轴和背景
            fig.update_layout(
                xaxis=dict(
                    range=[0,(all_data_tj+1)*2],
                    showgrid=False,  # 隐藏网格线
                    zeroline=False,  # 隐藏零轴线
                    visible=False    # 隐藏x轴
                ),
                yaxis=dict(
                    range=[0,5],
                    showgrid=False,  # 隐藏网格线
                    zeroline=False,  # 隐藏零轴线
                    visible=False    # 隐藏y轴
                ),
                plot_bgcolor='white',  # 设置背景颜色为透明
                paper_bgcolor='white',   # 设置整个图形的背景颜色为透明
                
                title=dict(
                    text=f'{sjstr} 学习情况图',  # 标题文本
                    font=dict(
                            size=16,  # 字体大小
                            color='black'  # 字体颜色
                        ),
                    x=0.5,  # 标题水平居中
                    xanchor='center',  # 相对于x位置的锚点设置为中心
                    y=0.8,  # 标题的垂直位置(可选,根据需要调整)
                    yanchor='top'  # 相对于y位置的锚点设置为顶部
                ),
                annotations=[
                    dict(
                        text=f'总{all_time}分钟,学习{xxtime}分钟,认真学习时间占{rztime}%,离开学习占总时间{lktime}%',  # 注释的文本内容
                        x=0.5,  # 注释的水平位置(相对于图表宽度的比例)
                        y=-0.2,  # 注释的垂直位置(相对于图表高度的比例,负值表示在图表下方)
                        xref='paper',  # 参照物为整个图表区域('paper')而非数据坐标('x')
                        yref='paper',  # 参照物为整个图表区域('paper')而非数据坐标('y')
                        showarrow=False,  # 不显示箭头
                        font=dict(
                            size=14,  # 字体大小
                            color='black'  # 字体颜色
                        ),
                        align='center'  # 文本对齐方式
                    )
                ],
                width=800,     # 设置图表宽度(可选)
                height=240     # 设置图表高度(可选)
            )
            pngfilename = esj.replace('-','').replace(' ','').replace(':','') 
            # 显示图表
            # fig.show()
            fig.write_image(rf'D:\sb\xx\{pngfilename}.png')
            insert_query = "INSERT INTO qkpng(url) VALUES(?);"
            insert_data = (rf'D:\sb\xx\{pngfilename}.png',)
            c.execute(insert_query,insert_data)
            conn.commit()

            print(rf'D:\sb\xx\{pngfilename}.png')
            c.close()
            conn.close()
            return f'学习{xxtime}分钟,认真学习时间占{rztime}%'
        else:
            c.close()
            conn.close()
            return '学习情况不能统计!'
    else:
        c.close()
        conn.close()
        return '学习情况不能统计!'
    
    
def getprexl(): #获取最后一个xl
    conn = sqlite3.connect(db_filepath, timeout=10, check_same_thread=False)
    c = conn.cursor()
    cursor = c.execute("select xl from jkme order by id desc limit 0,1;")
    row = cursor.fetchone()
    if row:
        xl = row[0]
    else:
        xl = 0
    c.close()
    conn.close
    return xl
def turntolight(turn):#turn=true为开灯,turn=false为关灯    
    try:
        device = broadlink.sp4(host=('192.168.99.50',80), mac=bytearray.fromhex('E81656CA934B'), devtype=32021)
        device.auth()
        state = device.check_power()
        if turn:
            if state == True:
                print(" ON pass")
            elif state == False:
                device.set_power(True)
                print(" ON")
        else:
            if state == False:
                print(" OFF pass")
            elif state == True:
                device.set_power(False)
                print(" OFF")
    except Exception as e:
        print(e)
# turntolight(False)
@app.route('/upload', methods=['POST'])
def upload_file():
    now = datetime.datetime.now()
    # 检查请求中是否包含'description'参数
    description = request.form.get('description')
    if not description:
        return jsonify({'error': 'No description provided'}), 400

    # 检查是否有文件在请求中
    if 'file' not in request.files:
        return jsonify({'error': 'No file part in the request'}), 400
    
    file = request.files['file']
    
    # 如果用户没有选择文件,返回错误
    if file.filename == '':
        return jsonify({'error': 'No selected file'}), 400
    
    # 如果文件合法,尝试保存文件
    if file and allowed_file(file.filename):
        filename = file.filename
        filepath = os.path.join(app.config['UPLOAD_FOLDER'], description.replace(' ','').replace('-','').replace(':','') + '.' + filename.rsplit('.', 1)[-1])
        file.save(filepath)
        res = 'ok'
        xl = 0
        tj = 0
        tjx = 0
        qkz = 0
        msg = ''
        if filepath:
            img = Image.open(filepath)  # 加载图片
            img_tensor = preprocess(img)  # 预处理图片
            img_tensor = img_tensor.unsqueeze(0)  # 增加batch维度
            img_tensor = img_tensor.to('cuda' if torch.cuda.is_available() else 'cpu')  # 移动到设备上
            # 进行推理
            with torch.no_grad():
                outputs = model(img_tensor)
                _, predicted = torch.max(outputs, 1)
            # 解释结果
            result = int(predicted.item())
            if result == 0:
                res = '**没在学习'
            elif result == 1:
                res = 'ok'
            elif result == 2:
                res = '**要认真学习'
            elif result == 3:
                res = '是**'
            elif result == 4:
                res = '是爸爸'
            elif result == 5:
                res = '是其他人'
            if result > 2:
                result = 3
            #正常情况
            xl = 0 
            zt = 0
            #如果26分钟内1和2,已经超过138个,那么设置为2,2    
            conn = sqlite3.connect(db_filepath, timeout=10, check_same_thread=False)
            c = conn.cursor()
            cursor = c.execute("SELECT count(*) FROM jkme WHERE zt = 0 and (qk > 0 and qk < 4) and sj > datetime('now', 'localtime', '-26 minute')")
            row = cursor.fetchone()
            tj = row[0]
            print(f'学习统计={tj}个10秒')
            if tj > 137 or getprexl() == 2:
                xl = 2 
                zt = 2
                result = 4
                
                if tj == 138:
                    res = sc_xxqkpng()
                    turntolight(True)
            #如果6分钟内2,2已经超过30个,那么设置为0,0
            cursor = c.execute("SELECT count(*) FROM jkme WHERE xl = 2 and zt = 2 and sj > datetime('now', 'localtime', '-6 minute')")
            row = cursor.fetchone()
            tjx = row[0]
            print(f'休息统计={tjx}个10秒')
            if tjx > 30:
                xl = 0 
                zt = 0
                res = '休息时间完了。'
                if tjx == 31:
                    turntolight(False)
            #如果上一个为2,这一个也是2,那么设置为1,0
            cursor = c.execute("SELECT qk FROM jkme order by id desc limit 0,1;")
            row = cursor.fetchone()
            qkz = row[0]
            print(f'上一个={qkz}')
            if qkz == 2:
                xl = 1 #1为提醒响铃
                zt = 0  
            if result == 0 and zt != 2:
                xl = 3 #3为警报响铃
            cursor = c.execute("SELECT id,msg FROM jkmsg WHERE zt = 0 limit 0,1;")
            row = cursor.fetchone()
            if row:
                msg = row[1]
                mid = row[0]
                c.execute(f"UPDATE jkmsg SET zt = 1 WHERE id = {mid}")
                conn.commit()
            c.close()
            conn.close
            insertdb(now.strftime("%Y-%m-%d %H:%M:%S"),filepath,xl,zt,result)
            # 返回包含接收到的描述和文件路径的响应
            if result == 4:
                res = 'ok'
        if int(now.strftime("%H")) in (12,13,14):
            res = 'ok'
        if msg != '':
            res = msg
        return jsonify({
            'message': f'学习={tj},' + f'休息={tjx},' + f'上个={qkz}。' + f'{msg}',
            'speak': res,
            'sj': now.strftime("%Y-%m-%d %H:%M:%S"),
            'xl': xl
        }), 201
    else:
        return jsonify({'error': 'Allowed file types are png, jpg, jpeg, gif'}), 400

@app.route('/')
def index():
    # 获取最新的图片
    conn = sqlite3.connect(db_filepath, timeout=10, check_same_thread=False)
    c = conn.cursor()
    cursor = c.execute("select furl,qk from jkme order by id desc limit 0,1;")
    row = cursor.fetchone()
    upic = row[0]
    result = row[1]
    res = 0
    if result == 0:
        res = '没在学习'
    elif result == 1:
        res = '认真学习'
    elif result == 2:
        res = '没有认真学习'
    elif result == 3:
        res = '是其他人'
    elif result == 4:
        res = '休息时间'
    c.close
    conn.close
    furl = os.path.basename(upic)
    image_url = f'http://192.168.99.235:5000/static/upimages/{furl}'
    return render_template('index.html', image_url=image_url, qk = res)

@app.route('/qk')
def index0():
    urls = []
    conn = sqlite3.connect(db_filepath, timeout=10, check_same_thread=False)
    c = conn.cursor()
    cursor = c.execute("SELECT url FROM qkpng order by id desc limit 0,5;")
    rows = cursor.fetchall()
    for row in rows:
        urls.append(os.path.basename(row[0]))
    c.close()
    conn.close
    return render_template('index0.html', image_urls=urls)

@app.route('/static/images/<path:filename>')
def custom_static(filename):
    return send_from_directory(r'D:\sb\xx', filename)

@app.route('/static/upimages/<path:filename>')
def custom_static_gz(filename):
    now = datetime.datetime.now()
    date_str = now.strftime("%Y%m%d")
    return send_from_directory(fr'D:\upload\{date_str}', filename)

@app.route('/submit', methods=['POST'])
def handle_form_submit():
    # 从POST请求中获取名为'textInput'的参数
    text_input = request.form.get('textInput')
    insertmsg(text_input)
    # 处理接收到的数据(这里只是简单地返回它)
    response = {
        'status': 'success',
        'receivedText': text_input
    }    
    # 返回JSON格式的响应
    return jsonify(response)
 

@app.route('/get_data', methods=['GET'])
def get_data():
    # 在这里,你可以根据需要动态生成数据
    # 例如,从数据库查询、调用其他API等
    conn = sqlite3.connect(db_filepath, timeout=10, check_same_thread=False)
    c = conn.cursor()
    cursor = c.execute("SELECT sj,furl,xl,zt,qk FROM jkme order by id desc limit 0,1")
    row = cursor.fetchone()
    sj = row[0]
    url = row[1]
    xl = row[2]
    zt = row[3]
    getqk = row[4]
    c.close
    conn.close
    if getqk == 0:
        qk = '无人'
    elif getqk == 1:
        qk = '认真'
    elif getqk == 2:
        qk = '不认真'
    else:
        qk = '其他情况'
    furl = os.path.basename(url)
    data = {
    'sj':sj,
    'image_url': f'http://192.168.99.235:5000/static/upimages/{furl}',
    'str_value': qk,
    'xl': xl,
    'zt': zt,
    'qk': getqk
    }
    # 返回JSON响应
    return jsonify(data)

if __name__ == '__main__':
    app.run(host='192.168.99.235')

三、数据库:

sql 复制代码
CREATE TABLE jkmsg (
    id  INTEGER PRIMARY KEY AUTOINCREMENT,
    sj  TEXT,
    msg TEXT,
    zt  INTEGER
);

运行效果:

相关推荐
rockmelodies13 分钟前
基于Python的端口扫描器和目录扫描工具实现方案,结合机器学习进行指纹识别
人工智能·python·机器学习
是十一月末20 分钟前
Opencv之掩码实现图片抠图
人工智能·python·opencv·计算机视觉·图片识别
阿_星_21 分钟前
解决pip install 出现error: subprocess-exited-with-error的问题
开发语言·python·pip
Asthenia041223 分钟前
从零聊起:RocketMQ Producer 的预绑定主题列表和事务消息
后端
Hum8le28 分钟前
CTF题目《SSRFMe》(网鼎杯 2020 玄武组)WriteUp
redis·python·安全·web安全·php
小程序设计34 分钟前
【2025】基于springboot+vue的体育场馆预约管理系统(源码、万字文档、图文修改、调试答疑)
vue.js·spring boot·后端
Dontla44 分钟前
uv命令介绍(高性能Python包管理工具,旨在替代pip、pip-tools和virtualenv等传统工具)
python·pip·uv
程序视点1 小时前
Linux内核与基础命令学习总结
linux·后端
alicema11111 小时前
Python+Django网页前后端rsp云端摄像头人数监控系统
开发语言·网络·后端·python·神经网络·算法·django
考虑考虑1 小时前
JDK21中的Switch模式匹配
java·后端·java ee