一、玩具开机提示语
先下载github代码,下面的操作,都是基于这个版本来的!
注意:由于涉及到版权问题,此附件没有图片和音乐。请参考链接,手动采集一下!
请参考链接:
判断设备id
每一个玩具,都有设备id。如果在设备表中,提示找小主人。否则提示 联系厂家。
如果在玩具表中,提示开机!
进入flask项目,将jquery.min.js下载到static目录,下载链接如下:
使用jquery的原因,是因为要发送ajax的POST请求。使用$.post{}
修改 templates-->index.html,增加开机按钮
Title
修改 serv-->toys.py,增加视图函数device_toy_id
from flask import Blueprint, request, jsonifyfrom setting import MONGO_DBfrom setting import RETfrom bson import ObjectIdtoy = Blueprint("toy", __name__)@toy.route("/toy_list", methods=["POST"])def toy_list(): # 玩具列表 user_id = request.form.get("user_id") # 用户id # 查看用户信息 user_info = MONGO_DB.users.find_one({ "_id": ObjectId(user_id)}) bind_toy = user_info.get("bind_toy") # 获取绑定的玩具 bind_toy_id = [] # 玩具列表 for toy_id in bind_toy: # 获取玩具列表中的所有玩具id bind_toy_id.append(ObjectId(toy_id)) # 一次性查询多个玩具 toys_list = list(MONGO_DB.toys.find({ "_id": { "$in": bind_toy_id}})) for index,item in enumerate(toys_list): # 将_id转换为字符串 toys_list[index]["_id"] = str(item.get("_id")) RET["code"] = 0 RET["msg"] = "" RET["data"] = toys_list return jsonify(RET)@toy.route("/device_toy_id", methods=["POST"])def device_toy_id(): device_id = request.form.get("device_id") # 获取设备id # 判断设备id是否在设备表中 if MONGO_DB.devices.find_one({ "device_id": device_id}): # 查询设备id是否在玩具表中 toy_info = MONGO_DB.toys.find_one({ "device_id": device_id}) if toy_info: return jsonify("开机") else: # 已授权的设备,但是没有绑定主人 return jsonify("找小主人") else: # 不在设备表中,说明是未授权,或者是冒牌的! return jsonify("联系玩具厂")
重启 manager.py,访问首页
输入一段数字,点击玩具开机键,效果如下:
打开 MongoDB客户端,复制一个不在玩具表(toys)中的设备id。效果如下:
复制一个,在玩具表中的设备id,效果如下:
提示语
后端逻辑判断,大致搞定了。下面来录制提示语,这里使用百度ai的接口。
在项目根目录,新建目录utils,在此目录下新建baidu_ai.py
此时,目录结构如下:
./├── audio├── audio_img├── device_code├── im_serv.py├── manager.py├── QRcode.py├── serv│ ├── content.py│ ├── devices.py│ ├── get_file.py│ └── toys.py├── setting.py├── static│ ├── jquery.min.js│ └── recorder.js├── templates│ └── index.html├── utils│ └── baidu_ai.py└── xiaopapa.py
修改 setting.py,增加百度AI的秘钥。注意:后5位被我修改了,请改为自己的!
import pymongoclient = pymongo.MongoClient(host="127.0.0.1", port=27017)MONGO_DB = client["bananabase"]RET = { # 0: false 2: True "code": 0, "msg": "", # 提示信息 "data": {}}XMLY_URL = "http://m.ximalaya.com/tracks/" # 喜马拉雅链接CREATE_QR_URL = "http://qr.liantu.com/api.php?text=" # 生成二维码API# 文件目录import osAUDIO_FILE = os.path.join(os.path.dirname(__file__), "audio") # 音频AUDIO_IMG_FILE = os.path.join(os.path.dirname(__file__), "audio_img") # 音频图片DEVICE_CODE_PATH = os.path.join(os.path.dirname(__file__), "device_code") # 二维码# 百度AI配置APP_ID = "11712345"API_KEY = "3v3igzCkVFUDwFByNEE12345"SECRET_KEY = "jRnwLE7kzC1aRi2FD10OQY3y9Og12345"SPEECH = { "spd": 4, 'vol': 5, "pit": 8, "per": 4}
修改 baidu_ai.py,录制开机语音
from aip import AipSpeechimport osBASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) # 项目根目录import syssys.path.append(BASE_DIR) # 加入到系统环境变量中import setting # 导入settingclient = AipSpeech(setting.APP_ID,setting.API_KEY,setting.SECRET_KEY)res = client.synthesis("欢迎来到嘉禾智能亲子互动乐园","zh",1,setting.SPEECH)with open(os.path.join(setting.AUDIO_FILE,"success.mp3"),"wb") as f: f.write(res)
执行 baidu_ai.py,会在audio目录生成 success.mp3 文件。试听一下,感觉萌萌哒!
注意:语言文件的保存路径是audio。为什么呢?因为前端会调用get_audio接口。它是从audio目录读取的!
修改 baidu_ai.py,录制没有小主人语音
from aip import AipSpeechimport osBASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) # 项目根目录import syssys.path.append(BASE_DIR) # 加入到系统环境变量中import setting # 导入settingclient = AipSpeech(setting.APP_ID,setting.API_KEY,setting.SECRET_KEY)res = client.synthesis("亲,我还没有小主人,快帮我找一个吧","zh",1,setting.SPEECH)with open(os.path.join(setting.AUDIO_FILE,"Nobind.mp3"),"wb") as f: f.write(res)
执行 baidu_ai.py,会在audio目录生成 Nobind.mp3 文件。试听一下吧
修改 baidu_ai.py,录制联系玩具厂商语音
from aip import AipSpeechimport osBASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) # 项目根目录import syssys.path.append(BASE_DIR) # 加入到系统环境变量中import setting # 导入settingclient = AipSpeech(setting.APP_ID,setting.API_KEY,setting.SECRET_KEY)res = client.synthesis("硬件设备不符,请联系玩具厂商","zh",1,setting.SPEECH)with open(os.path.join(setting.AUDIO_FILE,"Nodevice.mp3"),"wb") as f: f.write(res)
执行 baidu_ai.py,会在audio目录生成 Nodevice.mp3 文件。试听一下吧
修改 serv-->toys.py,返回语音文件名
from flask import Blueprint, request, jsonifyfrom setting import MONGO_DBfrom setting import RETfrom bson import ObjectIdtoy = Blueprint("toy", __name__)@toy.route("/toy_list", methods=["POST"])def toy_list(): # 玩具列表 user_id = request.form.get("user_id") # 用户id # 查看用户信息 user_info = MONGO_DB.users.find_one({ "_id": ObjectId(user_id)}) bind_toy = user_info.get("bind_toy") # 获取绑定的玩具 bind_toy_id = [] # 玩具列表 for toy_id in bind_toy: # 获取玩具列表中的所有玩具id bind_toy_id.append(ObjectId(toy_id)) # 一次性查询多个玩具 toys_list = list(MONGO_DB.toys.find({ "_id": { "$in": bind_toy_id}})) for index,item in enumerate(toys_list): # 将_id转换为字符串 toys_list[index]["_id"] = str(item.get("_id")) RET["code"] = 0 RET["msg"] = "" RET["data"] = toys_list return jsonify(RET)@toy.route("/device_toy_id", methods=["POST"])def device_toy_id(): # 验证设备id RET["code"] = 0 RET["msg"] = "开机成功" RET["data"] = {} device_id = request.form.get("device_id") # 获取设备id # 判断设备id是否在设备表中 if MONGO_DB.devices.find_one({ "device_id": device_id}): # 查询设备id是否在玩具表中 toy_info = MONGO_DB.toys.find_one({ "device_id": device_id}) if toy_info: # RET添加键值,获取玩具id RET["data"]["toy_id"] = str(toy_info.get("_id")) # 音频文件 RET["data"]["audio"] = "success.mp3" return jsonify(RET) else: # 已授权的设备,但是没有绑定主人 RET["msg"] = "找小主人" RET["data"]["audio"] = "Nobind.mp3" return jsonify(RET) else: # 不在设备表中,说明是未授权,或者是冒牌的! RET["msg"] = "联系玩具厂" RET["data"]["audio"] = "Nodevice.mp3" return jsonify(RET)
修改 index.html,POST请求成功后,修改audio标签的文件路径。将ws.onmessage代码移植到下面!
Title
重启 manager.py,访问首页,输入正确的设备id,效果如下:
这个功能,还可以扩展。比如判断今天是否为小主人的生日。说:生日快乐!
或者阳历节日,也可以提醒!
二、为多个玩具发送点播
用户有一个玩具,或者多个玩具时。
如果点击这个按钮,需要用户选择,指定发送给哪一个玩具。
目前数据库中,只有一个用户。昨天已经添加了一个,具体操作,请从参考昨天的链接:
现在再来添加一个!
绑定成功后,查看玩具表。有2条记录了
查看用户表,查看好友字段,会有2个!
建立连接
开2个页面,表示2个玩具。让2个玩具开机,需要2个合格的设备id。
打开玩具表,复制2个设备id
打开2个网页,左边输入 嘻嘻 的设备id
右边输入 小可爱 的设备id
在Console中,输入ws,回车。会出现一个websocket链接
注意:只要玩具开机了,就会建立 websocket连接!
查看Pycharm控制台输出:此时应该有2个websocket连接:
{ '5ba21c84e1253229c4acbd12':, '5ba0f1f2e12532418089bf88': }
那么APP页面,如何选择多个玩具呢?需要用到 弹出菜单
弹出菜单
mui框架内置了弹出菜单插件,弹出菜单显示内容不限,但必须包裹在一个含.mui-popover
类的div中,如下即为一个弹出菜单内容:
要显示、隐藏如上菜单,mui推荐使用锚点方式,例如:
点击如上定义的按钮,即可显示弹出菜单,再次点击弹出菜单之外的其他区域,均可关闭弹出菜单;这种使用方式最为简洁。
若希望通过js的方式控制弹出菜单,则通过如下一个方法即可:
mui('.bottomPopover').popover(status[,anchor]);
status
- 'show'显示popover
- 'hide'隐藏popover
- 'toggle'自动识别处理显示隐藏状态
mui('.bottomPopover').popover('toggle');//show hide toggle
本文参考链接:
修改 player.html,只修改html代码部分,js代码不用动!
使用模拟器访问,点击 发送给玩具,效果如下:
上面的item固定死了,需要展示为当前用户的 玩具名。需要访问后端接口,查询当前用户的所有玩具
修改 player.html
重新访问一次,效果如下:
点击 小豆芽 是没有效果的!需要增加点击事件。由于它是a标签,使用onclick
需要使用websocket发送数据。由于index.html建立了websocket连接,使用fire事件将数据发给index.html。
由index.html来发送数据!
修改player.html
index.html页面,就不需要修改了。因为它已经监听了 send_music 事件。
点击 小甜甜
此时,页面的第二个窗口,会自动播放歌曲。
那么点歌功能,就完成了!
三、聊天页面
之前我们写的phone.html,好像没咋用过。将phone.html重命名为 message.html
好友列表,来源于 用户表(users) 的friend_list字段!
修改index.html,将底部选项卡 中的 电话 改为 消息
底部选项卡,效果如下:
修改 message.html,发送post,请求好友列表
进入 flask项目,进入serv目录,新建文件friend.py
from flask import Blueprint, request, jsonifyfrom setting import MONGO_DBfrom setting import RETfrom bson import ObjectIdfri = Blueprint("fri", __name__)@fri.route("/friend_list", methods=["POST"])def friend_list(): # 好友列表 user_id = request.form.get("user_id") # 查询用户id信息 res = MONGO_DB.users.find_one({ "_id": ObjectId(user_id)}) friend_list = res.get("friend_list") # 获取好友列表 RET["code"] = 0 RET["msg"] = "" RET["data"] = friend_list return jsonify(RET)
修改 manager.py,注册蓝图
from flask import Flask, request,jsonify,render_templatefrom setting import MONGO_DBfrom setting import RETfrom bson import ObjectIdfrom serv import get_filefrom serv import contentfrom serv import devicesfrom serv import toysfrom serv import friendapp = Flask(__name__)app.register_blueprint(get_file.getfile)app.register_blueprint(content.cont)app.register_blueprint(devices.devs)app.register_blueprint(toys.toy)app.register_blueprint(friend.fri)@app.route('/')def hello_world(): return render_template("index.html")@app.route('/login',methods=["POST"])def login(): """ 登陆验证 :return: settings -> RET """ try: RET["code"] = 1 RET["msg"] = "用户名或密码错误" RET["data"] = {} username = request.form.get("username") password = request.form.get("password") user = MONGO_DB.users.find_one({ "username": username, "password": password}) if user: # 由于user中的_id是ObjectId对象,需要转化为字符串 user["_id"] = str(user.get("_id")) RET["code"] = 0 RET["msg"] = "欢迎登陆" RET["data"] = { "user_id": user.get("_id")} except Exception as e: RET["code"] = 1 RET["msg"] = "登陆失败" return jsonify(RET)@app.route('/reg',methods=["POST"])def reg(): """ 注册 :return: {"code":0,"msg":"","data":""} """ try: username = request.form.get("username") password = request.form.get("password") age = request.form.get("age") nickname = request.form.get("nickname") gender = request.form.get("gender") phone = request.form.get("phone") user_info = { "username": username, "password": password, "age": age, "nickname": nickname, # 判断gender==2,成立时为girl.jpg,否则为boy.jpg "avatar": "girl.jpg" if gender == 2 else "boy.jpg", "gender": gender, "phone": phone } res = MONGO_DB.users.insert_one(user_info) user_id = str(res.inserted_id) RET["code"] = 0 RET["msg"] = "注册成功" RET["data"] = user_id except Exception as e: RET["code"] = 1 RET["msg"] = "注册失败" return jsonify(RET)@app.route('/user_info', methods=["POST"])def user_info(): user_id = request.form.get("user_id") # "password": 0 表示忽略密码字段 res = MONGO_DB.users.find_one({ "_id": ObjectId(user_id)}, { "password": 0}) if res: res["_id"] = str(res.get("_id")) RET["code"] = 0 RET["msg"] = "" RET["data"] = res return jsonify(res)if __name__ == '__main__': app.run("0.0.0.0", 9527, debug=True)
重启 manager.py
使用模拟器访问 消息 ,效果如下:
这个页面还是空的。查看HBuilder控制台输出:
{ "code":0,"data":[{ "friend_avatar":"girl.jpg","friend_chat":"5ba0f1f2e12532418089bf87","friend_id":"5ba0f1f2e12532418089bf88","friend_name":"小可爱","friend_remark":"小甜甜"},{ "friend_avatar":"girl.jpg","friend_chat":"5ba21c84e1253229c4acbd11","friend_id":"5ba21c84e1253229c4acbd12","friend_name":"嘻嘻","friend_remark":"小豆芽"}],"msg":""} at message.html:37
已经得到了数据,下面开始渲染页面!
修改 message.html,渲染页面
重新访问,效果如下:
点击小甜甜,查看HBuilder控制台输出:
5ba0f1f2e12532418089bf88 at message.html:63
它会打印出,好友id。那么下面就可以开始聊天了!
新建css文件
chat.css
div.speech { float: left; margin: 0, 0; padding: 6px; table-layout: fixed; word-break: break-all; position: relative; background: -webkit-gradient( linear, 50% 0%, 50% 100%, from(#ffffff), color-stop(0.1, #ececec), color-stop(0.5, #dbdbdb), color-stop(0.9, #dcdcdc), to(#8c8c8c)); border: 1px solid #989898; border-radius: 8px;}div.speech:before { content: ''; position: absolute; width: 0; height: 0; left: 15px; top: -20px; border: 10px solid; border-color: transparent transparent #989898 transparent;}div.speech:after { content: ''; position: absolute; width: 0; height: 0; left: 17px; top: -16px; border: 8px solid; border-color: transparent transparent #ffffff transparent;}div.speech.right { display: inline-block; box-shadow: -2px 2px 5px #CCC; margin-right: 10px; max-width: 75%; float: right; background: -webkit-gradient( linear, 50% 0%, 50% 100%, from(#e4ffa7), color-stop(0.1, #bced50), color-stop(0.4, #aed943), color-stop(0.8, #a7d143), to(#99BF40));}div.speech.right:before { content: ''; position: absolute; width: 0; height: 0; top: 9px; bottom: auto; left: auto; right: -10px; border-width: 9px 0 9px 10px; border-color: transparent #989898;}div.speech.right:after { content: ''; position: absolute; width: 0; height: 0; top: 10px; bottom: auto; left: auto; right: -8px; border-width: 8px 0 8px 9px; border-color: transparent #bced50;}div.left { display: inline-block; box-shadow: 2px 2px 2px #CCCCCC; margin-left: 10px; max-width: 75%; position: relative; background: -webkit-gradient( linear, 50% 0%, 50% 100%, from(#ffffff), color-stop(0.1, #eae8e8), color-stop(0.4, #E3E3E3), color-stop(0.8, #DFDFDF), to(#D9D9D9));}div.left:before { content: ''; position: absolute; width: 0; height: 0; top: 9px; bottom: auto; left: -10px; border-width: 9px 10px 9px 0; border-color: transparent #989898;}div.left:after { content: ''; position: absolute; width: 0; height: 0; top: 10px; bottom: auto; left: -8px; border-width: 8px 9px 8px 0; border-color: transparent #eae8e8;}.leftimg { float: left; margin-top: 10px;}.rightimg { float: right; margin-top: 10px;}.leftd { clear: both; float: left; margin-left: 10px; margin-top: 15px;}.rightd { clear: both; float: right; margin-top: 15px; margin-right: 10px;}.leftd_h { width: 45px; height: 35px; border-radius: 100%; display: block; float: left; overflow: hidden;}.leftd_h img { display: block; width: 100%; height: auto;}.rightd_h { width: 45px; height: 35px; border-radius: 100%; display: block; float: right; overflow: hidden;}.rightd_h img { display: block; width: 100%; height: auto;}.chat-other { background-color: red; margin-top: 10px; margin-left: 20px;}.chat-other-span { background-color: aquamarine; /*border-radius: 10%;*/ height: 18px;}.chat-mine { margin-top: 10px; margin-right: 20px; text-align: right;}.chat-avatar { border-radius: 100%; width: 25px; height: 25px;}
新建文件chat.html
手势事件
在开发移动端的应用时,会用到很多的手势操作,比如滑动、长按等,为了方便开放者快速集成这些手势,mui内置了常用的手势事件,目前支持的手势事件见如下列表:
参考链接:
这里, 只用到了 长按里面的 hold和release
修改 chat.html
修改 message.html,增加点击事件。点击时,跳转到chat.html页面
使用模拟器访问,效果如下:
四、app录音
由于时间关系,详细步骤略...
五、app与服务器端文件传输
由于时间关系,详细步骤略...
六、简单的对话
由于时间关系,详细步骤略...
今日总结:
1.玩具开机提示语刚刚开机的时候: 1.授权问题(MD5授权码)提示语 : 请联系玩具厂商 2.绑定问题 提示语 : 快给我找一个小主人 3.成功 提示语:欢迎使用 2.为多个玩具发送点播: mpop 弹出菜单 3.聊天界面:点击播放按住录音: hold: 按住事件 开始录音(回调函数) release: 松开事件 结束录音 执行录音中的回调函数 4.app录音: var rec = plus.audio.getRcorder() rec.record( {filename:"_doc/audio/",format:"amr"}, function(success){ success //录音文件保存路径 }, function(error){} ) rec.stop() 5.app与服务器端文件传输(ws传输): 1.app使用dataURL方式打开录音文件 : base64 文件 2.通过某个函数 将 Base64 格式的文件 转为 Blob 用于 websocket传输 3.将Blob对象使用Ws发送至服务端 4.服务端保存文件(amr) 5.将amr 转换为 mp3 使用 ffmpeg -i xxx.amr xxx.mp3 6.简单的对话(app向玩具(web)发起): app: 1.发起两次 ws.send({to_user:}) 告诉服务端我要发给谁消息 2. ws.send(blob) app与服务器端文件传输 websocket服务: 0.创建两个变量,用于接收to_user 和 blob对象 1.收到用户的JSON字符串,to_user 获取对方的Websocket,用户send 2.收到用户的Blob对象,语音文件 保存成amr文件,转换成mp3 注意保存文件的路径 3.将转换完成的文件发送给 to_user 4.两个变量置空点击播放
由于时间关系,详细步骤略...,主要修改了3个文件。
MyApp: chat.html,index.html
banana: im_serv.py
最终效果,使用APP给 小甜甜 说一段话:
第二个网页,也就是小甜甜的,会自动播放声音
完整代码,请参考github:
未完待续。。。