【顺达|技能进修】灯光控制
顺达 发布于2017-12-30 01:06 浏览:2529 回复:12
3
收藏

【技能名称】灯光控制

【功能简介】控制不同颜色灯的开启和关闭

【示例对话】

          用户:帮我打开红色的灯

          小度:红色灯已开启

          用户:帮我关闭绿色的灯

          小度:绿色灯已关闭

介绍

       灯光控制这个技能是我上一个帖子【顺达|开发日记】DuerOS搭建智能语音管家 的后续,我希望我的语音管家具备硬件控制的功能,所以就先尝试了下对不同颜色的LED进行控制。用了红、绿、黄三色LED,通过树莓派的GPIO控制亮暗;灯光控制的服务使用Tornado部署在树莓派上,用Ngrok进行内网穿透,将接口暴露到外网;利用百度的CFC平台作为跳板,让技能能调用到灯光控制的接口。

       总之实现得很简易,分享出来是希望有些地方能给到大家启发,比如说自己的服务如果不是用PHP或者Node.js实现的,没有提供bot-sdk,是可以利用CFC平台作为跳板调用到自己的服务的。

最终实物图.jpg

收藏
点赞
3
个赞
共12条回复 最后由顺达回复于2018-06-19 23:33
#2顺达回复于2017-12-30

LED控制

       LED使用树莓派的GPIO控制,所以不能直接把开发板插在树莓派上,那样就没有多余的引脚可以用了。得照着开发板的引脚图用杜邦线把这些引脚单独连起来,将GPIO引脚空出来。

开发板引脚图.png

        

      红、绿、黄三色LED我分别接在了树莓派的GPIO5、GPIO12、GPIO16上,连接后长这样:

连线图.jpg

        灯光控制的接口实现如下,很简易,使用的Python版本是3.6。post接口接收两个参数"cmd"和"color","cmd"表明是开启或者关闭,"color"表明控制哪一个颜色的灯。post请求处理后会相应地返回“X色灯已开启”或“X色灯已关闭”。

import RPi.GPIO as GPIO
import os.path

from tornado import ioloop
from tornado import web
from tornado import httpserver

#red-GPIO5-29
#green-GPIO12-32
#yellow-GPIO16-36
lights = {red:29, green:32, yellow:36}
color_mapping = {红色:red, 绿色:green, 黄色:yellow,
                 红:red, 绿:green, 黄:yellow}

#test handler
class MainHandler(web.RequestHandler):
    def get(self):
        self.write("Hello, world")
    def head(self):
        self.write("success")
        
class LightHandler(web.RequestHandler):
    #check current lights status
    def get(self):
        for color in lights:
            status = GPIO.input(lights[color])
            if status == 1:
                self.write(color_mapping[color]+"灯已开启</br>")
            elif status == 0:
                self.write(color_mapping[color]+"灯已关闭</br>")
        
    #set lights status
    def post(self):
        cmd = self.get_argument(cmd)
        print(cmd+ )
        try:
            colorName = self.get_argument(color)
            print(colorName)
            color = color_mapping[colorName]
        except:
            self.write("缺少颜色参数")
            return
        if cmd == open:
            if light_on(color):
                self.write(colorName+"灯已开启")
            else:
                self.write("未找到对应的灯")
        elif cmd == close:
            if light_off(color):
                self.write(colorName+"灯已关闭")
            else:
                self.write("未找到对应的灯")
        self.finish()
                
#open light
def light_on(color):
    if (color in lights):
        GPIO.output(lights[color], GPIO.HIGH)
        return True
    else:
        return False

#close light
def light_off(color):  
    if (color in lights):
        GPIO.output(lights[color], GPIO.LOW)
        return True
    else:
        return False

def make_app():
    return web.Application([
        (r"/", MainHandler),
        (r"/light",LightHandler)
    ])

if __name__ == "__main__":
    #initial GPIO
    GPIO.setmode(GPIO.BOARD)
    for color in lights:
        GPIO.setup(lights[color], GPIO.OUT)

    #initial http server
    app = make_app()
    app.listen(8888)    
    ioloop.IOLoop.current().start()

        运行后服务就部署在了树莓派的8888端口上,但这样还没法从外部访问到这个接口,所以借助Ngork进行内网穿透。我用的是https://www.ngrok.cc/ 提供的服务,可以免费创建ngrok隧道。隧道的设置里可以修改前置域名和端口号。

ngork.png

        然后在树莓派上下载网站上提供的Python版本的Ngrok客户端,运行后输入隧道的clientid就能创建隧道,实现内网穿透。在外网通过隧道设置里的地址xxxxx.free.ngrok.cc就能访问到部署在树莓派上的服务。

0
#3顺达回复于2017-12-30

技能创建

        在技能平台上创建灯光控制的技能,配置意图、槽位和相应的词典。槽位有两个,一个是控制指令,一个是颜色,对应post请求中的两个参数。

技能配置.png

        技能回复选择“服务配置满足”。

image.png

        控制词典是自己设置的,颜色词典直接用系统提供的。

控制词典.png


0
#4顺达回复于2017-12-30

配置服务

       当技能的意图、槽位、词典都配置好后,我天真地以为自己离胜利近在咫尺了,只需要把灯光控制的服务地址填到服务配置里就行。然而并不行。。。提示我说需要https,而且我去看了看技能协议的文档,似乎还得有什么心跳机制。当时我就很难受,我只是想简单调个post接口而已,要这么搞也太麻烦了吧,而且python还没有bot-sdk。然后我看到服务配置里还有个百度云cfc的选项,就想到是否能在那上面发post请求调接口,实验了下确实可以。

       在cfc平台选择从模板新建函数:

