现在,智能家居的论题越来越火,物联网现已融入了咱们日子。最近闲在家里了解了一下这方面的布景知识,自己动手做了一个相似天猫精灵的物联网智能终端。于是打算出一个教程分享一下我的研究成果。

硬件预备

  • 一台能连上网的电脑(体系最好是Windows 10)

布景知识

物联网智能终端

相似于天猫精灵这类产品咱们都把它叫做智能终端(或许智能音箱)。它能经过用户之间的语音互动对智能家居进行指令操控,也能供给一些内置的软件服务,比方语音提示和QQ音乐等。因而,我把它总结出以下几个功用模块。

手把手教你做一个天猫精灵(一)

现在,市面上除了天猫精灵,还有百度小度、小米小爱、Google Home和比亚迪小迪等。

语音处理术语

智能终端运用了许多的语音处理方面的人工智能算法,这些算法可软件完成也能够硬件完成,首要包含语音辨认(Automated Speech Recognition,ASR)、语音组成(Text To Speech,TTS)和语音唤醒(Keyword Spotting,KWS)。

一般的流程是这样的,先经过语音唤醒捕捉用户的Keyword,当捕捉到了才给用户录音并作语音辨认,然后依据用户的意图调用相应的API并经过语音组成给用户适当的反馈。据我观察至少有三种API调用方法,或许说是对外调用的接口,总结如下:

  • MQTT音讯:智能终端操作硬件一般采用IBM定义的MQTT协议,这种音讯协议相似 Kafka,可是它更适应网络环境不稳定的场景,比方多了服务质量(QoS)和遗言机制(Last Will),这部分内容以后介绍。
  • 蓝牙接口:还有一种是经过蓝牙Mesh直接和硬件相连,不需求联网就能直接操作硬件。
  • HTTP接口:这种接口一般是供给软件服务的,比方QQ音乐、墨迹气候和喜马拉雅播送

快速开端

考虑到或许会接入许多运用功用,所以本项目是经过Python开发的。新建一个工程,并设置好环境,然后下载这个包:

pip install fubuki-iot

提示:假如下载不了能够换源,也能够在GitHub上下载,仓库地址点这儿 。一起也欢迎PR。

然后创立一个程序入口,我把它命名为 app.py, 然后调用 Terminalrun函数,如下:

from iot import Terminal
if __name__ == '__main__':
    Terminal.run()

当然现在这个程序是跑不起来的,由于没有相应的配置。新建一个目录叫 resources ,然后再创立一个配置文件 .env ,留意前面有一个点。接着,把刚刚新建的 resource 文件的途径写到这个配置文件中,如下:

RESOURCE_PATH=你的resources的途径

这时候,你的项目目录应该是这样的:

手把手教你做一个天猫精灵(一)

原则上就能够运转了,可是像上文所说需求语音处理相关的能力,所以我先暂时借用了百度智能云的AI能力。这个渠道能够免费供给半年的人工智能服务,所以先去请求以下相关服务。

进入百度智能云的官网,依次点击“产品”-“人工智能”-“语音技术”,然后请求这个AI服务。之后就能够在操控台中找到自己的API Key和Secret Key,如图所示:

手把手教你做一个天猫精灵(一)

然后在刚才的 .env文件中追加这两条信息,并预留一个BAIDU_ACCESS_TOKEN字段,如下所示:

BAIDU_API_KEY=你的API Key
BAIDU_SECRET_KEY=你的Secret Key
BAIDU_ACCESS_TOKEN=

提示:由于百度API是要生成access token运用的,这个token是有时效的,所以这儿预留能够便利以后不用重复请求,可是过了时效仍是要把这个字段置空,就像现在这个姿态。

当然,百度API试用结束后仍是会收费,假如你不想收费能够自己练习模型来替代它,这个以后会介绍。

现在就能够运转这个程序了。运转结果如图所示:

手把手教你做一个天猫精灵(一)

按下“F”键就能够唤醒终端,它会回应“我在,你说”。然后等它开端录音就能够说“你好”,它会回应“在的”。至此,一个极简的智能终端就做好了。

