本文介绍通过阿里云表格存储 TableStore、对象存储 OSS、DataWorks 数据集成和 Serverless 函数等云服务对物联网平台接收的 IoT 设备消息进行持久化存储和处理的过程,探讨如何通过云服务快速地构建一个 IoT 应用的原型。
本文继承专辑上一篇文章 ESP32 设备连接阿里云 IoT 平台 ,继续探讨云服务在快速构建 IoT 方案上的能力应用,以及云原生与 IoT 方案的结合。
我们在之前已经通过编写简单的 C 程序实现了 ESP32 与阿里云 IoT 平台的连接,并向平台发送 MQTT 消息。本文将继续介绍阿里云 IoT 平台的规则引擎,通过规则将 MQTT 消息持久化到阿里云表格存储和对象存储,并通过函数服务对数据集进行简单的处理。
设备消息流转到表格存储
在上一篇文章末尾,我们在阿里云平台上接收到了如下的设备消息。这里面包含了在 ESP32 设备代码里构造的温度和湿度上报数据。
接下来将要通过物联网平台的规则引擎,将设备消息流转到我们希望持久化或者过程处理的云服务中去。阿里云物联网平台规则引擎支持数据流转到新的 Topic、表格存储、DataHub、消息服务 MNS、RDS 数据库、消息队列 RocketMQ、函数服务 FC 等云服务。
我们在本文中将进行流转到 表格存储 和 函数服务 的实践过程。
首先在物联网平台创建 云产品流转规则,这里我们采用 JSON 数据格式。
一个设备通常发送多个 Topic 进行不同类型数据上报和订阅等,故在云产品流转的规则中我们可以通过 SQL 语句选择我们需要的设备信息进行流转处理。
这里选择在之前我们所自定义的 Topic,对上报的温度和湿度数据进行处理。
SQL 编写完成后,我们要将数据流转到表格存储 TableStore,故先跳转到表格存储云服务,创建相应的表格存储实例和表格。这里我们指定表主键为 Device_ID 和 Timestamp。
回到物联网平台云服务,在 转发数据 处,添加操作,选择 存储到表格存储 和 刚刚创建的表格存储实例和数据表。主键这里 Device_ID 我填写固定的字符串,Timestamp 主键我采用了云服务提供的内置函数 timestamp() ,该函数将为我的数据集自动添加上服务端的时间戳。
规则创建完成后,默认未开启。启用该规则后,设备上报的数据将不断地流转到表格存储。开启一段时间后,我们便可以在表格存储数据管理查看得持久化的设备信息。
到这里,便完成了通过云服务处理 IoT 数据的第一步。
通过 DataWorks 数据集成将 TableStore 转换为 CSV 存储到 OSS
数据流转到表格存储后,我们当然可以通过编写程序对表格数据进行处理和消费。不过,这一小节将通过阿里云 DataWorks 数据集成 里的数据同步功能,将表格存储里的数据进行数据转换为 CSV 格式数据,并存储到对象存储 OSS。
为什么要转换为 CSV 格式存储呢? 因为 CSV 格式数据是在 Data Science 比较常见的数据格式。
(多说一句,阿里云表格存储文档中关于通过数据集成转换格式的描述中,似乎因为 DataWorks 产品更新得比较快,有些描述比较老旧和简单了,花费不少时间摸索。)
进入 DataWorks 数据集成,新建数据集成离线任务同步。
新建节点,采用公共服务节点即可。
DataWorks 要求提前定义好离线同步的数据源(包括数据来源存储和目标源)。这里我们新建 OTS (TableStore) 数据源为之前创建的表格存储实例。
OSS 目的数据源,需要我们在 OSS 提前创建好相应的 Bucket,并在此定义该数据源。
定义好数据源后,返回数据集成同步任务页面,通过向导可以帮助生成一个 表格存储 到 OSS 存储的 Job 定义。
生成的 Job 定义仍然需要我们根据表格存储的键值进行对应的调整,将我们需要处理的 column 填入,以及表格存储实例的具体数据表名称等。
完整的 Job 定义如下:
{ "type": "job", "steps": [ { "stepType": "ots", "parameter": { "datasource": "esptable33", "column": [ { "name": "Device_ID" }, { "name": "Timestamp" }, { "name": "humid" }, { "name": "temp" }, { "name": "uptime" } ], "range": { "split": [], "end": [ { "type": "INF_MAX" }, { "type": "INF_MAX" } ], "begin": [ { "type": "INF_MIN" }, { "type": "INF_MIN" } ] }, "table": "esptable33" }, "name": "Reader", "category": "reader" }, { "stepType": "oss", "parameter": { "fieldDelimiterOrigin": ",", "nullFormat": "null", "dateFormat": "yyyy-MM-dd HH:mm:ss", "datasource": "espiothong", "writeMode": "truncate", "encoding": "UTF-8", "fieldDelimiter": ",", "fileFormat": "csv", "object": "esp32source/hongESP32" }, "name": "Writer", "category": "writer" } ], "version": "2.0", "order": { "hops": [ { "from": "Reader", "to": "Writer" } ] }, "setting": { "errorLimit": { "record": "" }, "speed": { "throttle": false, "concurrent": 2 } }}
定义完成后,点击运行即可。如运行成功,将返回如下日志。
运行完成后,返回对象存储云服务页面,可以看到对应的 bucket 里已经生成了相应的数据。
下载该数据,可以查看得如下的 CSV 格式设备数据。
hongESP32,1597554515303,95.000000,71.000000,1283hongESP32,1597554525225,90.000000,61.000000,1293hongESP32,1597554535238,85.000000,70.000000,1303hongESP32,1597554545234,95.000000,74.000000,1313hongESP32,1597554555305,80.000000,69.000000,1323hongESP32,1597554565237,92.000000,74.000000,1333hongESP32,1597554575243,84.000000,75.000000,1343hongESP32,1597554585244,85.000000,63.000000,1353hongESP32,1597554595247,89.000000,68.000000,1363hongESP32,1597554605251,83.000000,66.000000,1373hongESP32,1597554615254,83.000000,72.000000,1383hongESP32,1597554625252,87.000000,60.000000,1393hongESP32,1597554635263,87.000000,70.000000,1403
上面的数据我们发现,数据的 Headers 如 Device_ID, Timestampe, Humidity, Temperature, Uptime 没有了。
我对 DataWorks 不熟,不确定是否能通过调整上述的 Job 定义保留我们的 Column Headers。不过我想到了可以通过函数服务把这些信息加入到我们的 CSV 数据中。
通过函数服务对 CSV 格式数据进行简单处理
函数服务在云服务的中发挥越来越重要的作用,有点各类云服务 “粘合剂” 的意思。这一小节函数服务的任务只是为上述的 CSV 格式数据加入 Column Headers,本文下一小节以及未来的专辑文章将尝试进行更多的函数服务与 IoT 服务的结合实践。
新建普通事件函数,采用 Python 运行环境:
这个函数只需要加入 Device_ID, Timestampe, Humidity, Temperature, Uptime ,比较简单。因为我们希望这个函数是通过 OSS bucket 触发,所以函数体里会通过 OSS 传入的 context 确定 OSS 的 bucket,然后对 bucket 里的 CSV 文件进行处理。
以下为完整代码:
# -*- coding: utf-8 -*-import oss2, json, csv, os# Code Origin by Krishong Yedef handler(event, context): evt = json.loads(event) creds = context.credentials # Required by OSS sdk auth=oss2.StsAuth( creds.access_key_id, creds.access_key_secret, creds.security_token) evt = evt['events'][0] bucket_name = evt['oss']['bucket']['name'] endpoint = 'oss-' + evt['region'] + '.aliyuncs.com' bucket = oss2.Bucket(auth, endpoint, bucket_name) objectName = evt['oss']['object']['key'] objectTransformedName = objectName.replace("esp32source/", "esp32processed/") tmpdir = '/tmp/download/' os.system("rm -rf /tmp/*") os.mkdir(tmpdir) bucket.get_object_to_file(objectName, tmpdir + 'osstmpfile') datas = [['Device_ID', 'Time', 'Humidity', 'Temperature', 'Uptime']] with open(tmpdir + 'osstmpfile', newline='') as f: reader = csv.reader(f) for row in reader: datas.append(row) print(datas) with open(tmpdir + 'osstmpfile', 'w', newline='') as f: writer = csv.writer(f, quotechar='|', quoting=csv.QUOTE_MINIMAL) # writer.writerows(['Device_ID', 'Time', 'Humidity', 'Temperature']) writer.writerows(datas) bucket.put_object_from_file(objectTransformedName, tmpdir + 'osstmpfile')
为这个函数创建 对象存储触发器。触发规则这里我们需要注意定义,在之前的数据流转中,可以为不同的产品设备数据创建不同目录或前缀,那么此时便可以通过前缀来定义触发器规则。
函数服务创建完成后,此时只要 bucket 里以 esp32source/ 为前缀的文件对象创建,就可以触发函数对这个文件对象进行处理。
我们重新触发一次 DataWorks 处理,DataWorks 处理后在 esp32source/ 创建了新的文件,后函数服务处理后数据保存为 esp32processed/ 前缀。
下载处理后的 CSV 格式文件,我们可以查看得数据已经加入了我们需要的 Headers。
Device_ID,Time,Humidity,Temperature,UptimehongESP32,1597554515303,95.000000,71.000000,1283hongESP32,1597554525225,90.000000,61.000000,1293hongESP32,1597554535238,85.000000,70.000000,1303hongESP32,1597554545234,95.000000,74.000000,1313hongESP32,1597554555305,80.000000,69.000000,1323hongESP32,1597554565237,92.000000,74.000000,1333hongESP32,1597554575243,84.000000,75.000000,1343hongESP32,1597554585244,85.000000,63.000000,1353hongESP32,1597554595247,89.000000,68.000000,1363hongESP32,1597554605251,83.000000,66.000000,1373hongESP32,1597554615254,83.000000,72.000000,1383hongESP32,1597554625252,87.000000,60.000000,1393hongESP32,1597554635263,87.000000,70.000000,1403
这个函数处理的内容比较简单,但似乎事情开始变得有趣起来了。
IoT 数据直接通过函数服务转换为 CSV 并流转到 OSS
我们通过上面几个小节将 IoT 数据流转到 表格存储、DataWorks、对象存储 和 函数服务。过程似乎有点长。既然 IoT 平台规则引擎支持 函数服务,那我们便可以尝试直接通过函数服务进行 CSV 格式转换并存储到对象存储 OSS。
新建普通事件函数,采用 Python 运行环境。
IoT 平台规则引擎触发函数服务,会将 MQTT 消息传入 event,我们将在函数体中通过 event 中获取温度和湿度的信息,并将信息以 CSV 格式写入对象存储文件。
以下为完整函数代码:
# -*- coding: utf-8 -*-import logging, json, csv, os, oss2, time# Code Origin By Krishong Yedef handler(event, context): logger = logging.getLogger() logger.info(event) auth = oss2.Auth('AccessKeyId', 'AccessKeySecret') endpoint = 'https://oss-cn-shanghai.aliyuncs.com' bucketName = 'espiothong' objectName = 'esp32direct/hongESP32_direct_data.csv' bucket = oss2.Bucket(auth, endpoint, bucketName) tmpdir = '/tmp/download/' os.system("rm -rf /tmp/*") os.mkdir(tmpdir) evt = json.loads(event) temperature = evt['temp'] humidity = evt['humid'] uptime = evt['uptime'] try: remote_stream = bucket.get_object(objectName) except: bucket.put_object(objectName, '') # headers = [['Device_ID', 'Time', 'Humidity', 'Temperature']] iot_data = [] the_row = [] column_device_id = 'hongESP32' column_time = str(time.strftime("%Y-%m-%d %H:%M:%S", time.gmtime())) the_row.append(column_device_id) the_row.append(column_time) the_row.append(temperature) the_row.append(humidity) the_row.append(uptime) bucket.get_object_to_file(objectName, tmpdir + 'osstmpfile') with open(tmpdir + 'osstmpfile', newline='') as f: reader = csv.reader(f) for row in reader: iot_data.append(row) with open(tmpdir + 'osstmpfile', 'w', newline='') as f: writer = csv.writer(f, quotechar='|', quoting=csv.QUOTE_MINIMAL) iot_data.append(the_row) writer.writerows(iot_data) bucket.put_object_from_file(objectName, tmpdir + 'osstmpfile') return the_row
函数创建完成后,在物联网平台云服务规则引擎处,新建一条操作规则,发送到函数计算,并选择刚刚创建的函数。
这时只要有新的设备消息进入物联网平台,每一条消息都将被转发到函数服务进行处理,并通过函数服务存储到 OSS。
以下为直接通过函数服务存储到 OSS 的 IoT 设备数据。
hongESP32,2020-08-16 06:12:35,63.0,80.0,140hongESP32,2020-08-16 06:12:44,59.0,92.0,150hongESP32,2020-08-16 06:12:54,57.0,85.0,160hongESP32,2020-08-16 06:13:04,60.0,81.0,170hongESP32,2020-08-16 06:13:14,74.0,77.0,180hongESP32,2020-08-16 06:13:24,73.0,86.0,190hongESP32,2020-08-16 06:13:34,63.0,87.0,200hongESP32,2020-08-16 06:13:44,56.0,91.0,210hongESP32,2020-08-16 06:13:54,65.0,78.0,220hongESP32,2020-08-16 06:14:04,67.0,78.0,230hongESP32,2020-08-16 06:14:14,58.0,89.0,240
这里的 CSV 格式仍然没有 Headers,不过因为是通过函数处理,只要加入几行代码,也可以直接输出符合要求的格式数据,这里就不演示了。
另外,稍微讨论一下的是,这种直接通过函数处理的方法,每一条 MQTT 消息都会触发一次函数服务,函数服务的触发次数和成本都是相对之前的方式要多的。
上述的处理过程在 AWS 上的类似的实践可以参考 利用 AWS 云服务进行 IoT 设备数据的响应、存储和处理 ,有兴趣的同学可以自行对比。
文章的尾巴,数据的处理
通过上述几个小节的过程,我们已经拿到了简洁清楚的 CSV 格式数据,接下来就是对这个数据的处理和分析了。
数据处理和分析有非常多的手段、工具和平台。本文先简单通过 Python Pandas 进行简单的处理。后续文章将探索其他有趣的处理方案。
pandas 代码如下:
import numpy as npimport pandas as pdimport matplotlib.pyplot as pltdf = pd.read_csv('hongESP32__ebf7668eca7c462d8f09103aa618565c')cleaned_df = df.drop('Uptime', axis=1).set_index('Time')plt.figure()cleaned_df.plot()
处理的结果:
我们通过 pandas 和 matplot 快速简单地呈现出了 Line Chart 图表。
文章到这里就结束了。下一篇文章将继续探索通过函数服务、API 网关 等进行更多的 IoT 数据处理和可视化呈现的实践内容。
过去写的一些相关联的专辑
01
边缘计算和 AWS IoT
02
边缘计算和 Azure IoT03
开源 IoT 操作系统和平台