image.png

        创建出来的是“查个税”的例程,我就直接在上面进行了修改。

image.png

        新的代码如下,使用http模块发送post请求。通过Promise异步回调让小度能根据我post返回的内容进行回复:

class InquiryBot extends Bot {
    constructor(postData) {
        super(postData);

        this.addLaunchHandler(() => {
            return {
                outputSpeech: 欢迎使用灯光控制
            };
        });

        this.addIntentHandler(light_control, () => {
            let cmd = this.getSlot(cmd);
            let color = this.getSlot(color);

            if (this.request.isDialogStateCompleted()) {
                var data = {
                    cmd: cmd,
                    //前面拿到的color格式是这样的:color: {"color":"红色","origin":"红色"},所以得先JSON格式化后才能拿出颜色值
                    color: JSON.parse(color)[color]  
                };
                data = require(querystring).stringify(data);
                
                var opt = {
                    method: "POST",
                    host: "xxxxxx.free.ngrok.cc",     //灯光控制服务的地址
                    path: "/light",
                    headers:{
                        Content-Type:application/x-www-form-urlencoded,
                        Content-Length: data.length
                    }
                };
                var http = require(http);
                
                return new Promise((resolve,reject) => {
                    var req = http.request(opt, function(res) {
                        res.setEncoding("utf-8");
                        res.on('data', (content) => {
                            let card = new Bot.Card.TextCard(content.toString())
                            return resolve({
                                card: card,
                                outputSpeech: content.toString()
                            });
                        });
                    });
                    req.write(data);
                });
            }
            else {
                let card = new Bot.Card.TextCard("没有提供足够的参数");
                return {
                    card: card,
                    outputSpeech: "没有提供足够的参数"
                };
            }
        });
    }
}

        最后将函数的BRN填入服务部署里,总算大功告成了。

image.png

0
#5顺达回复于2017-12-30

测试和上线

     我是在技能平台升级前完成的这个技能,一大遗憾就是当时的技能平台不支持真机测试,所以只能在模拟测试中测试技能。模拟测试还是很ok的,能够顺利控制三色LED的亮暗,很可惜没有保存当时测试的截图。然后顺手给技能申请了上线,结果真的就上线成功了。

image.png

上线.jpg

       新升级的平台又加了不少新功能,比如支持真机测试了。不过升级后我还没有尝试过,等有空了到真机上试试看。

0
#6Joyce回复于2017-12-30

楼主,请问一下跟音箱对话之后,语音经过ASR,再经过NLP,最后进入skill里,可以让skill不返回TTS,而是后台返回其他的数据或执行其他的指令吗

0
#7顺达回复于2017-12-30
#6 Joyce回复
楼主,请问一下跟音箱对话之后,语音经过ASR,再经过NLP,最后进入skill里,可以让skill不返回TTS,而是后台返回其他的数据或执行其他的指令吗
展开

技能是你自己开发,你如果想在技能里执行其他的指令当然是可以的

0
#8stormspirit回复于2018-01-22

楼主,感觉你这么配置下来,树莓派不装dueros也可以啊,没看懂装dueros的意义,因为发布技能之后,利用手机就能收集到语音信息了。

装dueros的板子是取代手机的功能么

0
#9Mr.chen回复于2018-02-02

老哥方便留个Q么,想请教些问题

0
#10顺达回复于2018-02-02
#9 Mr.chen回复
老哥方便留个Q么,想请教些问题

923076444

0
#11顺达回复于2018-02-02
#8 stormspirit回复
楼主,感觉你这么配置下来,树莓派不装dueros也可以啊,没看懂装dueros的意义,因为发布技能之后,利用手机就能收集到语音信息了。 装dueros的板子是取代手机的功能么
展开

原本就是想做树莓派上的语音管家,没想在手机上做

0
#12归哲晨回复于2018-05-04

你好,我按照您的步骤从cfc平台选择从模板新建函数,函数里访问了第三方服务器,但是一直没收到第三方服务器的返回数据即:一直没有进入res.on('data',(content)=>{}),可以确定的是第三方服务器接到到请求了且有数据返回,请问是什么原因,盼回复

return new Promise((resolve,reject) => {
                    var req = http.request(option, function(res) {
                        
                        res.on('data', (content) => {
                          
                            console.log("content" + content);
                            let msg="有数据返回";
                            content=JSON.parse(content);

                            let card = new Bot.Card.TextCard(msg);
                                return {
                                    card: card,
                                    outputSpeech: '<speak>'+msg+'</speak>'
                                 };


                        });   
                    }).on('error',function(err){
                            console.log('error ' + err);
                    });
                    req.setHeader("Authorization",'XXXXXXXXXXXX');
                    req.write(body);//传递参数
                    req.end();
                });
0
#13顺达回复于2018-06-19
#12 归哲晨回复
你好,我按照您的步骤从cfc平台选择从模板新建函数,函数里访问了第三方服务器,但是一直没收到第三方服务器的返回数据即:一直没有进入res.on('data',(content)=>{}),可以确定的是第三方服务器接到到请求了且有数据返回,请问是什么原因,盼回复 [代码]
展开
return resolve({
    card: card,
    outputSpeech: '<speak>'+msg+'</speak>'
});

返回的时候加下resolve试试

0
TOP