敞开语音唤醒

虽然智能终端的姿态现已有了,可是间隔天猫精灵还差远了。现在,就让咱们敞开它的语音唤醒功用。

这个终端内置了一个PocketSphinx的语音唤醒功用。这个工具是C写的,因而要在Windows环境下需求对应的编译器,官方推荐的是Visual C++。

首先点这儿下载Visual Studio Installer,然后装置,接着翻开以后挑选装置“单个组件”,然后勾选这三个组件“Windows 10 SDK”、“Windows 通用 CRT SDK”以及“MSVC v140 – VS 2015 C++ 生成工具(v14.00)”,下载完即可。这个环境变量无需配置。

手把手教你做一个天猫精灵(一)

留意:有或许碰到“无法翻开包含文件: “windows.h”: No such file or directory”的问题,这个需求将<windows.h> 头文件放在指定目录,详细自行百度或许谷歌。

然后下载swigwin,这个软件是让python调用C/C++编译的软件的,点这儿下载,下载完成后解压,并配置环境变量,然后重启收效。

最终,装置PocketSphinx。

pip install pocketsphinx

假如没有报错就阐明成功了,有报错或许和C语言环境有关,这个需求详细问题详细分析了。

然后在 .env 文件中追加两行配置:

TERMINAL_MODE=0
DEVICE_REC=PocketsphinxRecorder

接着发动程序能够经过对他说“hello”或许“hi”唤醒它。

编写第一个程序

接下来,咱们要开端向终端里边增加功用。相似天猫精灵的守时提示功用,咱们也做一个提示功用。

首先创立一个包,命名为 mods,咱们接下来写的模组都将放在这个包里。新建一个文件命名为timer.py,然后在文件里新建一个语义模型,这个模型承继了SemanticsModel,其目的是为了匹配用户的指令,如下所示:

@SemanticsGroup.add_model
class TimerSemanticsModel(SemanticsModel):
    code = 'timer'
    frm = SemanticsFromEnum.USER
    topic = ''
    regex = '(.*)后提示我(.*)'
    regex_num = 3
    redirect = SemanticsRedirectEnum.ACOUSTICS
    func: SemanticsFunc = timer_semantics_func

字段阐明如下:

  • code:语义模型的标识,这儿随便填,只用来区分。
  • frm:语义来源,这儿是用户
  • topic:由于来自用户,所以这儿不需求填
  • regex:正则表达式,用来匹配用户的指令,假如匹配射中则运用这个语义模型,不然持续匹配下一个语义模型,假如都没有射中则发送兜底的语音回运用户
  • regex_num:上面正则表达式的groups()个数,至少为1,表明全量,详细参阅Python正则表达式相关文档,这个是用来抽取参数的
  • redirect:重定向,这儿不需求处理硬件,只是回来语音,所以是 ACOUSTICS
  • func:用来处理这个指令的回调函数

重点就在这个回调函数,它是处理语义的要害。在当时环境下,它接受一个列表,即正则表达式匹配的groups,咱们能够经过数组下标拿到想要的参数,并作后续处理。比方:

def timer_semantics_func(*args) -> FunctionDeviceModel:
    sentence = args[0] # 获取全文
    timespan: str = args[1] # 第一个参数,时刻
    content = args[2] # 第二个参数,内容
    ...

此外它回来一个功用设备模型FunctionDeviceModel,它告知终端要怎么处理,即怎么回来给用户信息。其字段如下。

class FunctionDeviceModel(BaseModel):
    """
    功用设备模型,既是用户发送指令的封装,也是语义转化模块处理后的产物。
    """
    # 对应的semantics_model的code
    smt_code: Optional[str]
    # raw标志位,假如为True则data为str,不然为dict
    is_raw: bool
    # 履行成功后回来的语音提示,假如重定向到MESSAGE则作为语音提示,
    # 假如是重定向到ACOUSTICS则也能够表明回来内容
    acoustics: str
    # 数据,只有当is_raw为False才为字典,用于展示详细内容
    data: Union[str, Dict[str, str]]

