注意
混合现实学院教程在制作时考虑到了 HoloLens(第一代)和混合现实沉浸式头戴显示设备。 因此,对于仍在寻求这些设备的开发指导的开发人员而言,我们觉得很有必要保留这些教程。 我们不会在这些教程中更新 HoloLens 2 所用的最新工具集或集成相关的内容。 我们将维护这些教程,使之持续适用于支持的设备。 将来会发布一系列演示如何针对 HoloLens 2 进行开发的新教程。 此通知将在教程发布时通过指向这些教程的链接进行更新。
在本课程中,你将学习如何在运行 Ubuntu 16.4 操作系统的虚拟机上实现 Azure IoT 中心服务。 然后使用 Azure 函数应用从 Ubuntu VM 接收消息,并将结果存储在 Azure 表服务中。 之后可以使用 Microsoft HoloLens 或沉浸式 (VR) 头戴显示设备上的 Power BI 来查看此数据。
本课程内容适用于 IoT Edge 设备,但就本课程而言,重点在于虚拟机环境,因此不必访问物理 Edge 设备。
完成本课程后,你将学习:
- 将 IoT Edge 模块部署到虚拟机 (Ubuntu 16 OS),该虚拟机将表示 IoT 设备。
- 将 Azure 自定义视觉 Tensorflow 模型添加到 Edge 模块,该模型包含用于分析容器中存储的映像的代码。
- 设置此模块以将分析结果消息发送回 IoT 中心服务。
- 使用 Azure 函数应用将消息存储在 Azure 表中。
- 设置 Power BI 以收集存储的消息并创建报告。
- 在 Power BI 中可视化 IoT 消息数据。
将使用的服务包括:
Azure IoT 中心是一项 Microsoft Azure 服务,它允许开发人员连接、监视和管理 IoT 资产。 有关详细信息,请访问“Azure IoT 中心服务”页。
Azure 容器注册表是一项 Microsoft Azure 服务,它允许开发人员为各种类型的容器存储容器映像。 有关详细信息,请访问“Azure 容器注册表服务”页。
Azure 函数应用是一项 Microsoft Azure 服务,它允许开发人员在 Azure 中运行小段代码“函数”。 这提供了一种将工作委托给云(而不是本地应用程序)的方法,这种方法会有很多优势。 Azure Functions 支持多种开发语言,包括 C#、F#、Node.js、Java 和 PHP。 有关详细信息,请访问“Azure Functions”页。
Azure 存储:表是一项 Microsoft Azure 服务,它允许开发人员在云中存储结构化的非 SQL 数据,使其在任何位置都可轻松访问。 此服务采用无架构设计,允许根据需要演变表,因此非常灵活。 有关详细信息,请访问“Azure 表”页
本课程将介绍如何设置和使用 IoT 中心服务,然后可视化设备提供的响应。 你可以自行决定将这些概念应用到你可能会生成的自定义 IoT 中心服务设置。
设备支持
课程 | HoloLens | 沉浸式头戴显示设备 |
---|---|---|
MR 和 Azure 313:IoT 中心服务 | ✔️ | ✔️ |
先决条件
有关利用混合现实(包括 Microsoft HoloLens)进行开发的最新先决条件,请参阅安装工具一文。
注意
本教程专为具有 Python 基本经验的开发人员设计。 另请注意,本文档中的先决条件和书面说明在编写时(2018 年 7 月)已经过测试和验证。 可以随意使用最新的软件(如安装工具一文所列),但不应假设本课程中的信息将与你在较新的软件中找到的信息(而不是下面列出的内容)完全匹配。
以下硬件和软件是必需的:
已启用开发人员模式的 Windows 10 Fall Creators Update(或更高版本)
警告
不能使用 Windows 10 家庭版上的 Hyper-V 运行虚拟机。
Windows 10 SDK(最新版本)
已启用开发人员模式的 HoloLens
Visual Studio 2017.15.4(仅用于访问 Azure Cloud Explorer)
针对 Azure 和 IoT 中心服务的 Internet 访问。 有关详细信息,请单击此链接访问“IoT 中心服务”页
一个机器学习模型。 如果没有现成可用的模型,可以使用本课程提供的模型。
在 Windows 10 开发计算机上启用的 Hyper-V 软件。
运行在开发计算机上的运行 Ubuntu(16.4 或 18.4)的虚拟机,或者也可以使用运行 Linux(Ubuntu 16.4 或 18.4)的单独的计算机。 可以在“准备工作”一章中找到有关如何使用 Hyper-V 在 Windows 上创建 VM 的详细信息。
开始之前
- 设置并测试 HoloLens。 如需有关设置 HoloLens 的支持,请确保参阅“HoloLens 设置”一文。
- 在开始开发新的 HoloLens 应用时,最好执行校准和传感器优化(有时 HoloLens 应用可以帮助为每个用户执行这些任务)。
有关校准的帮助信息,请单击此链接访问“HoloLens 校准”一文。
有关传感器优化的帮助信息,请单击此链接访问“HoloLens 传感器优化”一文。
使用 Hyper-V 设置 Ubuntu 虚拟机。 以下资源将帮助完成此过程。
- 首先,请单击此链接下载 Ubuntu 16.04.4 LTS (Xenial Xerus) ISO。 选择 64 位 (AMD64) 桌面映像。
- 请确保 Windows 10 计算机上启用了 Hyper-V。 可以单击此链接,获取有关在 Windows 10 上安装和启用 Hyper-V 的指南。
- 启动 Hyper-V 并创建新的 Ubuntu VM。 可以单击此链接,了解有关如何使用 Hyper-V 创建 VM 的分步指导。 当请求“从可启动的映像文件安装操作系统”时,选择之前下载的 Ubuntu ISO。
注意
不建议使用“Hyper-V 快速创建”。
第 1 章 - 检索自定义视觉模型
在本课程中,你将有权访问预生成的自定义视觉模型,该模型用于从映像中检测键盘和鼠标。 如果使用此模型,请转到第 2 章。
但是,如果想要使用自己的自定义视觉模型,则可以执行以下步骤:
在“自定义视觉项目”中转到“性能”选项卡。
警告
模型必须使用精简域来导出模型。 可以在设置中更改项目的模型域。
选择要导出的“迭代”,然后单击“导出”。 随即出现一个边栏选项卡。
在边栏选项卡中,单击“Docker 文件”。
单击下拉菜单中的“Linux”,然后单击“下载”。
解压缩内容。 稍后将在本课程中使用它。
第 2 章 - 容器注册表服务
容器注册表服务是用于托管容器的存储库。
将在本课程中生成和使用的 IoT 中心服务是指容器注册表服务,用于获取要部署在 Edge 设备中的容器。
首先,单击此链接转到 Azure 门户,然后使用凭据登录。
转到“创建资源”并查找“容器注册表”。
单击“创建”。
设置服务设置参数:
插入项目的名称,在本示例中,它称为 IoTCRegistry。
选择一个资源组或创建一个新资源组。 资源组提供一种监视、控制访问、预配和管理 Azure 资产集合计费的方法。 建议保留与常用资源组下的单个项目(例如这些课程)关联的所有 Azure 服务。
设置服务位置。
将“管理员用户”设置为“启用”。
将“SKU”设置为“基本”。
单击“创建”并等待服务创建完成。
在弹出通知并显示已成功创建容器注册表后,单击“转到资源”以重定向到“服务”页。
在“容器注册表”的“服务”页中,单击“访问密钥”。
记下(可以使用记事本)以下参数:
- 登录服务器
- 用户名
- 密码
第 3 章 - IoT 中心服务
现在开始创建和设置 IoT 中心服务。
如果尚未登录,请登录到 Azure 门户。
登录后,单击左上角的“创建资源”,搜索“IoT 中心”,并单击“Enter”。
新页面将提供“存储帐户”服务的说明。 在该提示的左下角,单击“创建”按钮,创建此服务的实例。
单击“创建”后,随即显示一个面板:
选择一个资源组或创建一个新资源组。 通过资源组,可监视和预配 Azure 资产集合、控制其访问权限并管理其计费。 建议保留与常用资源组下的单个项目(例如这些课程)关联的所有 Azure 服务。
若要详细了解 Azure 资源组,请单击此链接了解如何管理资源组。
选择适当的位置(本课程中创建的所有服务使用同一位置)。
插入此服务实例的所需名称。
在页面底部单击“下一步: 大小和规模”。
在此页中,选择“定价和缩放层”(如果这是首个 IoT 中心服务实例,则应可使用免费层)。
单击“查看 + 创建”。
查看设置,然后单击“创建”。
在弹出通知并显示已成功创建 IoT 中心服务后,单击“转到资源”以重定向到“服务”页。
滚动左侧面板,直到看到“自动设备管理”,然后单击“IoT Edge”。
在右侧出现的窗口中,单击“添加 IoT Edge 设备”。 右侧随即出现一个边栏选项卡。
在边栏选项卡中,提供新设备的“设备 ID”(你选择的名称)。 然后,单击“保存”。 如果已勾选“自动生成”,则将自动生成“主密钥”和“辅助密钥”。
导航回“IoT Edge 设备”部分,其中将列出新设备。 单击新设备(下图红色边框所示)。
在出现的“设备详细信息”页中,复制连接字符串(主密钥)。
返回左侧面板,单击“共享访问策略”将其打开。
在显示的页面中,单击“iothubowner”,屏幕右侧随即出现一个边栏选项卡。
记下(在记事本上)连接字符串(主密钥),以便之后在设置“连接字符串”以连接到设备时使用。
第 4 章 - 设置开发环境
若要为 IoT 中心 Edge 创建和部署模块,需要在运行 Windows 10 的开发计算机上安装以下组件:
适用于 Windows 的 Docker,它将要求你创建一个可供下载的帐户。
重要
Docker 需要 Windows 10 PRO、Enterprise 14393 或 Windows Server 2016 RTM 才能运行。 如果运行的是其他版本的 Windows 10,可以尝试使用 Docker 工具箱安装 Docker。
安装上述软件之后,需要重启计算机。
第 5 章 - 设置 Ubuntu 环境
现在,可以转到设置运行 Ubuntu OS 的设备。 按照以下步骤安装所需的软件,以便在开发板上部署容器:
重要
应始终将 sudo 置于终端命令之前,以便以管理员用户身份运行。 即:
sudo docker \<option> \<command> \<argument>
打开 Ubuntu 终端,并使用以下命令安装 pip:
[!提示] 可以使用键盘快捷方式非常轻松地打开终端:Ctrl + Alt + T。
sudo apt-get install python-pip
在本章中,终端可能会提示你授予使用设备存储的权限并要求你输入 y/n(是或否),请键入“y”,然后按“Enter”键接受。
完成该命令后,使用以下命令安装 curl:
sudo apt install curl
安装 pip 和 curl 后,使用以下命令安装 IoT Edge 运行时,这是在开发板上部署和控制模块所必需的:
curl https://packages.microsoft.com/config/ubuntu/16.04/prod.list > ./microsoft-prod.list sudo cp ./microsoft-prod.list /etc/apt/sources.list.d/ curl https://packages.microsoft.com/keys/microsoft.asc | gpg --dearmor > microsoft.gpg sudo cp ./microsoft.gpg /etc/apt/trusted.gpg.d/ sudo apt-get update sudo apt-get install moby-engine sudo apt-get install moby-cli sudo apt-get update sudo apt-get install iotedge
此时,系统会提示你打开运行时配置文件,并插入在创建 IoT 中心服务(第 3 章,步骤 14)时记下(在记事本中)的设备连接字符串。 在终端上运行以下行,以打开该文件:
sudo nano /etc/iotedge/config.yaml
此时将显示 config.yaml 文件,以供编辑:
警告
此文件打开时,可能会令你有些困惑。 你将在终端本身中编辑此文件的文本。
使用键盘上的箭头键向下滚动(需稍稍向下滚动一点),到达包含以下内容的列:
“<在此处添加设备连接字符串>”。
用之前记下的设备连接字符串替换这一行(包括括号)。
放置连接字符串后,请在键盘上按 Ctrl + X 键保存该文件。 系统将要求键入 Y 进行确认。然后,按 Enter 键以确认。 将返回到常规终端。
成功运行这些命令后,将安装 IoT Edge 运行时。 初始化后,每次打开设备时,运行时都会自行启动,并在后台等待从 IoT 中心服务部署模块。
运行以下命令行,初始化 IoT Edge 运行时:
sudo systemctl restart iotedge
重要
如果对 .yaml 文件或上述设置进行更改,则需要在终端中再次运行上述重启行。
运行以下命令行,检查 IoT Edge 运行时状态。 此时将显示带绿色文本“活动(正在运行)”状态的运行时。
sudo systemctl status iotedge
按 Ctrl + C 键退出状态页。 可以通过键入以下命令来验证 IoT Edge 运行时是否正在正确拉取容器:
sudo docker ps
此时应显示包含两 (2) 个容器的列表。 这些是 IoT 中心服务自动创建的默认模块(edgeAgent 和 edgeHub)。 创建和部署自己的模块后,这些模块将显示在此列表的默认模块下方。
第 6 章 - 安装扩展
重要
接下来几章 (6-9) 的步骤将在 Windows 10 计算机上执行。
打开 VS Code。
单击 VS Code 左侧栏上的“扩展”(方形)按钮,打开“扩展”面板。
搜索并安装以下扩展(如下图所示):
- Azure IoT Edge
- Azure IoT 工具包
- Docker
安装扩展后,关闭并重新打开 VS Code。
再次打开 VS Code 后,导航到“查看”>“集成终端”。
现在安装“Cookiecutter”。 在终端中运行以下 bash 命令:
pip install --upgrade --user cookiecutter
[!提示] 如果运行此命令出现问题:
- 重启 VS Code 和/或计算机。
- 可能需要将 VS Code 终端切换到用于安装 Python 的终端,即 Powershell(尤其是在计算机上已安装 Python 环境的情况下)。 打开终端后,将在终端右侧找到下拉菜单。
- 确保已在计算机上将 Python 安装路径添加为环境变量。 Cookiecutter 应为同一位置路径的一部分。 请单击此链接详细了解环境变量。
完成 Cookiecutter 安装后,应重启计算机,以便在系统环境中将 Cookiecutter 识别为命令。
第 7 章 - 创建容器解决方案
此时,需要创建包含模块的容器,以推送到容器注册表中。 推送容器后,将使用 IoT 中心 Edge 服务将其部署到运行 IoT Edge 运行时的设备。
从 VS Code 单击“查看”>“命令面板”。
在面板中,搜索并运行“Azure IoT Edge: 新 IoT Edge 解决方案”。
浏览到要创建解决方案的位置。 按 Enter 键接受该位置。
为解决方案提供名称。 按 Enter 键确认提供的名称。
现在,系统会提示你为解决方案选择模板框架。 单击“Python 模块”。 按 Enter 键确认选择。
为模板提供名称。 按 Enter 键确认模块的名称。 请务必记下(使用记事本)模块名称,因为稍后会使用它。
此时会发现面板上显示预生成的 Docker 映像存储库地址。 如下所示:
localhost:5000/-模板名称-。
删除 localhost:5000,并将容器注册表登录服务器地址插入到创建容器注册表服务时记下的容器注册表登录服务器地址(在第 2 章的步骤 8 中)。 按 Enter 键确认地址。
此时,将创建包含 Python 模块模板的解决方案,其结构将显示在屏幕左侧的 VS Code 的“浏览”选项卡中。 如果“浏览”选项卡未打开,可以通过单击左侧栏中最顶部的按钮将其打开。
本章的最后一步是从“浏览”选项卡中单击并打开 .env 文件,然后添加容器注册表用户名和密码。 git 会忽略此文件,但在生成容器时,此文件将设置用于访问容器注册表服务的凭据。
第 8 章 - 编辑容器解决方案
现在将通过更新以下文件来完成容器解决方案:
- main.py Python 脚本。
- requirements.txt。
- deployment.template.json。
- Dockerfile.amd64
然后创建“images”文件夹,Python 脚本将使用该文件夹来检查映像是否与自定义视觉模型相匹配。 最后将添加 labels.txt 文件(以帮助读取模型)和 model.pb 文件(即模型)。
打开 VS Code,导航到模块文件夹,并查找名为main.py 的脚本。 双击将其打开。
删除文件内容并插入以下代码:
# Copyright (c) Microsoft. All rights reserved. # Licensed under the MIT license. See LICENSE file in the project root for # full license information. import random import sched, time import sys import iothub_client from iothub_client import IoTHubModuleClient, IoTHubClientError, IoTHubTransportProvider from iothub_client import IoTHubMessage, IoTHubMessageDispositionResult, IoTHubError import json import os import tensorflow as tf import os from PIL import Image import numpy as np import cv2 # messageTimeout - the maximum time in milliseconds until a message times out. # The timeout period starts at IoTHubModuleClient.send_event_async. # By default, messages do not expire. MESSAGE_TIMEOUT = 10000 # global counters RECEIVE_CALLBACKS = 0 SEND_CALLBACKS = 0 TEMPERATURE_THRESHOLD = 25 TWIN_CALLBACKS = 0 # Choose HTTP, AMQP or MQTT as transport protocol. Currently only MQTT is supported. PROTOCOL = IoTHubTransportProvider.MQTT # Callback received when the message that we're forwarding is processed. def send_confirmation_callback(message, result, user_context): global SEND_CALLBACKS print ( "Confirmation[%d] received for message with result = %s" % (user_context, result) ) map_properties = message.properties() key_value_pair = map_properties.get_internals() print ( " Properties: %s" % key_value_pair ) SEND_CALLBACKS += 1 print ( " Total calls confirmed: %d" % SEND_CALLBACKS ) def convert_to_opencv(image): # RGB -> BGR conversion is performed as well. r,g,b = np.array(image).T opencv_image = np.array([b,g,r]).transpose() return opencv_image def crop_center(img,cropx,cropy): h, w = img.shape[:2] startx = w//2-(cropx//2) starty = h//2-(cropy//2) return img[starty:starty+cropy, startx:startx+cropx] def resize_down_to_1600_max_dim(image): h, w = image.shape[:2] if (h < 1600 and w < 1600): return image new_size = (1600 * w // h, 1600) if (h > w) else (1600, 1600 * h // w) return cv2.resize(image, new_size, interpolation = cv2.INTER_LINEAR) def resize_to_256_square(image): h, w = image.shape[:2] return cv2.resize(image, (256, 256), interpolation = cv2.INTER_LINEAR) def update_orientation(image): exif_orientation_tag = 0x0112 if hasattr(image, '_getexif'): exif = image._getexif() if (exif != None and exif_orientation_tag in exif): orientation = exif.get(exif_orientation_tag, 1) # orientation is 1 based, shift to zero based and flip/transpose based on 0-based values orientation -= 1 if orientation >= 4: image = image.transpose(Image.TRANSPOSE) if orientation == 2 or orientation == 3 or orientation == 6 or orientation == 7: image = image.transpose(Image.FLIP_TOP_BOTTOM) if orientation == 1 or orientation == 2 or orientation == 5 or orientation == 6: image = image.transpose(Image.FLIP_LEFT_RIGHT) return image def analyse(hubManager): messages_sent = 0; while True: #def send_message(): print ("Load the model into the project") # These names are part of the model and cannot be changed. output_layer = 'loss:0' input_node = 'Placeholder:0' graph_def = tf.GraphDef() labels = [] labels_filename = "labels.txt" filename = "model.pb" # Import the TF graph with tf.gfile.FastGFile(filename, 'rb') as f: graph_def.ParseFromString(f.read()) tf.import_graph_def(graph_def, name='') # Create a list of labels with open(labels_filename, 'rt') as lf: for l in lf: labels.append(l.strip()) print ("Model loaded into the project") results_dic = dict() # create the JSON to be sent as a message json_message = '' # Iterate through images print ("List of images to analyse:") for file in os.listdir('images'): print(file) image = Image.open("images/" + file) # Update orientation based on EXIF tags, if the file has orientation info. image = update_orientation(image) # Convert to OpenCV format image = convert_to_opencv(image) # If the image has either w or h greater than 1600 we resize it down respecting # aspect ratio such that the largest dimension is 1600 image = resize_down_to_1600_max_dim(image) # We next get the largest center square h, w = image.shape[:2] min_dim = min(w,h) max_square_image = crop_center(image, min_dim, min_dim) # Resize that square down to 256x256 augmented_image = resize_to_256_square(max_square_image) # The compact models have a network size of 227x227, the model requires this size. network_input_size = 227 # Crop the center for the specified network_input_Size augmented_image = crop_center(augmented_image, network_input_size, network_input_size) try: with tf.Session() as sess: prob_tensor = sess.graph.get_tensor_by_name(output_layer) predictions, = sess.run(prob_tensor, {input_node: [augmented_image] }) except Exception as identifier: print ("Identifier error: ", identifier) print ("Print the highest probability label") highest_probability_index = np.argmax(predictions) print('FINAL RESULT! Classified as: ' + labels[highest_probability_index]) l = labels[highest_probability_index] results_dic[file] = l # Or you can print out all of the results mapping labels to probabilities. label_index = 0 for p in predictions: truncated_probablity = np.float64(round(p,8)) print (labels[label_index], truncated_probablity) label_index += 1 print("Results dictionary") print(results_dic) json_message = json.dumps(results_dic) print("Json result") print(json_message) # Initialize a new message message = IoTHubMessage(bytearray(json_message, 'utf8')) hubManager.send_event_to_output("output1", message, 0) messages_sent += 1 print("Message sent! - Total: " + str(messages_sent)) print('----------------------------') # This is the wait time before repeating the analysis # Currently set to 10 seconds time.sleep(10) class HubManager(object): def __init__( self, protocol=IoTHubTransportProvider.MQTT): self.client_protocol = protocol self.client = IoTHubModuleClient() self.client.create_from_environment(protocol) # set the time until a message times out self.client.set_option("messageTimeout", MESSAGE_TIMEOUT) # Forwards the message received onto the next stage in the process. def forward_event_to_output(self, outputQueueName, event, send_context): self.client.send_event_async( outputQueueName, event, send_confirmation_callback, send_context) def send_event_to_output(self, outputQueueName, event, send_context): self.client.send_event_async(outputQueueName, event, send_confirmation_callback, send_context) def main(protocol): try: hub_manager = HubManager(protocol) analyse(hub_manager) while True: time.sleep(1) except IoTHubError as iothub_error: print ( "Unexpected error %s from IoTHub" % iothub_error ) return except KeyboardInterrupt: print ( "IoTHubModuleClient sample stopped" ) if __name__ == '__main__': main(PROTOCOL)
打开名为 requirements.txt 的文件,将其内容替换为以下内容:
azure-iothub-device-client==1.4.0.0b3 opencv-python==3.3.1.11 tensorflow==1.8.0 pillow==5.1.0
打开名为 deployment.template.json 的文件,并按照以下准则替换其内容:
由于你将拥有自己的唯一 JSON 结构,因此需要手动编辑它(而不是复制示例)。 为了方便操作,请使用下图作为指南。
看起来不同于你的内容但不应更改的区域以黄色突出显示。
需要删除的部分以红色突出显示。
请小心删除正确的括号以及逗号。
已完成的 JSON 应如下图所示(虽有唯一不同之处:用户名/密码/模块名称/模块引用):
打开名为 Dockerfile.amd64 的文件,将其内容替换为以下内容:
FROM ubuntu:xenial WORKDIR /app RUN apt-get update && \ apt-get install -y --no-install-recommends libcurl4-openssl-dev python-pip libboost-python-dev && \ rm -rf /var/lib/apt/lists/* RUN pip install --upgrade pip RUN pip install setuptools COPY requirements.txt ./ RUN pip install -r requirements.txt RUN pip install pillow RUN pip install numpy RUN apt-get update && apt-get install -y \ pkg-config \ python-dev \ python-opencv \ libopencv-dev \ libav-tools \ libjpeg-dev \ libpng-dev \ libtiff-dev \ libjasper-dev \ python-numpy \ python-pycurl \ python-opencv RUN pip install opencv-python RUN pip install tensorflow RUN pip install --upgrade tensorflow COPY . . RUN useradd -ms /bin/bash moduleuser USER moduleuser CMD [ "python", "-u", "./main.py" ]
右键单击模块下的文件夹(它将具有之前提供的名称;在接下来的示例中,它称为 pythonmodule),然后单击“新建文件夹”。 将文件夹命名为“images”。
在此文件夹中,添加一些包含鼠标或键盘的映像。 这些是 Tensorflow 模型将会分析的映像。
警告
如果使用自己的模型,则需要更改此模型以反映自己的模型数据。
现在需要从模型文件夹中检索之前在第 1 章中下载(或从自己的自定义视觉服务创建)的 labels.txt 和 model.pb 文件。 获得文件后,将它们与其他文件一起放在解决方案中。 最终结果的外观应类似于下图:
第 9 章 - 将解决方案打包为容器
现在可以将文件“打包”为容器,并将容器推送到 Azure 容器注册表。 在 VS Code 中,打开集成终端(“查看”>“集成终端”或Ctrl + ),然后使用以下行登录到 Docker(将命令值替换为 Azure 容器注册表 (ACR) 的凭据)`:
docker login -u <ACR username> -p <ACR password> <ACR login server>
右键单击文件 deployment.template.json,然后单击“生成 IoT Edge 解决方案”。 此生成过程需要很长时间(具体取决于设备),因此请准备好等待。 生成过程完成后,将在名为 config 的新文件夹中创建 deployment.json 文件。
再次打开“命令面板”,并搜索“Azure: 登录”。 按照提示使用 Azure 帐户凭据;VS Code 将提供“复制并打开”选项,该选项将复制即将需要的设备代码并打开默认 Web 浏览器。 在系统要求时,粘贴设备代码以对计算机进行身份验证。
登录后,会发现“浏览”面板底部出现一个名为“Azure IoT 中心设备”的新的部分。 单击此部分将其展开。
如果设备不在此处,则需要右键单击“Azure IoT 中心设备”,然后单击“设置 IoT 中心连接字符串”。 然后,会看到命令面板(位于 VS Code 顶部)提示输入连接字符串。 这是在第 3 章末尾记下的连接字符串。 将字符串复制进去后,按 Enter 键。
此时应加载并显示设备。 右键单击设备名称,然后单击“为单个设备创建部署”。
将获得一条文件资源管理器提示,可在其中导航到 config 文件夹,然后选择 deployment.json 文件。 选择该文件后,单击“选择 Edge 部署清单”按钮。
此时已为 IoT 中心服务提供了清单,以便其将容器作为模块从 Azure 容器注册表有效部署到设备。
若要查看从设备发送到 IoT 中心的消息,请在“资源管理器”面板的“Azure IoT 中心设备”部分中再次右键单击设备名称,然后单击“开始监视 D2C 消息”。 从设备发送的消息应显示在 VS 终端中。 请耐心等待,因为这可能需要一些时间。 请参阅下一章进行调试,并检查部署是否成功。
此模块现在将循环访问 images 文件夹中的图像,并分析每一次迭代的图像。 显然,这只演示了如何在 IoT Edge 设备环境中使用基本机器学习模型。
若要扩展此示例的功能,可以通过多种方法继续操作。 一种方法可以是在容器中包含一些代码,这些代码从连接到设备的网络摄像头捕获照片,并将图像保存在 images 文件夹中。
另一种方法可以是将图像从 IoT 设备复制到容器中。 为此,一种实用的方法是在 IoT 设备终端中运行以下命令(如果希望自动执行此过程,一个小应用可能可以完成此作业)。 可以通过从存储文件的文件夹位置手动运行此命令来测试此命令:
sudo docker cp <filename> <modulename>:/app/images/<a name of your choice>
第 10 章 - 调试 IoT Edge 运行时
下面是命令行和提示列表,可帮助监视和调试 Ubuntu 设备中 IoT Edge 运行时的消息传送活动。
运行以下命令行,检查 IoT Edge 运行时状态:
sudo systemctl status iotedge
注意
请记得按 Ctrl + C 以完成状态查看。
列出当前部署的容器。 如果 IoT 中心服务已成功部署容器,则运行以下命令行将显示这些容器:
sudo iotedge list
或
sudo docker ps
注意
以上是检查是否已成功部署模块的好办法,因为模块会显示在列表中;否则只能看到 edgeHub 和 edgeAgent。
若要显示容器的代码日志,请运行以下命令行:
journalctl -u iotedge
用于管理 IoT Edge 运行时的有用命令:
若要删除主机中的所有容器:
sudo docker rm -f $(sudo docker ps -aq)
若要停止 IoT Edge 运行时:
sudo systemctl stop iotedge
第 11 章 - 创建表服务
导航回 Azure 门户,将在其中创建存储资源来创建 Azure 表服务。
如果尚未登录,请登录到 Azure 门户。
登录后,单击左上角的“创建资源”,搜索“存储帐户”,然后按 Enter 键开始搜索。
显示搜索结果后,单击列表中的“存储帐户 - Blob、文件、表、队列”。
新页面将提供“存储帐户”服务的说明。 在该提示的左下角,单击“创建”按钮,创建此服务的实例。
单击“创建”后,随即显示一个面板:
插入此服务实例的所需名称(必须全部小写)。
对于“部署模型”,请单击“资源管理器”。
对于“帐户类型”,请使用下拉菜单并单击“存储(常规用途 v1)”。
单击相应的位置。
对于“复制”下拉菜单,请单击“读取访问异地冗余存储(RA-GRS)”。
对于“性能”,请单击“标准”。
在“需要安全传输”部分中,单击“禁用”。
在“订阅”下拉菜单中,单击相应的订阅。
选择一个资源组或创建一个新资源组。 资源组提供一种监视、控制访问、预配和管理 Azure 资产集合计费的方法。 建议保留与常用资源组下的单个项目(例如这些课程)关联的所有 Azure 服务。
若要详细了解 Azure 资源组,请单击此链接了解如何管理资源组。
将“虚拟网络”保留为“禁用”状态(如果有此选项)。
单击 “创建” 。
单击“创建”后,必须等待服务创建,这可能需要一分钟时间。
创建服务实例后,门户中将显示一条通知。 单击通知以浏览新的服务实例。
单击通知中的“转到资源”按钮,将转到新的存储服务实例概述页。
在概述页中,单击右侧的“表”。
右侧面板将更改为显示“表服务”信息,其中需要添加新表。 为此,单击左上角的“+ 表”按钮。
随即显示一个新页面,其中需要输入表名称。 这是稍后几章(创建函数应用和 Power BI)中用于引用应用程序数据的名称。 插入 IoTMessages 作为名称(可以选择自己的名称,只需记住它以便在本文档稍后使用),然后单击“确定”。
创建新表后,可以在“表服务”页(底部)看到此表。
现在单击“访问密钥”,并复制存储帐户名称和密钥(使用记事本)。本课程稍后创建 Azure 函数应用时将使用这些值。
再次使用左侧面板,滚动到“表服务”部分,单击“表”(或更新门户中的“浏览表”),然后复制表 URL(使用记事本)。 本课程稍后将表链接到 Power BI 应用程序时,将使用此值。
第 12 章 - 完成 Azure 表
设置表服务存储帐户后,可以向其添加数据,之后将用于存储和检索信息。 可以通过 Visual Studio 来编辑表。
打开 Visual Studio(非 Visual Studio Code)。
从菜单中,单击“查看”>“Cloud Explorer”。
“Cloud Explorer”将以停靠项方式打开(请耐心等待,加载可能需要一些时间)。
警告
如果用于创建存储帐户的订阅不可见,请确保:
已登录的帐户与用于 Azure 门户的帐户相同。
从“帐户管理”页选择了订阅(可能需要在帐户设置中应用筛选器):
随即将显示 Azure 云服务。 查找存储帐户,并单击其左侧的箭头以展开帐户。
展开后,新创建的存储帐户应可用。 单击存储左侧的箭头,然后在展开后,查找“表”并单击表旁边的箭头,以显示在上一章中创建的表。 双击表。
表将在 Visual Studio 窗口的中央位置打开。 单击带有 +(加号)的表图标。
随即显示一个窗口,提示“添加实体”。 只会创建一个实体,但它具有三个属性。 会注意到已提供“PartitionKey”和“RowKey”,因为表使用这些属性来查找数据。
请更新以下值:
名称:PartitionKey,值:PK_IoTMessages
名称:RowKey,值:RK_1_IoTMessages
然后,单击“添加属性”(“添加实体”窗口的左下角)并添加以下属性:
- 作为字符串的 MessageContent,值保留为空。
表应与下图中的表匹配:
注意
在行键中,实体的编号为 1,是因为你可能需要添加更多消息,希望在本课程中进行进一步试验。
完成后,单击确定。 表现已准备就绪,可供使用。
第 13 章 - 创建 Azure 函数应用
现在创建 Azure 函数应用,IoT 中心服务将调用该应用将 IoT Edge 设备消息存储在上一章中创建的表服务中。
首先,需要创建一个文件,该文件允许 Azure 函数加载所需的库。
打开“记事本”(按 Windows 键,然后键入“记事本”)。
打开“记事本”后,将下面的 JSON 结构插入其中。 完成此操作后,将其以 project.json 形式保存在桌面上。 此文件定义函数将使用的库。 如果使用过 NuGet,你会非常熟悉此文件。
警告
命名务必正确,确保它没有 .txt 文件扩展名。 请参阅下图以供参考:
{ "frameworks": { "net46":{ "dependencies": { "WindowsAzure.Storage": "9.2.0" } } } }
登录到 Azure 门户。
登录后,单击左上角的“创建资源”,搜索“函数应用”,然后按 Enter 键进行搜索。 在结果中单击“函数应用”,以打开新面板。
新面板将提供“函数应用”服务的说明。 在此面板的左下角,单击“创建”按钮,以创建与此服务的关联。
单击“创建”后,请填写以下内容:
对于“应用名称”,请插入此服务实例的所需名称。
选择一个“订阅” 。
选择适合的定价层,如果这是第一次创建函数应用服务,则应可使用免费层。
选择一个资源组或创建一个新资源组。 资源组提供一种监视、控制访问、预配和管理 Azure 资产集合计费的方法。 建议保留与常用资源组下的单个项目(例如这些课程)关联的所有 Azure 服务。
若要详细了解 Azure 资源组,请单击此链接了解如何管理资源组。
对于“OS”,请单击“Windows”,因为这是目标平台。
选择一种托管计划(本教程使用“消耗计划”)。
选择一个位置(选择与在上一步中生成的存储相同的位置)
对于“存储”部分,必须选择在上一步中创建的存储服务。
此应用中无需 Application Insights,因此可根据需要将其保留为“关闭”状态。
单击 “创建” 。
单击“创建”后,必须等待服务创建,这可能需要一分钟时间。
创建服务实例后,门户中将显示一条通知。
部署成功(已完成)后,单击此通知。
单击通知中的“转到资源”按钮,浏览新的服务实例。
在新面板的左侧,单击“函数”旁边的 +(加号)图标以创建新函数。
在中央面板中,将显示“函数”创建窗口。 再向下滚动,然后单击“自定义函数”。
在下一页中向下滚动,直到找到“IoT 中心(事件中心)”,然后单击它。
在“IoT 中心(事件中心)”边栏选项卡中,将“语言”设置为“C#”,然后单击“新建”。
在出现的窗口中,确保已选中“IoT 中心”,并且“IoT 中心”字段的名称对应于之前(第 3 章,步骤 8)创建的“IoT 中心服务”的名称。 然后单击“选择”按钮。
返回到“IoT 中心(事件中心)”边栏选项卡,单击“创建”。
将会重定向到函数编辑器。
删除其中的所有代码,并将其替换为以下代码:
#r "Microsoft.WindowsAzure.Storage" #r "NewtonSoft.Json" using System; using Microsoft.WindowsAzure.Storage; using Microsoft.WindowsAzure.Storage.Table; using Newtonsoft.Json; using System.Threading.Tasks; public static async Task Run(string myIoTHubMessage, TraceWriter log) { log.Info($"C# IoT Hub trigger function processed a message: {myIoTHubMessage}"); //RowKey of the table object to be changed string tableName = "IoTMessages"; string tableURL = "https://iothubmrstorage.table.core.windows.net/IoTMessages"; // If you did not name your Storage Service as suggested in the course, change the name here with the one you chose. string storageAccountName = "iotedgestor"; string storageAccountKey = "<Insert your Storage Key here>"; string partitionKey = "PK_IoTMessages"; string rowKey = "RK_1_IoTMessages"; Microsoft.WindowsAzure.Storage.Auth.StorageCredentials storageCredentials = new Microsoft.WindowsAzure.Storage.Auth.StorageCredentials(storageAccountName, storageAccountKey); CloudStorageAccount storageAccount = new CloudStorageAccount(storageCredentials, true); // Create the table client. CloudTableClient tableClient = storageAccount.CreateCloudTableClient(); // Get a reference to a table named "IoTMessages" CloudTable messageTable = tableClient.GetTableReference(tableName); //Retrieve the table object by its RowKey TableOperation operation = TableOperation.Retrieve<MessageEntity>(partitionKey, rowKey); TableResult result = await messageTable.ExecuteAsync(operation); //Create a MessageEntity so to set its parameters MessageEntity messageEntity = (MessageEntity)result.Result; messageEntity.MessageContent = myIoTHubMessage; messageEntity.PartitionKey = partitionKey; messageEntity.RowKey = rowKey; //Replace the table appropriate table Entity with the value of the MessageEntity Ccass structure. operation = TableOperation.Replace(messageEntity); // Execute the insert operation. await messageTable.ExecuteAsync(operation); } // This MessageEntity structure which will represent a Table Entity public class MessageEntity : TableEntity { public string Type { get; set; } public string MessageContent { get; set; } }
更改以下变量,以便它们对应于存储帐户中找到的相应值(分别是第 11 章步骤 11 和 13 中的“表”和“存储”值):
- tableName,替换为位于存储帐户的表的名称。
- tableURL,替换为位于存储帐户的表的 URL。
- storageAccountName,替换为与存储帐户名称的名称对应的值的名称。
- storageAccountKey,替换为在之前创建的存储服务中获取的密钥。
代码准备就绪后,单击“保存”。
接下来,单击页面右侧的 <(箭头)图标。
随即一个面板从右侧滑入。 在该面板中,单击“上传”,随即显示“文件浏览器”。
导航到之前在记事本中创建的 project.json 文件并单击该文件,然后单击“打开”按钮。 此文件定义函数将使用的库。
文件上传后,它将显示在右侧的面板中。 单击它会在“函数”编辑器中打开它。 它必须与下图完全相同。
此时,最好测试函数将消息存储在表中的功能。 在窗口的右上角,单击“测试”。
在请求正文中插入一条消息(如上图所示),然后单击“运行”。
函数将运行并显示结果状态(会发现“输出”窗口上方显示绿色“状态 202 已接受”,意味着调用已成功):
第 14 章 - 查看活动消息
如果现在打开 Visual Studio(非 Visual Studio Code),可以可视化测试消息结果,因为它将存储在“MessageContent”字符串区域中。
在表服务和函数应用准备就绪后,Ubuntu 设备消息将显示在“IoTMessages”表中。 如果尚未运行,则重新启动设备,这样便能通过使用 Visual Studio Cloud Explorer 在表中看到来自设备和模块的结果消息。
第 15 章 - 设置 Power BI
若要可视化 IOT 设备的数据,需要设置 Power BI(桌面版本),以便从刚创建的表服务中收集数据。 然后,Power BI 的 HoloLens 版本将使用该数据来可视化结果。
打开 Windows 10 上的 Microsoft Store 并搜索“Power BI Desktop”。
下载此应用程序。 下载完成后,将其打开。
使用 Microsoft 365 帐户登录 Power BI。 可能会重定向到浏览器进行注册。 注册后,返回到 Power BI 应用,然后再次登录。
单击“获取数据”,然后单击“更多...”。
依次单击“Azure”、“Azure 表存储”,然后单击“连接”。
系统将提示插入先前创建表服务时收集的“表 URL”(第 11 章,步骤 13)。 插入 URL 后,删除路径中引用表“子文件夹”的部分(在本课程中为 IoTMessages)。 最终结果应如下图所示。 然后单击“确定”。
系统将提示插入先前创建表存储时记下的“存储密钥”(第 11 章,步骤 11)。 然后单击“连接”。
随即将显示“导航器”面板,勾选表旁边的框,然后单击“加载”。
Power BI 中现已加载此表,但需要查询来显示表中的值。 为此,请右键单击屏幕右侧“字段”面板中的表名称。 然后单击“编辑查询”。
“Power Query 编辑器”将作为新窗口打开,并显示表。 单击表的“内容”列中的字词“记录”,以可视化存储的内容。
单击窗口左上角的“信息表”。
单击“关闭并应用”。
查询加载完成后,在屏幕右侧的“字段”面板中,勾选参数名称和值对应的框,以可视化“MessageContent”列内容。
单击窗口左上角的蓝色磁盘图标,将工作保存到所选的文件夹中。
现在可以单击“发布”按钮将表上传到工作区。 出现提示时,单击“我的工作区”,然后单击“选择”。 等待显示成功提交的结果。
警告
下一章内容特定于 HoloLens。 Power BI 目前不能作为沉浸式应用程序使用,但可以通过桌面应用在 Windows Mixed Reality 门户中运行桌面版本(也称为“悬崖小屋”)。
第 16 章 - 在 HoloLens 上显示 Power BI 数据
在 HoloLens 上,通过在应用程序列表中点击图标登录到“Microsoft Store”。
搜索并下载“Power BI”应用程序。
从应用程序列表启动“Power BI”。
Power BI 可能会要求登录到 Microsoft 365 帐户。
进入应用后,默认情况下工作区应会显示,如下图所示。 如果未显示,只需单击窗口左侧的工作区图标。
你已完成 IoT 中心应用程序
恭喜,你已成功创建 IoT 中心服务以及模拟虚拟机 Edge 设备。 通过 Azure 函数应用,设备可以将机器学习模型的结果传达给 Azure 表服务,随后将结果读入 Power BI 并在 Microsoft HoloLens 内进行可视化。
额外练习
练习 1
扩展表中存储的消息结构并将其显示为图形。 可能要收集更多数据,并将其存储在同一个表中,以便以后显示。
练习 2
额外创建一个“摄像头捕获”模块以部署在 IoT 开发板上,从而通过摄像头捕获要分析的图像。