因而,做一个提示功用便是判别用户的句子是否正确,假如正确则回来正确的语音信息,不然告诉用户听不懂,详细如下:

def timer_semantics_func(*args) -> FunctionDeviceModel:
    ...
    if not is_ok:
        return FunctionDeviceModel(
            smt_code='timer',
            is_raw=True,
            acoustics="抱歉我没听清",
            data=""
        )
    else:
        scheduler.start()
        return FunctionDeviceModel(
            smt_code='timer',
            is_raw=True,
            acoustics=f"好的,我会在{timespan}后提示你{content}",
            data=""
        )

有正确的处理和回来还不行,还需求在时刻到了的时候给用户语音提示,这完成也很简略,只要从Context中获取相应的API就能够了,详细如下:

path = Context.tts_processor.tts("您好,时刻到了,请" + content)
Context.player.play(path)

完成守时的计划有许多种,我采用了APScheduler完成了这个功用,下面仅供参阅:

def ch2i(ch: str):
    try:
        # 测验直接转化
        return int(ch)
    except Exception:
        pass
    _dict = {
        "一": 1,
        "二": 2,
        "三": 3,
        "四": 4,
        "五": 5,
        "六": 6,
        "七": 7,
        "八": 8,
        "九": 9,
        "十": 10,
        "百": 100
    }
    if ch.startswith("十") or ch.startswith("百"):
        ch = "一" + ch
    _buf = list()
    _res = 0
    for i in range(len(ch)):
        if len(_buf) == 0: 
            _buf.append(_dict[ch[i]]) 
        else: 
            _res += _buf.pop() * _dict[ch[i]]
    return _res + (_buf[0] if len(_buf) else 0)
def timer_semantics_func(*args) -> FunctionDeviceModel:
    sentence = args[0] # 获取全文
    timespan: str = args[1] # 获取时刻
    content = args[2] # 获取内容
    is_ok = True
    scheduler = BackgroundScheduler()
    def _job():
        path = Context.tts_processor.tts("您好,时刻到了,请" + content)
        Context.player.play(path)
    try:
        if timespan.endswith("秒"):
            target = ch2i(timespan.split("秒")[0])
            scheduler.add_job(_job, 'date', run_date=datetime.datetime.now() + datetime.timedelta(seconds=target))
        elif timespan.endswith("分钟"):
            target = ch2i(timespan.split("分钟")[0])
            scheduler.add_job(_job, 'date', run_date=datetime.datetime.now() + datetime.timedelta(minutes=target))
        elif timespan.endswith("小时"):
            target = ch2i(timespan.split("小时")[0])
            scheduler.add_job(_job, 'date', run_date=datetime.datetime.now() + datetime.timedelta(hours=target))
        else:
            # 只处理到小时,更大的单位不处理了
            raise RuntimeError("Fail to match")
    except Exception as e:
        logger.error("Fail to translate " + e.__str__())
        is_ok = False
    if not is_ok:
        return FunctionDeviceModel(
            smt_code='timer',
            is_raw=True,
            acoustics="抱歉我没听清",
            data=""
        )
    else:
        scheduler.start()
        return FunctionDeviceModel(
            smt_code='timer',
            is_raw=True,
            acoustics=f"好的,我会在{timespan}后提示你{content}",
            data=""
        )
@SemanticsGroup.add_model
class TimerSemanticsModel(SemanticsModel):
    code = 'timer'
    frm = SemanticsFromEnum.USER
    topic = ''
    regex = '(.*)后提示我(.*)'
    regex_num = 3
    redirect = SemanticsRedirectEnum.ACOUSTICS
    func: SemanticsFunc = timer_semantics_func

最终,在程序入口处加载这个包:

from iot import Terminal
Terminal.load_models('mods.timer')
if __name__ == '__main__':
    Terminal.run()

现在,再发动程序,对它说“五分钟后提示我打扫卫生”它就会回应,并在五分钟后经过语音播报提示咱们。

本章初步介绍了物联网领域的相关概念,然后跑通了一个简略的智能终端的程序。下一章咱们会进一步完善这个程序,并把它做成硬件,离咱们的方针更近一步!