未验证 提交 5cae6e2c 编写于 作者: E ethanlcz 提交者: GitHub

haaseduk1 examples (#1799)

* modify HaaSPython's brief introduction, fix #1772
Signed-off-by: Nethan.lcz <ethan.lcz@alibaba-inc.com>

* add HaaS Python v2.2.0 release notest, fix #1726
Signed-off-by: Nethan.lcz <ethan.lcz@alibaba-inc.com>

* add README for HaaS EDU K1 examples, fix #1798
Signed-off-by: Nethan.lcz <ethan.lcz@alibaba-inc.com>
上级 9f958d24
# 火焰检测系统
&emsp;&emsp;
下图是本案例除硬件连线外的3步导学,每个步骤中实现的功能请参考图中的说明。在硬件连线完成之后我们建议您先使用“一分钟上云体验”功能预先体验本案例的实际运行效果。
<div align="center">
<img src=./../../../images/4_fire_detector_步骤概述.jpg width=60%/>
</div>
## 1、简介
&emsp;&emsp;
自从第一次工业革命开始到现代,化石燃料一直是很重要的能源之一,而煤在化石燃料中占比较高。煤转化成能量需要燃烧,不管是第一次工业革命的内燃机还是现在仍然占比很高的火力发电,都需要大量的烧煤。现代的火力发电系统会持续对炉膛中煤的燃烧情况进行监控,防止出现燃煤熄灭的情况。
本场景就是针对这个场景对火焰进行实时的监控,如果出现火焰熄灭的情况,则进行灯光报警。
### 1.1、背景知识
&emsp;&emsp;
火力发电系统中所用的火焰监控的核心在于如何精准的检测炉膛中的火焰强度,目前市场上已经有很多的传感器可以对火焰强度进行测量。
目前市面上用的最多的火焰传感器,即红外接收二极管,对火焰特别敏感。它对火焰产生的红外线非常敏感,当火焰亮度越大时,发出的红外线越多,火焰传感器管脚间的阻抗变小;当火焰变小时,发出的红外线变少,火焰传感器管脚间的阻抗变大。
本节选用的火焰传感器外观如下:
<div align="center">
<img src=./../../../images/4_火焰传感器外观图.png width=40%/>
</div>
<br>
### 1.2、准备
&emsp;&emsp;
完成本案例需要如下硬件:
1. [HaaS EDU K1开发板](https://haas.iot.aliyun.com/solution/detail/hardware?versionId=800C5AB3B8A4A88800000001&dataId=800C5AB3B8A4A888) 一台
2. [火焰传感器](https://haas.iot.aliyun.com/solution/detail/hardware?versionId=800C65AE63D505D100000002&dataId=800C65AE63D505D1) 一个
3. 连接线若干
&emsp;&emsp;
硬件连线图如下图所示:
<div align="center">
<img src=./../../../images/4_HaaS_EDU_K1_火焰传感器连线图.png width=90%/>
</div>
<br>
## 2、物联网平台开发
&emsp;&emsp;
对于第一次使用物联网平台的读者,需要开通实例以使用物联网平台的功能。这里可以使用免费的公共实例进行开发。
&emsp;&emsp;
[物联网平台](https://iot.console.aliyun.com/lk/summary/new)中,左上角选择“华东2-上海”,点击“公共实例”,即可开通。
<div align="center">
<img src=./../../../images/5_3_开通公共实例.png
width=100%/>
</div>
&emsp;&emsp;
开通物联网平台功能之后,需要完成下面的3个步骤完成云端设备的创建:
1. 创建云端产品
2. 创建产品属性(物模型)
3. 创建云端设备(获取三元组)
<br>
### 2.1、创建云端产品
&emsp;&emsp;
点击上图中的“公共实例”,即可进入[控制台](https://iot.console.aliyun.com/lk/summary/new)进行产品创建。然后,创建云端产品的网址:https://iot.console.aliyun.com/product
&emsp;&emsp;
点击创建产品按钮,如下图所示。
<div align="center">
<img src=./../../../images/1_创建产品.png
width=100%/>
</div>
&emsp;&emsp;
在新建产品设定页面按照下图所示,设定“产品名称”,选择所属的“自定义品类”(自定义品类的物模型为空,需要自己创建,也可以通过导入外部物模型的方式导入),节点类型选择“直连设备”,联网方式选择“Wi-Fi”,数据格式选择“ICA标准数据格式”,检验类型和认证方式选择默认设定即可。还可以根据开发者自己的需求在“产品描述”页面添加针对此产品的描述。
<div align="center">
<img src=./../../../images/4_新建火焰检测设备.png
width=100%/>
</div>
&emsp;&emsp;
选择之后,点击“确认”按钮,即可完成产品创建。返回“产品”页面之后可以看到产品类表中会出现刚刚创建的“火焰检测系统”的产品,如下图所示。
<div align="center">
<img src=./../../../images/4_火焰检测系统_产品列表页.png width=100%/>
</div>
<br>
### 2.2、创建产品属性(物模型)
&emsp;&emsp;
点击上图中的“查看”按钮,即可看到产品信息,Topic列表,功能定义,数据解析等跟产品相关功能的设定。点开“功能定义”标签页,可以看到设备物模型定义。
<div align="center">
<img src=./../../../images/4_火焰检测系统_产品详情页面.png width=100%/>
</div>
&emsp;&emsp;
标识符是设备端上报设备属性状态的消息中需要使用的标识符,并且只有在设备上报的属性内容符合“数据定义”中的数据取值范围的时候才会被物联网平台记录,否则会被物联网平台认定为非法属性而过滤掉。
&emsp;&emsp;
本节我们选择导入物模型的方式来创建此系统需要的物模型信息,点击上图中的”编辑草稿“按钮。然后按照下图的步骤,选择本地文件[火焰检测系统物模型](./link_platform/fire_detector_model.zip)进行导入。
<div align="center">
<img src=./../../../images/4_火焰检测系统_发布物模型.png width=100%/>
</div>
&emsp;&emsp;
物模型导入成功后可以看到网页出现了我们刚刚导入的物模型属性。其中alarmState代表的是火焰的报警状态,1处于报警状态,0代表没有报警;fireVoltage代表火焰传感器检测到的电压值。
<div align="center">
<img src=./../../../images/4_火焰检测系统_物模型.png width=100%/>
</div>
&emsp;&emsp;
产品及其物模型创建完成后,就可以创建这个产品的设备了。
<br>
### 2.3、创建云端设备(获取三元组)
&emsp;&emsp;
在产品列表页面中,点击”火焰检测系统“后的“管理设备”,就会进到设备管理页面。
<div align="center">
<img src=./../../../images/4_火焰检测系统_产品页_管理设备.png width=100%/>
</div>
&emsp;&emsp;
在“设备”页面点击“添加设备”按钮,如下图所示。
<div align="center">
<img src=./../../../images/4_火焰传感器_添加设备入口.png width=100%/>
</div>
&emsp;&emsp;
在“添加设备”页面中设定“deviceName”,这里开发者可以自己填入自己想设定的设备名称,也可以不填任何内容让系统自动生成设备名称,如下图所示。
<div align="center">
<img src=./../../../images/1_添加设备.png width=40%/>
</div>
&emsp;&emsp;
设备添加完成后,点击“前往查看”按钮,就可以看到此设备端详细信息了。
<div align="center">
<img src=./../../../images/1_完成添加设备.png width=40%/>
</div>
&emsp;&emsp;
设备信息中有两个信息需要和设备端开发相匹配:
1. 三元组
2. 物模型属性信息
<div align="center">
<img src=./../../../images/4_火焰检测系统_设备详情.png width=100%/>
</div>
<br>
#### 2.4.1、**获取设备三元组**
&emsp;&emsp;
如上图所示,点击“查看”按钮,就可以看到设备的三元组信息,三元组是物联网设备端和物联网云端设备相关联的唯一标识符,在设备端连接云端的时候会使用三元组信息和云端进行鉴权,鉴权通过之后云端会认为设备已激活并上线。
<div align="center">
<img src=./../../../images/4_火焰检测系统_设备三元组.png width=50%/>
</div>
<br>
#### 2.4.2、**查看设备属性信息**
&emsp;&emsp;
设备详情信息页中的“物模型数据”标签页中可以看到设备的所有属性信息、设备时间上报情况及设备服务调用情况,如下图所示。待物联网设备按照设备属性对应的标识符上报设备属性的时候,本图片中的“火焰检测电压“,”报警状态“等属性值就会显示设备最新的属性信息。
<div align="center">
<img src=./../../../images/4_火焰检测系统_设备物模型数据.png width=100%/>
</div>
<br>
> 创建产品和设备的过程是按照面向对象的思想进行设计的,其中创建产品可以看成是新建一个类,其中的物模型则是类的对象,创建设备则是进行类的实例化。
<br>
## 3、设备端开发
### 3.1、开发环境
&emsp;&emsp;
在进行下一步之前请确保HaaS EDU K1开发环境已经搭建完毕。详情请参考[HaaS EDU K1 快速开始](../../../startup/HaaS_EDU_K1_startup.md)的说明。
<br>
### 3.2、创建解决方案
&emsp;&emsp;
如下图所示,在Haas Studio中创建项目。先选择左侧的“开发板型号”再从右侧的案例中选择“火焰检测系统”案例点击“立即创建”即可。
<div align="center">
<img src=./../../../images/HaaS_Studio_创建工程示范.png width=100%/>
</div>
<br>
1. **修改路由器名称及密码**
&emsp;&emsp;
修改工程里main.py中wifiSsid和wifiPassword的值为读者实际要连接的路由器的名称及密码(请注意名称和密码都需要放在""符号中间)。
```python
# Wi-Fi SSID和Password设置
wifiSsid = "请填写您的路由器名称"
wifiPassword = "请填写您的路由器密码"
```
&emsp;&emsp;
修改完成之后get_wifi_status函数中的wlan.connect(wifiSsid, wifiPassword) 语句就会连接读者自己设定的路由器。
2. **修改设备端三元组**
&emsp;&emsp;
修改fire_detector工程里main.py中productKey、deviceName和deviceSecret的值为读者创建的物联网设备的三元组信息。
```python
# 三元组信息
productKey = "产品密钥"
deviceName = "设备名称"
deviceSecret = "设备密钥"
```
3. **修改设备端上报数据所用标识符**
&emsp;&emsp;
fire_detector工程里main.py中下面的代码实现的是上传火焰检测结果和报警灯状态到云端的功能。其中fireVoltage便是火焰检测结果上报云端所用的标识符。
```python
# 无限循环
while True:
# 获取电压值
fireVoltage = fireDev.getVoltage()
print("The fire status Voltage ",fireVoltage)
# 生成上报到物联网平台的属性值字串,此处的属性标识符"fireVoltage"和"alarmState"必须和物联网平台的属性一致
# "fireVoltage" - 代表火焰传感器测量到的电压值
# "alarmState" - 代表报警灯的当前状态
upload_data = {'params': ujson.dumps({
'fireVoltage': fireVoltage,
'alarmState': alarm_on
})
}
# 上传火焰传感器测量结果和报警灯状态到物联网平台
device.postProps(upload_data)
# 每2秒钟上报一次
utime.sleep(2)
```
<br>
## 4、物联网应用开发
### 4.1、火焰亮度监控系统
&emsp;&emsp;
下图是一个典型的物联网应用程序开发的流程图,接下来本节就按照这个流程介绍如何完成火焰监控系统web端应用程序的开发。
<div align="center">
<img src=./../../../images/1_物联网应用开发的一般流程.png width=30%/>
</div>
<br>
### 4.2、**新建“普通项目”**
&emsp;&emsp;
打开[IoT Studio官网](https://studio.iot.aliyun.com/),在项目管理中创建一个空白项目,如下图所示,将此项目命名为“火焰监控报警系统”。
<div align="center">
<img src=./../../../images/4_火焰监控报警系统_创建IoTStudio项目.png width=80%/>
</div>
<br>
### 4.3、**新建“web应用”**
&emsp;&emsp;
新建“普通项目”之后,在新项目的首页新建一个web应用,命名为“火焰实时监控”。
<div align="center">
<img src=./../../../images/4_火焰监控系统_web应用创建.png width=80%/>
</div>
&emsp;&emsp;
web应用创建成功后会进入到应用界面设计页面。
<div align="center">
<img src=./../../../images/4_火焰监控系统_移动应用_页面编辑页.png width=80%/>
</div>
&emsp;&emsp;
点击上图红框中的“组件”按钮图标,就可以看到可用的组件列表。各组件的说明请参考[IoT Studio组件说明](https://help.aliyun.com/document_detail/125196.html)
<br>
### 4.4、**页面设计**
&emsp;&emsp;
这里我们用到3个组件:
* 实时曲线
用于显示火焰亮度的历史变化曲线及实时数据
* 指示灯
显示和控制空调和火焰的当前报警状态
* 设备地图
用于显示设备所在位置
将三个组件拖到中间的画布区,适当调整组件布局,如下图所示。
<div align="center">
<img src=./../../../images/2_火焰监控系统_实时监控页面设计.png width=80%/>
</div>
<br>
### 4.5、**关联产品和设备**
&emsp;&emsp;
此时回到”火焰监控系统“项目的主页,对产品和设备进行关联,如下图所示:
<div align="center">
<img src=./../../../images/4_火焰监控系统_关联产品和设备.png width=80%/>
</div>
&emsp;&emsp;
关联产品和设备的过程如下,选中左下角的“关联产品的同时关联其下所有设备”之后 ,该产品下创建的所有的产品都会被关联到这个项目中。
<div align="center">
<img src=./../../../images/4_火焰监控系统_关联产品和设备.png width=80%/>
</div>
&emsp;&emsp;
产品和设备关联完毕之后,就可以将把组件和设备的属性关联起来了。
<br>
### 4.6、**关联数据源**
&emsp;&emsp;
关联数据源分为如下3个步骤,每个步骤的截图如下:
* 关联产品
* 关联设备
* 关联属性
&emsp;&emsp;
具体操作步骤如下:
1. 选中”指示灯“组件,点击右侧的“配置数据源”。
<div align="center">
<img src=./../../../images/4_火焰监控系统_指示灯_配置数据源.png width=80%/>
</div>
2. 选择目标产品
<div align="center">
<img src=./../../../images/4_火焰监控系统_指示灯_选择产品.png width=80%/>
</div>
3. 选择目标设备
<div align="center">
<img src=./../../../images/4_火焰监控系统_开关_配置数据源_设备.png width=80%/>
</div>
4. 选择“报警灯”属性
<div align="center">
<img src=./../../../images/4_火焰监控系统_指示灯_配置数据源_报警灯.png width=80%/>
</div>
&emsp;&emsp;
选择好产品、设备和属性之后,需要修改指示灯大小及其展示样式(设置为图片),并且分别为”开始报警“/”停止报警“两种状态上传图片,如下图所示。图片位于[开启报警](../../../images/4_火焰燃烧示意图.png)[关闭报警](../../../images/4_火焰熄灭示意图.png)
<div align="center">
<img src=./../../../images/4_火焰监控系统_指示灯_配置数据源_报警灯图片.png width=80%/>
</div>
&emsp;&emsp;
同样的方式为”实时曲线“设置为目标设备的”火焰检测电压“,并显示最近半小时的数据,如下图所示。
<div align="center">
<img src=./../../../images/4_火焰监控系统_实时曲线设定.png width=80%/>
</div>
&emsp;&emsp;
选中”地图“组件,点击“编辑设备地图”按钮,如下图所示。
<div align="center">
<img src=./../../../images/4_火焰监控系统_地图_编辑入口.png width=80%/>
</div>
&emsp;&emsp;
点击“选择产品”按钮 ,选中“火焰检测系统”,如下图所示。如果此时设备已经上线,则地图会自动跳到设备当前所在位置。
<div align="center">
<img src=./../../../images/4_火焰监控系统_地图_选择产品.png width=80%/>
</div>
<br>
### 4.7、**业务逻辑开发**
&emsp;&emsp;
业务逻辑的主要目的是为了让用户设定物联网设备端的行为逻辑,常规物联网系统都是在设备端固化行为逻辑,出厂之后如果需要修改设备的行为,则需要进行OTA升级。本节课程则向读者介绍如何通过IoT Studio完成业务逻辑的开发。
&emsp;&emsp;
新建一条名为“火焰亮度监控报警设定”的规则。
<div align="center">
<img src=./../../../images/4_火焰检测系统_创建业务逻辑.png width=80%/>
</div>
&emsp;&emsp;
系统会自动进入到业务逻辑编辑页面,如下图所示,点击左侧的“节点”按钮,则可以看到所有可用的节点选项。右侧红框是如何设计一个业务逻辑的介绍。
<div align="center">
<img src=./../../../images/4_火焰监控报警.png width=80%/>
</div>
1. 选择目标节点
&emsp;&emsp;
此逻辑需要通过“火焰检测系统”上报的“火焰检测电压”当低于高于电压阈值的时候打开报警灯,否则关闭报警灯。所以需要如下4个节点:
* 设备触发节点
* 条件判断节点
* 开启报警灯节点
* 关闭报警灯节点
&emsp;&emsp;
分别从左侧拖动“设备触发”,“条件判断”和2个“火焰检测系统”4个节点到右侧的业务逻辑编辑框。
<div align="center">
<img src=./../../../images/4_火焰监控系统_节点列表.png width=80%/>
</div>
2. 建立节点间的关联关系
&emsp;&emsp;
按照预设的逻辑建立,如下图所示(在节点的一侧按下鼠标拖动到另一节点的一侧即可建立连线关系)。
<div align="center">
<img src=./../../../images/4_火焰监控系统_节点逻辑关系建立.png width=80%/>
</div>
1. 业务逻辑编辑
* 设备触发节点
&emsp;&emsp;
此设备触发选择“火焰检测设备”的“火焰检测电压”属性即可,如下图所示(和前面“组件”设定类似,同样是鼠标选中第节点,在右侧的配置选项中进行配置)。
<div align="center">
<img src=./../../../images/4_火焰监控系统_设备触发节点配置.png width=80%/>
</div>
* 条件判断节点
&emsp;&emsp;
此处我们设定为当传感器测量到的电压值高于1000mV则开始报警。设定步骤如下。
> 在没有检测到火焰的时候传感器输出高电平,在检测到火焰之后,输出电压会下降
<div align="center">
<img src=./../../../images/4_火焰监控系统_比较节点配置.png width=80%/>
</div>
* 设备节点行为设定
&emsp;&emsp;
分别为设备节点设定开启报警灯和关闭报警灯的行为,如下图所示。
<div align="center">
<img src=./../../../images/4_火焰监控系统_设备节点行为设定.png width=80%/>
</div>
* 业务逻辑保存和部署
&emsp;&emsp;
依此点击右上角的“保存”和“部署”按钮,即可将此业务逻辑设定完毕。
<br>
<br>
### 4.8、**预览和发布上线**
&emsp;&emsp;
业务逻辑设定完毕之后,可以在“火焰监控报警系统”页面编辑页面点击“预览”按钮进行预览,如下图所示。
<div align="center">
<img src=./../../../images/4_火焰监控系统_预览.png width=80%/>
</div>
&emsp;&emsp;
在发布之前可以点击上图的“预览”查看应用的实际运行效果。实际运行效果如下所示,同时可以扫描二维码在手机上面查看实际运行效果。
<div align="center">
<img src=./../../../images/4_火焰监控报警系统效果.gif width=80%/>
</div>
&emsp;&emsp;
此时查看设备上面的报警灯的状态会同步和web应用的报警灯状态同步显示。
## 5、运行结果
### 5.1、本地查看
&emsp;&emsp;
Python脚本推送到HaaS EDU K1之后 ,会自动运行,运行过程中日志如下。其中:
* "物联网平台连接成功" 代表成功连接到物联网平台
* 打火机打着火后,靠近火焰传感器电压值降低
* 打火机打着火后,远离火焰传感器电压值升高
```python
...
Wi-Fi connected
...
DeviceIP:172.20.10.10
...
success to establish tcp, fd=54
物联网平台连接成功
uploading data to the cloud, {"alarmState": 1, "fireVoltage": 2598}
uploading data to the cloud, {"alarmState": 1, "fireVoltage": 2598}
uploading data to the cloud, {"alarmState": 1, "fireVoltage": 2598}
```
> 打火机打着火后,远离和靠近火焰传感器的二极管,查看设备端日志的量测值是否有变化。
> 请务必注意安全!!!
<br>
### 5.2、物联网平台端设备信息查看
&emsp;&emsp;
物联网设备的系统启动成功并连接到物联网平台之后,物联网平台上对应的设备状态会从”未激活状态“变为”上线“,在物模型数据标签页上会显示设备上报到物联网平台的属性值。
<div align="center">
<img src=./../../../images/1_火焰检测系统_设备状态及属性.png width=100%/>
</div>
&emsp;&emsp;
此时如果开发板周围的火焰强度发生变化,物联网平台的物模型数据会更新为设备上报的最新的属性值。
<br>
### 5.3、物联网平台控制报警灯状态
&emsp;&emsp;
物联网设备上线之后,可以通过”监控运维“中的"在线调试"功能进行调试,详细操作步骤见下图:
<div align="center">
<img src=./../../../images/1_火焰检测系统_物联网平台在线调试功能.png width=60%/>
</div>
&emsp;&emsp;
此产品的物模型属性中,"火焰检测电压值"设置的是只读,也就是说智能从设备端读取,不支持设置此状态到设备端,所以点开"火焰检测电压"后面的”调试“之后,里边只有获取的选项。”报警状态“设置的是可读可写,所以点开”报警状态“后面的”调试“之后,里边有”获取“、”设置”和“设置期望值”三个选项。
这里可以选择打开报警状态之后点击“设置”进行报警灯功能的调试。
<div align="center">
<img src=./../../../images/1_火焰检测系统_云端打开报警灯.png width=100%/>
</div>
&emsp;&emsp;
此时查看设备端LED是否已经打开,打开成功则代表控制报警灯成功。
<br>
<br>
&emsp;&emsp;
这样整个火焰检测系统的创意案例就完成了。如果想要看整个案例更详细的操作步骤,请参考“[火焰检测系统详解](https://gitee.com/haasedu/haasedu/blob/release_2.0/4-%E6%99%BA%E6%85%A7%E5%B7%A5%E4%B8%9A/%E5%9C%BA%E6%99%AF1-%E7%81%AB%E7%84%B0%E7%9B%91%E6%8E%A7%E6%8A%A5%E8%AD%A6%E7%B3%BB%E7%BB%9F/README.md)”中的说明。
<br>
\ No newline at end of file
{
"name": "haaseduk1",
"version": "1.0.0",
"io": {
"fire": {
"type": "ADC",
"port": 2,
"sampling": 12000000
},
"mpu6050": {
"type": "I2C",
"port": 1,
"addrWidth": 7,
"freq": 100000,
"mode": "master",
"devAddr": 105
},
"ap3216c": {
"type": "I2C",
"port": 1,
"addrWidth": 7,
"freq": 100000,
"mode": "master",
"devAddr": 30
},
"si7006": {
"type": "I2C",
"port": 1,
"addrWidth": 7,
"freq": 400000,
"mode": "master",
"devAddr": 64
},
"spl06": {
"type": "I2C",
"port": 1,
"addrWidth": 7,
"freq": 400000,
"mode": "master",
"devAddr": 119
},
"qmc5883": {
"type": "I2C",
"port": 1,
"addrWidth": 7,
"freq": 400000,
"mode": "master",
"devAddr": 13
},
"cht8305": {
"type": "I2C",
"port": 1,
"addrWidth": 7,
"freq": 1000000,
"mode": "master",
"devAddr": 64
},
"qmi8610": {
"type": "I2C",
"port": 1,
"addrWidth": 7,
"freq": 100000,
"mode": "master",
"devAddr": 106
},
"qmp6988": {
"type": "I2C",
"port": 1,
"addrWidth": 7,
"freq": 100000,
"mode": "master",
"devAddr": 86
},
"qmc6310": {
"type": "I2C",
"port": 1,
"addrWidth": 7,
"freq": 100000,
"mode": "master",
"devAddr": 28
},
"ADC2": {
"type": "ADC",
"port": 2,
"sampling": 12000000
},
"GPIO2": {
"type": "GPIO",
"port": 2,
"dir": "irq",
"pull": "pullup",
"intMode": "rising"
},
"GPIO3": {
"type": "GPIO",
"port": 3,
"dir": "output",
"pull": "pullup"
},
"GPIO4": {
"type": "GPIO",
"port": 4,
"dir": "output",
"pull": "pullup"
},
"GPIO5": {
"type": "GPIO",
"port": 5,
"dir": "output",
"pull": "pullup"
},
"GPIO6": {
"type": "GPIO",
"port": 6,
"dir": "output",
"pull": "pullup"
},
"GPIO7": {
"type": "GPIO",
"port": 7,
"dir": "output",
"pull": "pullup"
},
"GPIO19": {
"type": "GPIO",
"port": 19,
"dir": "output",
"pull": "pullup"
},
"GPIO22": {
"type": "GPIO",
"port": 22,
"dir": "output",
"pull": "pullup"
},
"GPIO23": {
"type": "GPIO",
"port": 23,
"dir": "irq",
"pull": "pullup",
"intMode": "rising"
},
"I2C1": {
"type": "I2C",
"port": 1,
"addrWidth": 7,
"freq": 100000,
"mode": "master",
"devAddr": 60
},
"led_r": {
"type": "GPIO",
"port": 36,
"dir": "output",
"pull": "pullup"
},
"led_g": {
"type": "GPIO",
"port": 35,
"dir": "output",
"pull": "pullup"
},
"led_b": {
"type": "GPIO",
"port": 34,
"dir": "output",
"pull": "pullup"
},
"SPI0": {
"type": "SPI",
"port": 0,
"mode": "master",
"freq": 2000000
},
"oled_spi": {
"type": "SPI",
"port": 1,
"mode": "master",
"freq": 26000000
},
"oled_dc": {
"type": "GPIO",
"port": 28,
"dir": "output",
"pull": "pullup"
},
"oled_res": {
"type": "GPIO",
"port": 30,
"dir": "output",
"pull": "pullup"
},
"serial2": {
"type": "UART",
"port": 2,
"dataWidth": 8,
"baudRate": 9600,
"stopBits": 1,
"flowControl": "disable",
"parity": "none"
}
},
"debugLevel": "ERROR",
"repl": "disable"
}
\ No newline at end of file
from driver import ADC
class Fire(object):
def __init__(self, adcObj):
self.adcObj = None
if not isinstance(adcObj, ADC):
raise ValueError("parameter is not an ADC object")
self.adcObj = adcObj
def getVoltage(self):
if self.adcObj is None:
raise ValueError("invalid ADC object")
value = self.adcObj.readVoltage()
return value
# -*- encoding: utf-8 -*-
from aliyunIoT import Device # aliyunIoT组件是连接阿里云物联网平台的组件
import utime # 延时API所在组件
import ujson # json字串解析库
import fire
from driver import GPIO # HaaS EDU K1 LED使用GPIO进行控制
from driver import ADC # ADC类,通过微处理器的ADC模块读取ADC通道输入电压
import netmgr as nm # netmgr是Wi-Fi网络连接的组件
adcDev = 0 # ADC通道对象
ledDev = 0 # 报警LED对象
alarm_on = 0 # 记录报警状态
# 物联网平台连接标志位
iot_connected = False
# 三元组信息
productKey = "产品密钥"
deviceName = "设备名称"
deviceSecret = "设备密钥"
# Wi-Fi SSID和Password设置
wifiSsid = "请填写您的路由器名称"
wifiPassword = "请填写您的路由器密码"
# 物联网设备实例
device = None
def alarm_control(on):
global ledDev
if on == 0:
ledDev.write(0) # GPIO写入0,执行灭灯操作
else:
ledDev.write(1) # GPIO写入 1,执行亮灯报警动作
# 等待Wi-Fi成功连接到路由器
def get_wifi_status():
nm.init()
nm.disconnect()
wifi_connected = nm.getStatus()
# 连接到指定的路由器(路由器名称为wifiSsid, 密码为:wifiPassword)
print("start to connect " , wifiSsid)
nm.connect(wifiSsid, wifiPassword)
while True :
if wifi_connected == 5: # nm.getStatus()返回5代表连线成功
break
else:
wifi_connected = nm.getStatus() # 获取Wi-Fi连接路由器的状态信息
utime.sleep(0.5)
print("Wi-Fi connected")
print('DeviceIP:' + nm.getInfo()['ip']) # 打印Wi-Fi的IP地址信息
# 物联网平台连接成功的回调函数
def on_connect(data):
global iot_connected
iot_connected = True
# 设置props 事件接收函数(当云平台向设备下发属性时)
def on_props(request):
global alarm_on, device
# print(request)
payload = ujson.loads(request['params'])
# 获取dict状态字段 注意要验证键存在 否则会抛出异常
if "alarmState" in payload.keys():
alarm_on = payload["alarmState"]
if (alarm_on):
print("开始报警")
else:
print("结束报警")
# print(alarm_on)
# 根据云端设置的报警灯状态改变本地LED状态
alarm_control(alarm_on)
# 要将更改后的状态同步上报到云平台
prop = ujson.dumps({'alarmState': alarm_on})
# print('uploading data: ', prop)
upload_data = {'params': prop}
# 上报本地报警灯状态到云端
device.postProps(upload_data)
# 连接物联网平台
def connect_lk(productKey, deviceName, deviceSecret):
global device, iot_connected
key_info = {
'region': 'cn-shanghai',
'productKey': productKey,
'deviceName': deviceName,
'deviceSecret': deviceSecret,
'keepaliveSec': 60
}
# 将三元组信息设置到iot组件中
device = Device()
# 设定连接到物联网平台的回调函数,如果连接物联网平台成功,则调用on_connect函数
device.on(Device.ON_CONNECT, on_connect)
# 配置收到云端属性控制指令的回调函数
# 如果收到物联网平台发送的属性控制消息,则调用on_props函数
device.on(Device.ON_PROPS, on_props)
# 启动连接阿里云物联网平台过程
device.connect(key_info)
# 等待设备成功连接到物联网平台
while(True):
if iot_connected:
print('物联网平台连接成功')
break
else:
print('sleep for 1 s')
utime.sleep(1)
# 上传火焰传感器检测电压信息和报警信息到物联网平台
def upload_fire_detector_state():
global device, alarm_on
fireVoltage = 0
# 无限循环
while True:
fireVoltage = fireDev.getVoltage() # 获取电压值
# print("The fire status Voltage ",fireVoltage)
# 生成上报到物联网平台的属性值字串
# 此处的属性标识符"fireVoltage"和"alarmState"必须和物联网平台的属性一致
# "fireVoltage" - 代表燃气传感器测量到的电压值
# "alarmState" - 代表报警灯的当前状态
prop = ujson.dumps({
'fireVoltage': fireVoltage,
'alarmState': alarm_on
})
print("uploading data to the cloud, ", prop)
upload_data = {'params': prop}
# 上传火焰传感器测量结果和报警灯状态到物联网平台
device.postProps(upload_data)
# 每2秒钟上报一次
utime.sleep(2)
if __name__ == '__main__':
global fireDev
alarm_on = 0
# 硬件初始化
# 初始化 ADC
adcDev = ADC()
adcDev.open("fire")
fireDev = fire.Fire(adcDev)
# 初始化LED所连接GPIO
ledDev = GPIO()
ledDev.open("led_r")
alarm_control(alarm_on) # 关闭报警灯
# 请替换物联网平台申请到的产品和设备信息,可以参考文章:https://blog.csdn.net/HaaSTech/article/details/114360517
get_wifi_status()
connect_lk(productKey, deviceName, deviceSecret)
upload_fire_detector_state()
adcDev.close()
ledDev.close()
# 花卉养植
&emsp;&emsp;
下图是本案例除硬件连线外的3步导学,每个步骤中实现的功能请参考图中的说明。
<div align="center">
<img src=./../../../images/3_花卉养植系统_步骤概述.jpg width=70%/>
</div>
## 1、简介
&emsp;&emsp;
我们国家的现代农业和过去相比已经有了长足的进步,其中花卉养植监控系统也是高效农业的一个重要组成部分。
&emsp;&emsp;
花卉养植监控系统是一种可以改变植物生长环境、为植物生长创造最佳条件、避免外界四季变化和恶劣气候对其影响的场所。它以采光覆盖材料作为全部或部分结构材料,可在冬季或其他不适宜露地植物生长的季节栽培植物。花卉养植监控系统生产以达到调节产期,促进生长发育,防治病虫害及提高质量、产量等为目的。而花卉养植监控系统设施的关键技术是环境控制,该技术的最终目标是提高控制与作业精度。
&emsp;&emsp;
本章课程就以山茶花养植为例介绍如何打造一个最适合茶花生长的花卉养植系统。
&emsp;&emsp;
按照茶花的生长习性,最适宜茶花的生长环境因素主要有3个:
1. 温度:20~28度
2. 湿度:~70%
3. 光照:不能太强
&emsp;&emsp;
为了制造适合茶花生长的环境,本场景的示意图如下图所示,场景设计的行为如下:
1. 通过温度传感器测量环境温度,并在温度过高时打开空调进行降低环境温度
2. 通过湿度传感器测量环境湿度,并在湿度过低的时候控制加湿器进行加湿操作
### 1.1、准备
&emsp;&emsp;
本实验只需要如下硬件,无需外接其它硬件:
* [HaaS EDU K1开发板](https://haas.iot.aliyun.com/solution/detail/hardware?versionId=800C5AB3B8A4A88800000001&dataId=800C5AB3B8A4A888) 一台
* Type-C USB数据线 一条
&emsp;&emsp;
案例中需要两颗LED模拟控制空调和加湿器的开关的功能,此处就借助于HaaS EDU K1的蓝色和绿色LED灯,其中:
* 蓝色LED模拟空调的开关
* 绿色LED模拟加湿器的开关
&emsp;&emsp;
如果要模拟控制空调打开,则需要拉高P34(蓝色LED);如需模拟控制加湿器打开,则需要拉高P35(绿色LED)。
## 2、物联网平台开发
### 2.1、开通公共实例
&emsp;&emsp;
对于第一次使用物联网平台的读者,需要开通实例以使用物联网平台的功能。这里可以使用免费的公共实例进行开发。
&emsp;&emsp;
[物联网平台](https://iot.console.aliyun.com/lk/summary/new)中,左上角选择“华东2-上海”,点击“公共实例”,即可开通。
<div align="center">
<img src=./../../../images/5_3_开通公共实例.png
width=100%/>
</div>
开通物联网平台功能之后,需要完成下面的4个步骤完成云端设备的设定:
1. 创建云端产品
2. 创建产品属性(物模型)
3. 创建云端设备(获取三元组)
4. 多设备场景联动设定
<br>
### 2.2、创建云端产品
&emsp;&emsp;
点击上图中的“公共实例”,即可进入[控制台](https://iot.console.aliyun.com/lk/summary/new)进行产品创建。然后,点击创建产品按钮,如下图所示。
<div align="center">
<img src=./../../../images/1_创建产品.png
width=100%/>
</div>
&emsp;&emsp;
在新建产品设定页面按照下图所示,设定“产品名称”,选择所属的“标准品类”(如果创建的产品品类为非标品类,可以选择自定义品类),节点类型选择“直连设备”,联网方式选择“Wi-Fi”,数据格式选择“ICA标准数据格式”,检验类型和认证方式选择默认设定即可。还可以根据开发者自己的需求在“产品描述”页面添加针对此产品的描述。
<div align="center">
<img src=./../../../images/1_新建产品页面.png
width=100%/>
</div>
&emsp;&emsp;
选择之后,点击“确认”按钮,即可完成产品创建。返回“产品”页面之后可以看到产品类表中会出现刚刚创建的“温湿度检测装置”的产品,如下图所示。
<div align="center">
<img src=./../../../images/1_产品列表页.png width=100%/>
</div>
<br>
### 2.3、创建产品属性(物模型)
&emsp;&emsp;
点击上图中的“查看”按钮,即可看到产品信息,Topic列表,功能定义,数据解析等跟产品相关功能的设定。点开“功能定义”标签页,可以看到设备物模型定义。
> 物模型中的“地理位置”属性本案例后面的内容没有用到,读者可忽略。
<div align="center">
<img src=./../../../images/1_产品详情页面.png width=100%/>
</div>
&emsp;&emsp;
因为在创建产品的过程中选择了标准的产品品类,这里会出现标准品类中自带的物模型设定,包含“当前温度”、“当前湿度”和“地理位置”信息。这三条都适合属性信息,其标识符、数据类型、数据定义及该属性支持的操作都可以在这个页面看到。
&emsp;&emsp;
标识符是设备端上报设备属性状态的消息中需要使用的标识符,并且只有在设备上报的属性内容符合“数据定义”中的数据取值范围的时候才会被物联网平台记录,否则会被物联网平台认定为非法属性而过滤掉。
&emsp;&emsp;
如果在创建产品的时候没有选择标准的品类,则需要在这个地方新增物模型的属性或服务。详情请参考[物联网平台](../../../拓展知识/物联网平台/README.md)中的内容。
&emsp;&emsp;
产品及其物模型创建完成后就可以创建这个产品的设备了。
<br>
### 2.4、创建云端设备(获取三元组)
&emsp;&emsp;
在产品列表页面中,点击“管理设备”,就会进到设备管理页面。
<div align="center">
<img src=./../../../images/1_管理设备入口.png width=100%/>
</div>
&emsp;&emsp;
在“设备”页面点击“添加设备”按钮,如下图所示。
<div align="center">
<img src=./../../../images/1_添加设备入口.png width=100%/>
</div>
&emsp;&emsp;
在“添加设备”页面中设定“deviceName”,这里开发者可以自己填入自己想设定的设备名称,也可以不填任何内容让系统自动生成设备名称,如下图所示。
<div align="center">
<img src=./../../../images/1_添加设备.png width=40%/>
</div>
&emsp;&emsp;
设备添加完成后,点击“前往查看”按钮,就可以看到此设备端详细信息了。
<div align="center">
<img src=./../../../images/1_完成添加设备.png width=40%/>
</div>
&emsp;&emsp;
设备信息中有两个信息需要和设备端开发相匹配:
1. 三元组
2. 物模型属性信息
<div align="center">
<img src=./../../../images/1_设备详细信息.png width=100%/>
</div>
<br>
##### 2.4.1、**获取设备三元组**
&emsp;&emsp;
如上图所示,点击“查看”按钮,就可以看到设备的三元组信息,三元组是物联网设备端和物联网云端设备相关联的唯一标识符,在设备端连接云端的时候会使用三元组信息和云端进行鉴权,鉴权通过之后云端会认为设备已激活并上线。
<div align="center">
<img src=./../../../images/1_三元组信息.png width=60%/>
</div>
<br>
#### 2.4.2、**查看设备属性信息**
&emsp;&emsp;
设备详情信息页中的“物模型数据”标签页中可以看到设备的所有属性信息、设备时间上报情况及设备服务调用情况,如下图所示。待物联网设备按照设备属性对应的标识符上报设备属性的时候,本图片中的“当前温度“,”当前湿度“等属性值就会显示设备最新的属性信息。
<div align="center">
<img src=./../../../images/1_设备物模型数据.png width=100%/>
</div>
<br>
> 创建产品和设备的过程是按照面向对象的思想进行设计的,其中创建产品可以看成是新建一个类,其中的物模型则是类的对象,创建设备则是进行类的实例化。
### 2.5、**云端物模型**
&emsp;&emsp;
在此产品的物模型中新增两个bool类型的属性,命名为"airconditioner"和"humidifier"分别用来控制空调和加湿器。
* 当需要打开空调时,云端将“airconditioner”的属性值设置为True;需要关闭空调时,将其设置为False。
* 当需要打开加湿器时,云端将“humidifier”的属性值设置为True;需要关闭空调时,将其设置为False。
&emsp;&emsp;
添加物模型属性的过程下所述:
1. 在产品的“功能定义”标签页中点击"编辑草稿",如果没有“编辑草稿”的按钮,说明此产品的物模型已经发布,需要点击右上角的“取消发布”按钮才能添加或删除属性。
<div align="center">
<img src=./../../../images/3_添加物模型属性_编辑草稿.png width=80%/>
</div>
2. 点击“添加自定义功能”按钮,如下图所示:
<div align="center">
<img src=./../../../images/3_花卉养植_添加空调和加湿器属性.png width=80%/>
</div>
3. 添加属性名称、标识符、类型等信息,如下图所示:
* 添加“airconditioner”属性
<div align="center">
<img src=./../../../images/3_花卉养植_添加控制空调属性.png width=40%/>
</div>
* 添加“humidifier”属性
<div align="center">
<img src=./../../../images/3_花卉养植_添加控制加湿器属性.png width=40%/>
</div>
&emsp;&emsp;
添加完属性之后,点击左下角的“发布上线”按钮 ,根据系统提示进行确认即可,如下图所示:
<div align="center">
<img src=./../../../images/3_花卉养植_属性发布上线.png width=80%/>
</div>
<div align="center">
<img src=./../../../images/3_花卉养植_属性发布上线_确认.png width=40%/>
</div>
### 2.6、**多设备场景联动设定**
&emsp;&emsp;
控制逻辑有两种实现方式:
1. 使用物联网平台的规则引擎功能
2. 使用IoT Studio的业务逻辑功能
&emsp;&emsp;
本节接下来就开始介绍使用物联网平台的规则引擎来完成此控制逻辑的设定。
&emsp;&emsp;
按照下图所示的步骤创建场景联动的规则:
<div align="center">
<img src=./../../../images/3_花卉养植_创建场景联动规则.png width=80%/>
</div>
&emsp;&emsp;
点击“创建规则”之后,填入场景规则名称,这里我们先创建第一个“温度过高开启空调”的规则。
<div align="center">
<img src=./../../../images/3_花卉养植_创建场景联动规则_温度过高开启空调.png width=40%/>
</div>
&emsp;&emsp;
规则创建完成后,等待几秒钟系统会自动跳转到规则编辑页面,这里需要配置如下两步完整一整条规则的创建:
1. 设置“触发器”为温湿度监控器的“当前温度"属性高于茶花最适宜生长的温度28度触发此规则的执行,如下图所示
<div align="center">
<img src=./../../../images/3_花卉养植_温度过高开启空调_触发器设定.png width=80%/>
</div>
2. 设置“执行动作"为控制设备打开”空调开关“属性
<div align="center">
<img src=./../../../images/3_花卉养植_温度过高开启空调_执行器设定.png width=80%/>
</div>
&emsp;&emsp;
点击保存之后”温度过高打开空调“的场景联动规则就创建好了。
&emsp;&emsp;
用相同的方式创建另外三条场景联动规则:
* "温度合适关闭空调",如下图所示
<div align="center">
<img src=./../../../images/3_花卉养植_创建场景联动规则_温度合适关闭空调.png width=80%/>
</div>
* ”湿度适合关闭加湿器“,如下图所示
<div align="center">
<img src=./../../../images/3_花卉养植_创建场景联动规则_湿度适合关闭加湿器.png width=80%/>
</div>
* "湿度过低打开加湿器“,如下图所示
<div align="center">
<img src=./../../../images/3_花卉养植_创建场景联动规则_湿度过低开启加湿器.png width=80%/>
</div>
&emsp;&emsp;
创建完这几条规则之后,在”场景联动“页面中点击规则后面的启动按钮,则所有的规则都会运行起来。
<div align="center">
<img src=./../../../images/3_花卉养植_开启场景联动规则.png width=80%/>
</div>
## 3、设备端开发
### 3.1、开发环境
&emsp;&emsp;
在进行下一步之前请确保HaaS EDU K1开发环境已经搭建完毕。详情请参考[HaaS EDU K1开发环境](../../../startup/HaaS_EDU_K1_startup.md)的说明。
### 3.2、创建解决方案
&emsp;&emsp;
如下图所示,在Haas Studio中创建项目。先选择左侧的“开发板型号”再从右侧的案例中选择“花卉养植”案例点击“立即创建”即可。
<div align="center">
<img src=./../../../images/HaaS_Studio_创建工程示范.png width=100%/>
</div>
<br>
1. **修改路由器名称及密码**
&emsp;&emsp;
修改工程里main.py中wifiSsid和wifiPassword的值为读者实际要连接的路由器的名称及密码(请注意名称和密码都需要放在""符号中间)。
```python
# Wi-Fi SSID和Password设置
wifiSsid = "请填写您的路由器名称"
wifiPassword = "请填写您的路由器密码"
```
&emsp;&emsp;
修改完成之后get_wifi_status函数中的wlan.connect(wifiSsid, wifiPassword)语句就会连接读者自己设定的路由器。
2. **修改设备端三元组**
&emsp;&emsp;
修改humiture工程里main.py中productKey、deviceName和deviceSecret的值为读者创建的物联网设备的三元组信息,如下图所示:
```python
# 三元组信息
productKey = "产品密钥"
deviceName = "设备名称"
deviceSecret = "设备密钥"
```
3. **修改设备端上报温湿度信息所用标识符**
&emsp;&emsp;
humiture工程里main.py中下面的代码实现的是上传温湿度值到云端的功能。其中CurrentTemperature和CurrentHumidity便是温湿度值上报时所用的标识符。
```python
# 上传温度信息和湿度信息到物联网平台
def upload_temperature_and_Humidity():
global device
while True:
data = get_temp_humi() # 读取温度信息和湿度信息
# 生成上报到物联网平台的属性值字串
prop = ujson.dumps({
'CurrentTemperature': data[0],
'CurrentHumidity': data[1]
})
print('uploading data: ', prop)
upload_data = {'params': prop}
# 上传温度和湿度信息到物联网平台
device.postProps(upload_data)
utime.sleep(2)
```
&emsp;&emsp;
确保这两个标识符和物联网产品的物模型中属性标识符是一样的,如下图所示:
<div align="center">
<img src=./../../../images/Haa2200_humiture_属性标识符修改.png
width=80%/>
</div>
&emsp;&emsp;
<b>下面是本节添加的重点代码段落讲解:</b>
* 新增两个变量用来控制空调和加湿器对应的GPIO
```python
# 空调和加湿器状态变量
airconditioner = 0
humidifier = 0
```
* 新增函数处理云端信息并控制GPIO的接口
```python
# 设置props 事件接收函数(当云平台向设备下发属性时)
def on_props(request):
global airconditioner, humidifier, airconditioner_value, humidifier_value
# {"airconditioner":1} or {"humidifier":1} or {"airconditioner":1, "humidifier":1}
payload = ujson.loads(request['params'])
# print (payload)
# 获取dict状态字段 注意要验证键存在 否则会抛出异常
if "airconditioner" in payload.keys():
airconditioner_value = payload["airconditioner"]
if (airconditioner_value):
print("打开空调")
else:
print("关闭空调")
if "humidifier" in payload.keys():
humidifier_value = payload["humidifier"]
if (humidifier_value):
print("打开加湿器")
else:
print("关闭加湿器")
# print(airconditioner_value, humidifier_value)
airconditioner.write(airconditioner_value) # 控制空调开关
humidifier.write(humidifier_value) # 控制加湿器开关
# 要将更改后的状态同步上报到云平台
prop = ujson.dumps({
'airconditioner': airconditioner_value,
'humidifier': humidifier_value,
})
upload_data = {'params': prop}
# 上报空调和加湿器属性到云端
device.postProps(upload_data)
```
<br>
## 4、运行结果
&emsp;&emsp;
推送此脚本到HaaS EDU K1之后,运行此脚本,HaaS EDU K1串口会周期性的打印如下日志。其中:
* “物联网平台连接成功” 代表成功连接到物联网平台
* "uploading data:"之后跟着的为设备端向云端发布的温湿度属性信息,其中CurrentTemperature后面的数值为温度值,单位:摄氏度;CurrentHumidity后面数值为相对湿度值。
* 设备端解析到云端送下来的控制指令中包含“{"humidifier":1}”之后会打印"打开加湿器"并点亮绿色LED模拟打开控制加湿器的继电器的动作
* 设备端解析到云端送下来的控制指令包含"{"airconditioner":1}"之后会打印"打开空调"并点亮蓝色LED模拟打开控制空调的继电器的动作
```log
...
...
Wi-Fi connected
...
DeviceIP:172.20.10.10
...
success to establish tcp, fd=54
物联网平台连接成功
物联网平台连接成功
sleep for 2s
uploading data: {"CurrentTemperature": 28.30015, "CurrentHumidity": 50.82117}
打开空调
打开加湿器
```
&emsp;&emsp;
开发板实际效果如下:
• 在温度高于阈值时,可以看到日志中出现"打开空调"的字样,伴随着蓝色LED灯亮起
• 在温度低于阈值之后 ,可以看到日志中出现“关闭空调”的字样,伴随着蓝色LED灯熄灭
• 在湿度低于阈值时,可以看到日志中出现"打开加湿器"的字样,伴随着绿色LED灯亮起
• 在湿度高于阈值时,可以看到日志中出现"关闭加湿器"的字样,伴随着绿色LED灯熄灭
### 4.1、物联网平台端设备信息查看
&emsp;&emsp;
物联网设备的系统启动成功并连接到物联网平台之后,物联网平台上对应的设备状态会从”未激活状态“变为”上线“,在物模型数据标签页上会显示设备上报到物联网平台的属性值。
<div align="center">
<img src=./../../../images/1_温湿度监控设备状态及属性.png width=100%/>
</div>
&emsp;&emsp;
此时如果开发板温度和周围湿度发生变化,物联网平台的物模型数据会更新为设备上报的最新的属性值。
## 5、物联网应用开发
&emsp;&emsp;
现代花卉养植的过程中越来越多的需要农业专家的参与,但和养花的农户来说,农业专家的数量是很少的。在茶花生长出现问题的时候,怎样让农业专家远程迅速的完成对茶花异常生长状况的诊断是很重要的一个研究议题。本节要介绍的中控大屏就是将茶花当前生产环境的信息通过网页的形式直观的展现出来,一方面方便农户很方便的查看相关信息并判断是否需要人为干预;另一方面在出现问题的时候可以让专家远程快速的对问题进行诊断。
<br>
### 5.1、中控大屏目标
&emsp;&emsp;
本节课程主要目标是设计一个总控大屏的web应用,能实时的查看如下信息:
1. 过去3小时的温湿度实时信息
2. 空调及加湿器状态实时状态显示
3. 手动控制空调或加湿器的开关状态
<br>
### 5.2、茶花生长环境监控系统
&emsp;&emsp;
下图是一个典型的物联网应用程序开发的流程图,接下来本节就按照这个流程介绍如何花卉养植系统中控大屏的web应用程序的开发。
<div align="center">
<img src=./../../../images/1_物联网应用开发的一般流程.png width=80%/>
</div>
<br>
#### 5.2.1、**新建“普通项目”**
&emsp;&emsp;
打开[IoT Studio官网](https://studio.iot.aliyun.com/),在项目管理中创建一个空白项目,如下图所示,将此项目命名为“茶花养植系统”。
<div align="center">
<img src=./../../../images/3_茶花养植系统_创建IoTStudio项目.png width=80%/>
</div>
<br>
#### 5.2.2、**新建“Web应用”**
&emsp;&emsp;
新建“普通项目”之后,在新项目的首页新建一个Web应用,命名为“茶花生长环境实时监控”。
<div align="center">
<img src=./../../../images/3_茶花养植系统_茶花生长环境实时监控.png width=80%/>
</div>
&emsp;&emsp;
Web应用创建成功后会进入到应用界面设计页面。
<div align="center">
<img src=./../../../images/3_茶花养植系统_茶花生长环境实时监控_页面编辑页.png width=80%/>
</div>
&emsp;&emsp;
点击上图红框中的“组件”按钮图标,就可以看到可用的组件列表。各组件的说明请参考[IoT Studio组件说明](https://help.aliyun.com/document_detail/125196.html)
<br>
#### 5.2.3、**页面设计**
&emsp;&emsp;
这里我们用到3个组件:
* 实时曲线
用于显示温湿度的历史变化曲线及实时数据
* 开关
显示和控制空调和加湿器的当前状态
* 图片
空调或加湿器的示意图
&emsp;&emsp;
将三个组件拖到中间的画布区,适当调整组件布局。然后选中图片组件,点击右边的“上传图片”按钮,分别上传空调和加湿器的图片,如下图所示。
<div align="center">
<img src=./../../../images/3_茶花养植系统_茶花生长环境实时监控_图片上传.png width=80%/>
</div>
<br>
#### 5.2.4、**关联产品和设备**
&emsp;&emsp;
此时回到”茶花养植系统“项目的主页,对产品和设备进行关联,如下图所示:
<div align="center">
<img src=./../../../images/3_花卉养殖系统_关联产品和设备.png width=80%/>
</div>
&emsp;&emsp;
关联产品的过程如下:
<div align="center">
<img src=./../../../images/3_茶花养植系统_茶花养植系统_关联产品.png width=80%/>
</div>
&emsp;&emsp;
关联设备的过程如下:
<div align="center">
<img src=./../../../images/3_茶花养植系统_茶花生长环境实时监控_关联设备.png width=80%/>
</div>
&emsp;&emsp;
产品和设备关联完毕之后,就可以将把组件和设备的属性关联起来了。
<br>
#### 5.2.5、**关联数据源**
&emsp;&emsp;
关联数据源分为如下3个步骤,每个步骤的截图如下:
* 关联产品
* 关联设备
* 关联属性
&emsp;&emsp;
具体操作步骤如下:
1. 选中”开关“组件,点击右侧的“配置数据源”。
<div align="center">
<img src=./../../../images/3_茶花养植系统_茶花生长环境实时监控_开关_配置数据源.png width=80%/>
</div>
2. 选择目标产品
<div align="center">
<img src=./../../../images/3_茶花养植系统_茶花生长环境实时监控_选择产品.png width=80%/>
</div>
3. 选择目标设备
<div align="center">
<img src=./../../../images/3_茶花养植系统_茶花生长环境实时监控_开关_配置数据源_设备.png width=80%/>
</div>
4. 选择“空调开关”属性
<div align="center">
<img src=./../../../images/3_茶花养植系统_茶花生长环境实时监控_开关_配置数据源_空调开关.png width=80%/>
</div>
&emsp;&emsp;
选择好产品、设备和属性之后,可以看到湿度计表盘上面的湿度值已经已经更新为物联网平台设备属性中最新的湿度值。
&emsp;&emsp;
同样的方式为另一个”开关“组件选择相同设备的“加湿器”属性。
接下来需要为”实时曲线“组件设定数据源。同样点击该组件后,点击右边的配置数据源。因为本场景中的温湿度测量和空调/加湿器控制都是通过一个物联网设备完成的,所以本页中要选择”单设备多属性“。然后选择好本场景创建的产品,如下图所示。
<div align="center">
<img src=./../../../images/3_茶花养植系统_茶花生长环境实时监控_实时曲线_设备选择.png width=80%/>
</div>
&emsp;&emsp;
选择相同的设备,如下图所示。
<div align="center">
<img src=./../../../images/3_茶花养植系统_茶花生长环境实时监控_实时曲线_选择设备.png width=80%/>
</div>
&emsp;&emsp;
本场景显示的是茶花生长环境的温度信息和湿度信息,所以”数据项“中要选择这两项属性。
<div align="center">
<img src=./../../../images/3_茶花养植系统_茶花生长环境实时监控_实时曲线_历史数据属性.png width=80%/>
</div>
&emsp;&emsp;
最后选择要显示的实时温湿度数据的时间长度,这里选择3小时,如下图所示。
<div align="center">
<img src=./../../../images/3_茶花养植系统_茶花生长环境实时监控_实时曲线_数据时间段.png width=80%/>
</div>
<br>
#### 5.2.6、**发布上线**
<div align="center">
<img src=./../../../images/1_IoT_Studio_HaaS_EDU_K1_预览和发布.png width=80%/>
</div>
&emsp;&emsp;
在发布之前可以点击上图的“预览”查看应用的实际运行效果。实际运行效果如下图所示:
<div align="center">
<img src=./../../../images/3_茶花养植系统_茶花生长环境实时监控_运行效果.png width=80%/>
</div>
<br>
&emsp;&emsp;
这样我们就完成了一个花卉养植系统从设备端温湿度测量,设备控制、物联网云平台开发及物联网应用开发全链路的开发。
{
"name": "haaseduk1",
"version": "1.0.0",
"io": {
"mpu6050": {
"type": "I2C",
"port": 1,
"addrWidth": 7,
"freq": 100000,
"mode": "master",
"devAddr": 105
},
"ap3216c": {
"type": "I2C",
"port": 1,
"addrWidth": 7,
"freq": 100000,
"mode": "master",
"devAddr": 30
},
"si7006": {
"type": "I2C",
"port": 1,
"addrWidth": 7,
"freq": 400000,
"mode": "master",
"devAddr": 64
},
"spl06": {
"type": "I2C",
"port": 1,
"addrWidth": 7,
"freq": 400000,
"mode": "master",
"devAddr": 119
},
"qmc5883": {
"type": "I2C",
"port": 1,
"addrWidth": 7,
"freq": 400000,
"mode": "master",
"devAddr": 13
},
"cht8305": {
"type": "I2C",
"port": 1,
"addrWidth": 7,
"freq": 1000000,
"mode": "master",
"devAddr": 64
},
"qmi8610": {
"type": "I2C",
"port": 1,
"addrWidth": 7,
"freq": 100000,
"mode": "master",
"devAddr": 106
},
"qmp6988": {
"type": "I2C",
"port": 1,
"addrWidth": 7,
"freq": 100000,
"mode": "master",
"devAddr": 86
},
"qmc6310": {
"type": "I2C",
"port": 1,
"addrWidth": 7,
"freq": 100000,
"mode": "master",
"devAddr": 28
},
"ADC2": {
"type": "ADC",
"port": 2,
"sampling": 12000000
},
"GPIO2": {
"type": "GPIO",
"port": 2,
"dir": "irq",
"pull": "pullup",
"intMode": "rising"
},
"GPIO3": {
"type": "GPIO",
"port": 3,
"dir": "output",
"pull": "pullup"
},
"GPIO4": {
"type": "GPIO",
"port": 4,
"dir": "output",
"pull": "pullup"
},
"GPIO5": {
"type": "GPIO",
"port": 5,
"dir": "output",
"pull": "pullup"
},
"GPIO6": {
"type": "GPIO",
"port": 6,
"dir": "output",
"pull": "pullup"
},
"GPIO7": {
"type": "GPIO",
"port": 7,
"dir": "output",
"pull": "pullup"
},
"GPIO19": {
"type": "GPIO",
"port": 19,
"dir": "output",
"pull": "pullup"
},
"GPIO22": {
"type": "GPIO",
"port": 22,
"dir": "output",
"pull": "pullup"
},
"GPIO23": {
"type": "GPIO",
"port": 23,
"dir": "irq",
"pull": "pullup",
"intMode": "rising"
},
"I2C1": {
"type": "I2C",
"port": 1,
"addrWidth": 7,
"freq": 100000,
"mode": "master",
"devAddr": 60
},
"led_r": {
"type": "GPIO",
"port": 36,
"dir": "output",
"pull": "pullup"
},
"led_g": {
"type": "GPIO",
"port": 35,
"dir": "output",
"pull": "pullup"
},
"led_b": {
"type": "GPIO",
"port": 34,
"dir": "output",
"pull": "pullup"
},
"SPI0": {
"type": "SPI",
"port": 0,
"mode": "master",
"freq": 2000000
},
"oled_spi": {
"type": "SPI",
"port": 1,
"mode": "master",
"freq": 26000000
},
"oled_dc": {
"type": "GPIO",
"port": 28,
"dir": "output",
"pull": "pullup"
},
"oled_res": {
"type": "GPIO",
"port": 30,
"dir": "output",
"pull": "pullup"
},
"serial2": {
"type": "UART",
"port": 2,
"dataWidth": 8,
"baudRate": 9600,
"stopBits": 1,
"flowControl": "disable",
"parity": "none"
}
},
"debugLevel": "ERROR",
"repl": "disable"
}
\ No newline at end of file
"""
Copyright (C) 2015-2021 Alibaba Group Holding Limited
MicroPython's driver for CHT8305
Author: HaaS
Date: 2021/09/14
"""
from micropython import const
from utime import sleep_ms
from driver import I2C
CHT8305_REG_TEMP = 0x00
CHT8305_REG_HUMI = 0x01
# The register address in CHT8305 controller.
class CHT8305Error(Exception):
def __init__(self, value=0, msg="cht8305 common error"):
self.value = value
self.msg = msg
def __str__(self):
return "Error code:%d, Error message: %s" % (self.value, str(self.msg))
__repr__ = __str__
class CHT8305(object):
"""
This class implements cht8305 chip's functions.
"""
def __init__(self, i2cDev):
self._i2cDev = None
if not isinstance(i2cDev, I2C):
raise ValueError("parameter is not an I2C object")
# make CHT8305's internal object points to i2cDev
self._i2cDev = i2cDev
def getTemperature(self):
"""Get temperature."""
# make sure CHT8305's internal object is valid before I2C operation
if self._i2cDev is None:
raise ValueError("invalid I2C object")
# send temperature register to CHT8305
reg = bytearray([CHT8305_REG_TEMP])
self._i2cDev.write(reg)
# wait for 30ms
sleep_ms(30)
readData = bytearray(2)
# read temperature from CHT8305
self._i2cDev.read(readData)
# convert the temperature data to actual value
value = (readData[0] << 8) | readData[1]
if (value & 0xFFFC):
temperature = (165.0 * float(value)) / 65536.0 - 40.0
else:
raise CHT8305Error("failed to get temperature.")
return temperature
def getHumidity(self):
"""Get humidity."""
# make sure CHT8305's internal object is valid before I2C operation
if self._i2cDev is None:
raise ValueError("invalid I2C object")
# send humidity register to CHT8305
reg = bytearray([CHT8305_REG_HUMI])
self._i2cDev.write(reg)
sleep_ms(30)
# read humidity from CHT8305
readData = bytearray(2)
self._i2cDev.read(readData)
# convert the humidity data to actual value
value = (readData[0] << 8) | readData[1]
if (value & 0xFFFE):
humidity = ((125.0 * float(value)) / 65535.0) - 6.0
else:
raise CHT8305Error("failed to get humidity.")
return humidity
def getTempHumidity(self):
"""Get temperature and humidity."""
# make sure CHT8305's internal object is valid before I2C operation
if self._i2cDev is None:
raise ValueError("invalid I2C object")
temphumidity = [0, 1]
# send temperature register to CHT8305
reg = bytearray([CHT8305_REG_TEMP])
self._i2cDev.write(reg)
sleep_ms(30)
# 4 bytes means read temperature and humidity back in one read operation
readData = bytearray(4)
self._i2cDev.read(readData)
#print("rawdata %d-%d-%d-%d" %(readData[0],readData[1],readData[2],readData[3]))
# convert the temperature and humidity data to actual value
value = (readData[0] << 8) | readData[1]
if (value & 0xFFFC):
temphumidity[0] = (165.0 * float(value)) / 65536.0 - 40.0
else:
raise CHT8305Error("failed to get temperature.")
value = (readData[2] << 8) | readData[3]
if (value & 0xFFFE):
temphumidity[1] = ((125.0 * float(value)) / 65535.0) - 6.0
else:
raise CHT8305Error("failed to get humidity.")
return temphumidity
if __name__ == "__main__":
'''
The below i2c configuration is needed in your board.json.
"cht8305": {
"type": "I2C",
"port": 1,
"addrWidth": 7,
"freq": 400000,
"mode": "master",
"devAddr": 64
}
'''
print("Testing cht8305 ...")
i2cDev = I2C()
i2cDev.open("cht8305")
cht8305Dev = CHT8305(i2cDev)
temperature = cht8305Dev.getTemperature()
print("The temperature is: %f" % temperature)
humidity = cht8305Dev.getHumidity()
print("The humidity is: %f" % humidity)
i2cDev.close()
print("Test cht8305 done!")
"""
Copyright (C) 2015-2021 Alibaba Group Holding Limited
"""
from driver import I2C
import kv
class HAASEDUK1(object):
def __init__(self):
self.i2cDev = None
# 获取版本号
# 返回值为1,代表k1c
# 返回值为0,代表k1
def getHWID(self):
hwId = -1
result = kv.geti("HAASEDU_NAME")
if (result != None):
if (result == 1):
print("HAASEDUK1 hw version is 1.1")
return 1
elif (result == 0):
print("HAASEDUK1 hw version is 1.0")
return 0
else:
pass
# kv中不存在HAASEDU_NAME键值对,则通过外围传感器进行判断
# 读取QMI8610传感器device identifier register的值
self.i2cDev = I2C()
self.i2cDev.open("qmi8610")
buf = bytearray(1)
buf[0] = 0
self.i2cDev.memRead(buf, 0, 8) # register address:0 - FIS device identifier register address
self.i2cDev.close()
if buf[0] == 0xfc:
hwId = 1
else:
# 读取QMI8610传感器chip id register的值
self.i2cDev.open("qmp6988")
buf[0] = 0xD1 # chip id register
self.i2cDev.write(buf)
self.i2cDev.read(buf)
self.i2cDev.close()
if buf[0] == 0x5C:
hwId = 1
else:
# 读取QMC6310传感器chip id register的值
self.i2cDev.open("qmc6310")
buf[0] = 0x00 # chip id register
self.i2cDev.write(buf)
self.i2cDev.read(buf)
self.i2cDev.close()
if buf[0] == 0x80:
hwId = 1
if hwId == 1:
kv.seti("HAASEDU_NAME", 1)
print("HAASEDUK1 hw version is 1.1")
return 1
else:
kv.seti("HAASEDU_NAME", 0)
print("HAASEDUK1 hw version is 1.0")
return 0
# -*- encoding: utf-8 -*-
'''
@File : main.py
@Description: 温湿度上云
@Author : ethan.lcz
@version : 1.0
'''
from aliyunIoT import Device # aliyunIoT组件是连接阿里云物联网平台的组件
import netmgr as nm # Wi-Fi功能所在库
import utime # 延时API所在组件
from driver import I2C # I2C总线驱动库
from driver import GPIO # ESP32和使用GPIO控制LED
from haaseduk1 import HAASEDUK1 # 引入haaseduk1库,目标用于区分K1版本
import ujson # json字串解析库
# 物联网平台连接标志位
iot_connected = False
# 物联网设备实例
device = None
# 三元组信息
productKey = "产品密钥"
deviceName = "设备名称"
deviceSecret = "设备密钥"
# Wi-Fi SSID和Password设置
wifiSsid = "请填写您的路由器名称"
wifiPassword = "请填写您的路由器密码"
# 空调和加湿器状态变量
airconditioner = 0
humidifier = 0
airconditioner_value = 0
humidifier_value = 0
humitureDev = 0
board = HAASEDUK1() # 新建HAASEDUK1对象
hwID = board.getHWID() # 获取开发板ID
i2cObj = I2C()
if (hwID == 1):
from cht8305 import CHT8305 # HaaS EDU K1C上的温湿度传感器采用的是CHT8305
i2cObj.open("cht8305") # 按照board.json中名为"cht8305"的设备节点的配置参数(主设备I2C端口号,从设备地址,总线频率等)初始化I2C类型设备对象
humitureDev = CHT8305(i2cObj) # 初始化CHT8305传感器
# print("cht8305 inited!")
else:
from si7006 import SI7006 # HaaS EDU K1上的温湿度传感器采用的是SI7006
i2cObj.open("si7006") # 按照board.json中名为"si7006"的设备节点的配置参数(主设备I2C端口号,从设备地址,总线频率等)初始化I2C类型设备对象
humitureDev = SI7006(i2cObj) # 初始化SI7006传感器
version = humitureDev.getVer() # 获取SI7006的版本信息
chipID = humitureDev.getID() # 获取SI7006 ID信息
# print("si7006 version:%d, chipID:%d" , version, chipID)
# 等待Wi-Fi成功连接到路由器
def get_wifi_status():
nm.init()
nm.disconnect()
wifi_connected = nm.getStatus()
print("start to connect " , wifiSsid)
nm.connect(wifiSsid, wifiPassword) # 连接到指定的路由器(路由器名称为wifiSsid, 密码为:wifiPassword)
while True :
if wifi_connected == 5: # nm.getStatus()返回5代表连线成功
break
else:
wifi_connected = nm.getStatus() # 获取Wi-Fi连接路由器的状态信息
utime.sleep(0.5)
print("Wi-Fi connected")
print('DeviceIP:' + nm.getInfo()['ip']) # 打印Wi-Fi的IP地址信息
# 通过温湿度传感器读取温湿度信息
def get_temp_humi():
global humitureDev
'''
# 如果需要同时获取温湿度信息,可以呼叫getTempHumidity,实例代码如下:
humniture = humitureDev.getTempHumidity() # 获取温湿度传感器测量到的温湿度值
temperature = humniture[0] # get_temp_humidity返回的字典中的第一个值为温度值
humidity = humniture[1] # get_temp_humidity返回的字典中的第二个值为相对湿度值
'''
temperature = humitureDev.getTemperature() # 获取温度测量结果
# print("The temperature is: %.1f" % temperature)
humidity = humitureDev.getHumidity() # 获取相对湿度测量结果
# print("The humidity is: %d" % humidity)
return temperature, humidity # 返回读取到的温度值和相对湿度值
# 物联网平台连接成功的回调函数
def on_connect(data):
global iot_connected
iot_connected = True
# 设置props 事件接收函数(当云平台向设备下发属性时)
def on_props(request):
global airconditioner, humidifier, airconditioner_value, humidifier_value
# {"airconditioner":1} or {"humidifier":1} or {"airconditioner":1, "humidifier":1}
payload = ujson.loads(request['params'])
# print (payload)
# 获取dict状态字段 注意要验证键存在 否则会抛出异常
if "airconditioner" in payload.keys():
airconditioner_value = payload["airconditioner"]
if (airconditioner_value):
print("打开空调")
else:
print("关闭空调")
if "humidifier" in payload.keys():
humidifier_value = payload["humidifier"]
if (humidifier_value):
print("打开加湿器")
else:
print("关闭加湿器")
# print(airconditioner_value, humidifier_value)
airconditioner.write(airconditioner_value) # 控制空调开关
humidifier.write(humidifier_value) # 控制加湿器开关
# 要将更改后的状态同步上报到云平台
prop = ujson.dumps({
'airconditioner': airconditioner_value,
'humidifier': humidifier_value,
})
upload_data = {'params': prop}
# 上报空调和加湿器属性到云端
device.postProps(upload_data)
# 连接物联网平台
def connect_lk(productKey, deviceName, deviceSecret):
global device, iot_connected
key_info = {
'region': 'cn-shanghai',
'productKey': productKey,
'deviceName': deviceName,
'deviceSecret': deviceSecret,
'keepaliveSec': 60
}
# 将三元组信息设置到iot组件中
device = Device()
# 设定连接到物联网平台的回调函数,如果连接物联网平台成功,则调用on_connect函数
device.on(Device.ON_CONNECT, on_connect)
# 配置收到云端属性控制指令的回调函数,如果收到物联网平台发送的属性控制消息,则调用on_props函数
device.on(Device.ON_PROPS, on_props)
# 启动连接阿里云物联网平台过程
device.connect(key_info)
# 等待设备成功连接到物联网平台
while(True):
if iot_connected:
print('物联网平台连接成功')
break
else:
print('sleep for 1 s')
utime.sleep(1)
# 上传温度信息和湿度信息到物联网平台
def upload_temperature_and_Humidity():
global device
while True:
data = get_temp_humi() # 读取温度信息和湿度信息
# 生成上报到物联网平台的属性值字串
prop = ujson.dumps({
'CurrentTemperature': data[0],
'CurrentHumidity': data[1]
})
print('uploading data: ', prop)
upload_data = {'params': prop}
# 上传温度和湿度信息到物联网平台
device.postProps(upload_data)
utime.sleep(2)
if __name__ == '__main__':
# 硬件初始化
# 初始化 GPIO
airconditioner = GPIO()
humidifier = GPIO()
humidifier.open('led_g') # 加湿器使用board.json中led_g节点定义的GPIO,对应HaaS EDU K1上的绿灯
airconditioner.open('led_b') # 空调使用board.json中led_b节点定义的GPIO,对应HaaS EDU K1上的蓝灯
# 请替换物联网平台申请到的产品和设备信息,可以参考文章:https://blog.csdn.net/HaaSTech/article/details/114360517
# global productKey, deviceName, deviceSecret ,on_request, on_play
get_wifi_status()
connect_lk(productKey, deviceName, deviceSecret)
upload_temperature_and_Humidity()
humitureDev.close()
"""
Copyright (C) 2015-2021 Alibaba Group Holding Limited
MicroPython's drive for SI7006
Author: HaaS
Date: 2021/09/09
"""
from driver import I2C
from utime import sleep_ms
# The commands provided by SI7006
Si7006_MEAS_REL_HUMIDITY_MASTER_MODE = 0xE5
Si7006_MEAS_REL_HUMIDITY_NO_MASTER_MODE = 0xF5
Si7006_MEAS_TEMP_MASTER_MODE = 0xE3
Si7006_MEAS_TEMP_NO_MASTER_MODE = 0xF3
Si7006_READ_OLD_TEMP = 0xE0
Si7006_RESET = 0xFE
Si7006_READ_ID_LOW_0 = 0xFA
Si7006_READ_ID_LOW_1 = 0x0F
Si7006_READ_ID_HIGH_0 = 0xFC
Si7006_READ_ID_HIGH_1 = 0xC9
Si7006_READ_Firmware_Revision_0 = 0x84
Si7006_READ_Firmware_Revision_1 = 0xB8
class SI7006Error(Exception):
def __init__(self, value=0, msg="si7006 common error"):
self.value = value
self.msg = msg
def __str__(self):
return "Error code:%d, Error message: %s" % (self.value, str(self.msg))
__repr__ = __str__
class SI7006(object):
"""
This class implements SI7006 chip's functions.
"""
# i2cDev should be an I2C object and it should be opened before __init__ is called
def __init__(self, i2cDev):
self._i2cDev = None
if not isinstance(i2cDev, I2C):
raise ValueError("parameter is not an I2C object")
# make SI7006's internal object points to i2cDev
self._i2cDev = i2cDev
def getVer(self):
"""
Get the firmware version of the chip.
"""
# make sure SI7006's internal object is valid before I2C operation
if self._i2cDev is None:
raise ValueError("invalid I2C object")
# send read firmware version command to SI7006
reg = bytearray([Si7006_READ_Firmware_Revision_0, Si7006_READ_Firmware_Revision_1])
self._i2cDev.write(reg)
sleep_ms(30)
version = bytearray(1)
# read the version info back
self._i2cDev.read(version)
return version[0]
def getID(self):
"""Get the chip ID."""
# make sure SI7006's internal object is valid before I2C operation
if self._i2cDev is None:
raise ValueError("invalid I2C object")
# send read chip id‘s lower part command to SI7006
reg = bytearray([Si7006_READ_ID_LOW_0, Si7006_READ_ID_LOW_1])
self._i2cDev.write(reg)
sleep_ms(30)
id_buf_low = bytearray(4)
# read the id info back
self._i2cDev.read(id_buf_low)
# send read chip id‘s higher part command to SI7006
reg = bytearray([Si7006_READ_ID_HIGH_0, Si7006_READ_ID_HIGH_1])
id_buf_high = bytearray(4)
self._i2cDev.read(id_buf_high)
return id_buf_low + id_buf_high
def getTemperature(self):
"""Get temperature."""
# make sure SI7006's internal object is valid before I2C operation
if self._i2cDev is None:
raise ValueError("invalid I2C object")
# send measure temperature command to SI7006
reg = bytearray([Si7006_MEAS_TEMP_NO_MASTER_MODE])
self._i2cDev.write(reg)
# wait for 30ms to wait for the measure finish according to SI7006's datasheet
sleep_ms(30)
readData = bytearray(2)
# read the temperature measure result
self._i2cDev.read(readData)
value = (readData[0] << 8 | readData[1])
# convert to actual temperature
if (value & 0xFFFC):
temperature = (175.72 * value) / 65536.0 - 46.85
return round(temperature, 1)
else:
raise SI7006Error("failed to get temperature.")
def getHumidity(self):
"""Get humidity."""
# make sure SI7006's internal object is valid before I2C operation
if self._i2cDev is None:
raise ValueError("invalid I2C object")
# send measure humidity command to SI7006
reg = bytearray([Si7006_MEAS_REL_HUMIDITY_NO_MASTER_MODE])
self._i2cDev.write(reg)
# wait for 30ms to wait for the measure finish according to SI7006's datasheet
sleep_ms(30)
readData = bytearray(2)
self._i2cDev.read(readData)
value = (readData[0] << 8) | readData[1]
# convert to actual humidity
if (value & 0xFFFE):
humidity = (125.0 * value) / 65535.0 - 6.0
return round(humidity)
else:
raise SI7006Error("failed to get humidity.")
def getTempHumidity(self):
"""Get temperature and humidity."""
# make sure SI7006's internal object is valid before I2C operation
if self._i2cDev is None:
raise ValueError("invalid I2C object")
# read tempperature and humidity in sequence
temphumidity = [0, 0]
temphumidity[0] = self.getTemperature()
temphumidity[1] = self.getHumidity()
return temphumidity
if __name__ == "__main__":
'''
The below i2c configuration is needed in your board.json.
"si7006": {
"type": "I2C",
"port": 1,
"addrWidth": 7,
"freq": 400000,
"mode": "master",
"devAddr": 64
}
'''
print("Testing si7006 ...")
i2cDev = I2C()
i2cDev.open("si7006")
si7006Dev = SI7006(i2cDev)
version = si7006Dev.getVer()
print("si7006 version is: %d" % version)
chipID = si7006Dev.getID()
print("si7006 chip id is:", chipID)
temperature = si7006Dev.getTemperature()
print("The temperature is: %f" % temperature)
humidity = si7006Dev.getHumidity()
print("The humidity is: %d" % humidity)
i2cDev.close()
print("Test si7006 done!")
# 燃气检测系统
&emsp;&emsp;
下图是本案例除硬件连线外的3步导学,每个步骤中实现的功能请参考图中的说明。
<div align="center">
<img src=./../../../images/2_燃气检测_步骤概述.jpg width=70%/>
</div>
## 1、简介
&emsp;&emsp;
天然气是国人目前家庭里边烹饪和取暖所用的主要燃料,其主要成分是甲烷(CH4),同时还含有少量的丙烷、乙烷、丁烷等其他少量气体。天然气本身无色无味,密度比空气小。如果发生天然气泄漏,其在空气中浓度达到5%~15%时,遇到明火就会爆炸。如今天然气普及,每年由于因使用不当、人为因素(忘记关闭开关)造成的燃气泄漏爆炸事故接连不断。
&emsp;&emsp;
本节内容就以此为背景教开发者如何一步一步打造一个燃气泄漏检测系统。
### 1.1、准备
&emsp;&emsp;
完成本节内容的学习需要如下硬件:
1. [HaaS EDU K1开发板](https://haas.iot.aliyun.com/solution/detail/hardware?versionId=800C5AB3B8A4A88800000001&dataId=800C5AB3B8A4A888) 一台
2. [MQ2燃气传感器](https://haas.iot.aliyun.com/solution/detail/hardware?versionId=800CEE990B95992200000002&dataId=800CEE990B959922) 一个
3. 连接线若干
&emsp;&emsp;
硬件连线图如下图所示:
<div align="center">
<img src=./../../../images/2_燃气检测_HaaS_EDU_K1_MQ2连线.png width=70%/>
</div>
<br>
## 2、物联网平台开发
&emsp;&emsp;
对于第一次使用物联网平台的读者,需要开通实例以使用物联网平台的功能。这里可以使用免费的公共实例进行开发。
&emsp;&emsp;
[物联网平台](https://iot.console.aliyun.com/lk/summary/new)中,左上角选择“华东2-上海”,点击“公共实例”,即可开通。开通后点击“公共实例”,即可进入[控制台](https://iot.console.aliyun.com/lk/summary/new)进行产品创建。
<div align="center">
<img src=./../../../images/5_3_开通公共实例.png
width=100%/>
</div>
&emsp;&emsp;
开通物联网平台功能之后,需要完成下面的3个步骤完成云端设备的创建:
1. 创建云端产品
2. 创建产品属性(物模型)
3. 创建云端设备(获取三元组)
### 2.1、创建云端产品
&emsp;&emsp;
点击上图中的“公共实例”,即可进入[控制台](https://iot.console.aliyun.com/lk/summary/new)进行产品创建。然后,点击创建产品按钮,如下图所示。
<div align="center">
<img src=./../../../images/1_创建产品.png
width=100%/>
</div>
&emsp;&emsp;
在新建产品设定页面按照下图所示,设定“产品名称”,选择所属的“自定义品类”(自定义品类的物模型为空,需要自己创建,也可以通过导入外部物模型的方式导入),节点类型选择“直连设备”,联网方式选择“Wi-Fi”,数据格式选择“ICA标准数据格式”,检验类型和认证方式选择默认设定即可。还可以根据开发者自己的需求在“产品描述”页面添加针对此产品的描述。
<div align="center">
<img src=./../../../images/2_新建燃气检测设备.png
width=100%/>
</div>
&emsp;&emsp;
选择之后,点击“确认”按钮,即可完成产品创建。返回“产品”页面之后可以看到产品类表中会出现刚刚创建的“燃气检测系统”的产品,如下图所示。
<div align="center">
<img src=./../../../images/2_燃气检测系统_产品列表页.png width=100%/>
</div>
<br>
### 2.2、创建产品属性(物模型)
&emsp;&emsp;
点击上图中的“查看”按钮,即可看到产品信息,Topic列表,功能定义,数据解析等跟产品相关功能的设定。点开“功能定义”标签页,可以看到设备物模型定义。
<div align="center">
<img src=./../../../images/2_燃气检测系统_产品详情页面.png width=100%/>
</div>
&emsp;&emsp;
标识符是设备端上报设备属性状态的消息中需要使用的标识符,并且只有在设备上报的属性内容符合“数据定义”中的数据取值范围的时候才会被物联网平台记录,否则会被物联网平台认定为非法属性而过滤掉。
&emsp;&emsp;
本节我们选择导入物模型的方式来创建此系统需要的物模型信息,点击上图中的”编辑草稿“按钮。然后按照下图的步骤,选择本地文件导入[燃气检测系统物模型](./link_platform/gas_detector_model.zip)
<div align="center">
<img src=./../../../images/2_燃气检测系统_快速导入物模型.png width=100%/>
</div>
&emsp;&emsp;
物模型导入成功后可以看到网页出现了我们刚刚导入的物模型属性。其中alarmLight代表的是燃气灯的报警状态,1处于报警状态,0代表没有报警;gasVoltage代表MQ2燃气传感器检测到的电压值。
<div align="center">
<img src=./../../../images/2_燃气检测系统_发布物模型.png width=100%/>
</div>
&emsp;&emsp;
产品及其物模型创建完成后就可以创建这个产品的设备了。
<br>
### 2.3、创建云端设备(获取三元组)
&emsp;&emsp;
在产品列表页面中,点击”燃气检测系统“后的“管理设备”,就会进到设备管理页面。
<div align="center">
<img src=./../../../images/2_燃气检测系统_产品页_管理设备.png width=100%/>
</div>
&emsp;&emsp;
在“设备”页面点击“添加设备”按钮,如下图所示。
<div align="center">
<img src=./../../../images/2_燃气检测系统_添加设备入口.png width=100%/>
</div>
&emsp;&emsp;
在“添加设备”页面中设定“deviceName”,这里开发者可以自己填入自己想设定的设备名称,也可以不填任何内容让系统自动生成设备名称,如下图所示。
<div align="center">
<img src=./../../../images/1_添加设备.png width=40%/>
</div>
&emsp;&emsp;
设备添加完成后,点击“前往查看”按钮,就可以看到此设备端详细信息了。
<div align="center">
<img src=./../../../images/1_完成添加设备.png width=40%/>
</div>
&emsp;&emsp;
设备信息中有两个信息需要和设备端开发相匹配:
1. 三元组
2. 物模型属性信息
<div align="center">
<img src=./../../../images/2_燃气检测系统设备详情.png width=100%/>
</div>
<br>
#### 2.4.1、**获取设备三元组**
&emsp;&emsp;
如上图所示,点击“查看”按钮,就可以看到设备的三元组信息,三元组是物联网设备端和物联网云端设备相关联的唯一标识符,在设备端连接云端的时候会使用三元组信息和云端进行鉴权,鉴权通过之后云端会认为设备已激活并上线。
<div align="center">
<img src=./../../../images/2_燃气检测系统_设备三元组.png width=50%/>
</div>
<br>
#### 2.4.2、**查看设备属性信息**
&emsp;&emsp;
设备详情信息页中的“物模型数据”标签页中可以看到设备的所有属性信息、设备时间上报情况及设备服务调用情况,如下图所示。待物联网设备按照设备属性对应的标识符上报设备属性的时候,本图片中的“燃气检测电压值“,”报警灯“等属性值就会显示设备最新的属性信息。
<div align="center">
<img src=./../../../images/2_燃气检测系统_设备物模型数据.png width=100%/>
</div>
<br>
> 创建产品和设备的过程是按照面向对象的思想进行设计的,其中创建产品可以看成是新建一个类,其中的物模型则是类的对象,创建设备则是进行类的实例化。
<br>
## 3、设备端开发
### 3.1、开发环境
&emsp;&emsp;
在进行下一步之前请确保HaaS EDU K1开发环境已经搭建完毕。详情请参考[HaaS EDU K1开发环境](../../../startup/HaaS_EDU_K1_startup.md)的说明。
### 3.2、创建解决方案
&emsp;&emsp;
如下图所示,在Haas Studio中创建项目。先选择左侧的“开发板型号”再从右侧的案例中选择“燃气检测系统”案例点击“立即创建”即可。
<div align="center">
<img src=./../../../images/HaaS_Studio_创建工程示范.png width=100%/>
</div>
<br>
1. **修改路由器名称及密码**
&emsp;&emsp;
修改工程里main.py中wifiSsid和wifiPassword的值为读者实际要连接的路由器的名称及密码(请注意名称和密码都需要放在""符号中间)。
```python
# Wi-Fi SSID和Password设置
wifi_ssid = "请填写您的路由器名称"
wifi_password = "请填写您的路由器密码"
```
&emsp;&emsp;
修改完成之后get_wifi_status函数中的代码就会连接读者自己设定的路由器。
2. **修改设备端三元组**
&emsp;&emsp;
修改gas_detector工程里main.py中productKey、deviceName和deviceSecret的值为读者创建的物联网设备的三元组信息,如下所示:
```python
# 三元组信息
productKey = "产品密钥"
deviceName = "设备名称"
deviceSecret = "设备密钥"
```
3. **修改设备端上报数据所用标识符**
&emsp;&emsp;
gas_detector工程里main.py中下面的代码实现的是上传燃气检测结果和报警灯状态到云端的功能。其中gasVoltate便是燃气检测结果上报云端所用的标识符。代码逻辑及功能说明如下:
```python
# 上传燃气传感器检测电压信息和报警信息到物联网平台
def upload_gas_detector_state():
global device, alarmOn, mq2Dev
gasVoltage = 0
# 无限循环
while True:
gasVoltage = mq2Dev.getVoltage() # 呼叫mq2传感器库提供的getVoltage函数获取电压值,单位:mV
print(gasVoltage)
data = ujson.dumps({
'gasVoltage': gasVoltage,
'alarmLight': alarmOn
})
# 生成上报到物联网平台的属性值字串,此处的属性标识符"gasVoltage"和"alarmLight"必须和物联网平台的属性一致
# "gasVoltage" - 代表燃气传感器测量到的电压值
# "alarmLight" - 代表报警灯的当前状态
uploadData = {'params': data}
# 上传燃气传感器检测电压信息和报警信息到物联网平台
device.postProps(uploadData)
# 每2秒钟上报一次
utime.sleep(2)
```
&emsp;&emsp;
确保这两个标识符和物联网产品的物模型中属性标识符是一样的,如下图所示:
<div align="center">
<img src=./../../../images/1_ESP32_gasVoltage_属性标识符.png
width=70%/>
</div>
<br>
## 4、运行结果
### 4.1、本地查看
&emsp;&emsp;
推送此脚本到HaaS EDU K1之后,通过PC端串口调试工具可以看到HaaS EDU K1打印如下日志。其中:
* “物联网平台连接成功” 代表成功连接到物联网平台
* "reporting data to cloud:"之后跟着的为设备端向云端发布的燃气检测结果信息,gasVoltage后 的值为燃气传感器检测的电压值,单位:mV
* "{"alarmLight":x}"则是收到云端回复的控制报警灯亮灭的控制指令,1代表打开报警灯,此时接在HaaS EDU K1的红色LED应该亮起;0代表关闭报警灯,此时接在HaaS EDU K1的红色LED应该熄灭。(注意:这条log的及报警灯亮灭的行为是云端下发控制指令触发,只有在完成下一步“物联网应用开发”小节之后才会打印)
```log
...
Welcome to AliOS Things
dev /dev/wifi0 is already add
==== python execute from /data/pyamp/main.py ====
Welcome to MicroPython
...
Wi-Fi connected
DeviceIP:192.168.0.107
sleep for 1 s
...
success to establish tcp, fd=4
物联网平台连接成功
reporting data to cloud: {"gasVoltage": 381, "alarmLight": 0}
{"alarmLight":0}
结束报警
reporting data to cloud: {"gasVoltage": 382, "alarmLight": 0}
{"alarmLight":0}
结束报警
reporting data to cloud: {"gasVoltage": 1200, "alarmLight": 1}
{"alarmLight":1}
开始报警
...
```
### 4.2、物联网平台端设备信息查看
&emsp;&emsp;
物联网设备的系统启动成功并连接到物联网平台之后,物联网平台上对应的设备状态会从”未激活状态“变为”上线“,在物模型数据标签页上会显示设备上报到物联网平台的属性值。
<div align="center">
<img src=./../../../images/1_燃气检测系统_设备状态及属性.png width=100%/>
</div>
&emsp;&emsp;
此时如果开发板周围的燃气浓度发生变化,物联网平台的物模型数据会更新为设备上报的最新的属性值。
<br>
### 4.3、物联网平台控制报警灯状态
<br>
&emsp;&emsp;
物联网设备上线之后,可以通过”监控运维“中的"在线调试"功能进行调试,详细操作步骤见下图:
<div align="center">
<img src=./../../../images/1_燃气检测系统_物联网平台在线调试功能.png width=100%/>
</div>
&emsp;&emsp;
此产品的物模型属性中,"燃气检测电压值"设置的是只读,也就是说智能从设备端读取,不支持设置此状态到设备端,所以点开"燃气超标标志"后面的”调试“之后,里边只有获取的选项。”报警灯“设置的是可读可写,所以点开”报警灯“后面的”调试“之后,里边有”获取“、”设置”和“设置期望值”三个选项。
这里可以选择打开报警灯之后点击“设置”进行报警灯功能的调试。
<div align="center">
<img src=./../../../images/1_燃气检测系统_云端打开报警灯.png width=100%/>
</div>
&emsp;&emsp;
此时查看设备端LED是否已经打开,打开成功则代表控制报警灯成功。
<br>
<br>
## 5、物联网应用开发
&emsp;&emsp;
通过本节课程的学习,读者可以搭建一个在手机上面可以随时随地查看燃气监控系统状态的应用。
<br>
### 5.1、燃气监控系统
&emsp;&emsp;
下图是一个典型的物联网应用程序开发的流程图,接下来本节就按照这个流程介绍如何完成燃气监控系统移动端应用程序的开发。
<div align="center">
<img src=./../../../images/1_物联网应用开发的一般流程.png width=80%/>
</div>
<br>
### 5.2、**新建“普通项目”**
&emsp;&emsp;
打开[IoT Studio官网](https://studio.iot.aliyun.com/),在项目管理中创建一个空白项目,如下图所示,将此项目命名为“燃气监控报警系统”。
<div align="center">
<img src=./../../../images/2_燃气监控报警系统_创建IoTStudio项目.png width=80%/>
</div>
<br>
### 5.3、**新建“移动应用”**
&emsp;&emsp;
新建“普通项目”之后,在新项目的首页新建一个移动应用,命名为“燃气实时监控”。
<div align="center">
<img src=./../../../images/2_燃气监控系统_移动应用创建.png width=80%/>
</div>
创建完移动应用之后系统会自动跳转到”新建页面“,这里选择”创建页面“完成应用页面的创建,如下图所示。
<div align="center">
<img src=./../../../images/2_燃气监控系统_移动应用_新建页面.png width=80%/>
</div>
&emsp;&emsp;
移动端应用创建成功后会进入到应用界面设计页面。
<div align="center">
<img src=./../../../images/2_燃气监控系统_移动应用_页面编辑页.png width=80%/>
</div>
&emsp;&emsp;
点击上图红框中的“组件”按钮图标,就可以看到可用的组件列表。各组件的说明请参考[IoT Studio组件说明](https://help.aliyun.com/document_detail/125196.html)
<br>
### 5.4、**页面设计**
&emsp;&emsp;
这里我们用到3个组件:
* 实时曲线
用于显示温湿度的历史变化曲线及实时数据
* 指示灯
显示和控制空调和加湿器的当前状态
* 天气
用于显示目的地天气
&emsp;&emsp;
将三个组件拖到中间的画布区,适当调整组件布局,如下图所示。
<div align="center">
<img src=./../../../images/2_燃气监控系统_实时监控页面设计.png width=80%/>
</div>
<br>
### 5.5、**关联产品和设备**
&emsp;&emsp;
此时回到”燃气监控系统“项目的主页,对产品和设备进行关联,如下图所示:
<div align="center">
<img src=./../../../images/2_燃气监控系统_关联产品和设备.png width=80%/>
</div>
&emsp;&emsp;
关联产品的过程如下:
<div align="center">
<img src=./../../../images/2_燃气监控系统_关联产品.png width=80%/>
</div>
&emsp;&emsp;
关联设备的过程如下:
<div align="center">
<img src=./../../../images/2_燃气监控系统_关联设备.png width=80%/>
</div>
&emsp;&emsp;
产品和设备关联完毕之后,就可以将把组件和设备的属性关联起来了。
<br>
### 5.6、**关联数据源**
&emsp;&emsp;
关联数据源分为如下3个步骤,每个步骤的截图如下:
* 关联产品
* 关联设备
* 关联属性
&emsp;&emsp;
具体操作步骤如下:
1. 选中”开关“组件,点击右侧的“配置数据源”。
<div align="center">
<img src=./../../../images/2_燃气监控系统_指示灯_配置数据源.png width=80%/>
</div>
1. 选择目标产品
<div align="center">
<img src=./../../../images/2_燃气监控系统_指示灯_选择产品.png width=80%/>
</div>
3. 选择目标设备
<div align="center">
<img src=./../../../images/2_燃气监控系统_开关_配置数据源_设备.png width=80%/>
</div>
4. 选择“报警灯”属性
<div align="center">
<img src=./../../../images/2_燃气监控系统_指示灯_配置数据源_报警灯.png width=80%/>
</div>
&emsp;&emsp;
选择好产品、设备和属性之后,需要修改指示灯大小及”开始报警“/”停止报警“时指示灯的颜色,如下图所示。
<div align="center">
<img src=./../../../images/2_燃气监控系统_指示灯_配置数据源_报警灯颜色.png width=80%/>
</div>
&emsp;&emsp;
同样的方式为”实时曲线“设置为目标设备的”燃气检测电压“,并显示最近半小时的数据,如下图所示。
<div align="center">
<img src=./../../../images/2_燃气监控系统_实时曲线设定.png width=80%/>
</div>
&emsp;&emsp;
选中”天气“组件,可以设定要显示的是什么位置的天气,本节课程中显示默认地址即可,读者也可以设定自己所在地的信息,如下图所示。
<div align="center">
<img src=./../../../images/2_燃气监控系统_位置信息设定.png width=80%/>
</div>
<br>
### 5.7、**业务逻辑开发**
&emsp;&emsp;
业务逻辑的主要目的是为了让用户设定物联网设备端的行为逻辑,常规物联网系统都是在设备端固化行为逻辑,出厂之后如果需要修改设备的行为,则需要进行OTA升级。本节课程则向读者介绍如何通过IoT Studio完成业务逻辑的开发。
&emsp;&emsp;
新建一条名为“燃气浓度超过阈值报警”的规则。
<div align="center">
<img src=./../../../images/2_燃气检测系统_创建业务逻辑.png width=80%/>
</div>
&emsp;&emsp;
系统会自动进入到业务逻辑编辑页面,如下图所示,点击左侧的“节点”按钮,则可以看到所有可用的节点选项。右侧红框是如何设计一个业务逻辑的介绍。
<div align="center">
<img src=./../../../images/2_燃气监控系统_高浓度报警.png width=80%/>
</div>
1. 选择目标节点
&emsp;&emsp;
此逻辑需要通过“燃气检测设备”上报的“燃气测量电压”当超过阈值的时候打开报警灯,否则关闭报警灯。所以需要如下4个节点:
* 设备触发节点
* 条件判断节点
* 开启报警灯节点
* 关闭报警灯节点
&emsp;&emsp;
分别从左侧拖动“设备触发”,“条件判断”和2个“燃气检测系统”4个节点到右侧的业务 逻辑编辑框。
<div align="center">
<img src=./../../../images/2_燃气监控系统_节点列表.png width=80%/>
</div>
2. 建立节点间的关联关系
&emsp;&emsp;
按照预设的逻辑建立,如下图所示(在节点的一侧按下鼠标拖动到另一节点的一侧即可建立连线关系)。
<div align="center">
<img src=./../../../images/2_燃气监控系统_节点逻辑关系建立.png width=80%/>
</div>
3. 业务逻辑编辑
* 设备触发节点
&emsp;&emsp;
此设备触发选择“燃气检测设备”的“燃气检测电压”属性即可,如下图所示(和前面“组件”设定类似,同样是鼠标选中第节点,在右侧的配置选项中进行配置)。
<div align="center">
<img src=./../../../images/2_燃气监控系统_设备触发节点配置.png width=80%/>
</div>
* 条件判断节点
&emsp;&emsp;
此处我们设定为当传感器测量到的电压值高于900mV则开始报警。设定步骤如下。
<div align="center">
<img src=./../../../images/2_燃气监控系统_比较节点配置.png width=80%/>
</div>
* 设备节点行为设定
&emsp;&emsp;
分别为设备节点设定开启报警灯和关闭报警灯的行为,如下图所示。
<div align="center">
<img src=./../../../images/2_燃气监控系统_设备节点行为设定.png width=80%/>
</div>
* 业务逻辑保存和部署
&emsp;&emsp;
依此点击右上角的“保存”和“部署”按钮,即可将此业务逻辑设定完毕。
<br>
<br>
### 5.8、**预览和发布上线**
&emsp;&emsp;
业务逻辑设定完毕之后,可以在“燃气监控报警系统”页面编辑页面点击“预览”按钮进行预览,如下图所示。
<div align="center">
<img src=./../../../images/2_燃气监控系统_预览.png width=80%/>
</div>
&emsp;&emsp;
在发布之前可以点击上图的“预览”查看应用的实际运行效果。实际运行效果如下所示,同时可以扫描二维码在手机上面查看实际运行效果。
<div align="center">
<img src=./../../../images/2_燃气监控报警系统效果.gif width=80%/>
</div>
&emsp;&emsp;
此时查看设备上面的报警灯的状态会同步和移动端应用的报警灯状态同步显示。
<br>
&emsp;&emsp;
到此为止,燃气检测系统的案例就已经完成了。如果想要学习燃气检测系统更详细的操作步骤,请参考“[燃气检测系统详解](https://gitee.com/haasedu/haasedu/blob/release_2.0/2-%E6%99%BA%E8%83%BD%E7%94%9F%E6%B4%BB/%E5%9C%BA%E6%99%AF1-%E5%AE%88%E6%8A%A4%E5%AE%B6%E5%BA%AD%E5%81%A5%E5%BA%B7/%E7%87%83%E6%B0%94%E6%B3%84%E6%BC%8F%E6%A3%80%E6%B5%8B/README.md)”中的说明。
{
"name": "haasedu",
"version": "1.0.0",
"io": {
"mq2": {
"type": "ADC",
"port": 2,
"sampling": 12000000
},
"led": {
"type": "GPIO",
"port": 36,
"dir": "output",
"pull": "pullup"
},
"led_r": {
"type": "GPIO",
"port": 36,
"dir": "output",
"pull": "pullup"
},
"ap3216c": {
"type": "I2C",
"port": 1,
"addrWidth": 7,
"freq": 100000,
"mode": "master",
"devAddr": 30
},
"mpu6050": {
"type": "I2C",
"port": 1,
"addrWidth": 7,
"freq": 100000,
"mode": "master",
"devAddr": 105
},
"si7006": {
"type": "I2C",
"port": 1,
"addrWidth": 7,
"freq": 400000,
"mode": "master",
"devAddr": 64
},
"spl06": {
"type": "I2C",
"port": 1,
"addrWidth": 7,
"freq": 400000,
"mode": "master",
"devAddr": 119
},
"qmc5883": {
"type": "I2C",
"port": 1,
"addrWidth": 7,
"freq": 400000,
"mode": "master",
"devAddr": 13
},
"GPIO2": {
"type": "GPIO",
"port": 2,
"dir": "irq",
"pull": "pullup",
"intMode": "rising"
},
"GPIO3": {
"type": "GPIO",
"port": 3,
"dir": "output",
"pull": "pullup"
},
"GPIO4": {
"type": "GPIO",
"port": 4,
"dir": "output",
"pull": "pullup"
},
"GPIO5": {
"type": "GPIO",
"port": 5,
"dir": "output",
"pull": "pullup"
},
"GPIO6": {
"type": "GPIO",
"port": 6,
"dir": "output",
"pull": "pullup"
},
"GPIO7": {
"type": "GPIO",
"port": 7,
"dir": "output",
"pull": "pullup"
},
"GPIO19": {
"type": "GPIO",
"port": 19,
"dir": "output",
"pull": "pullup"
},
"GPIO22": {
"type": "GPIO",
"port": 22,
"dir": "output",
"pull": "pullup"
},
"KEY_1": {
"type": "GPIO",
"port": 23,
"dir": "irq",
"pull": "pullup",
"intMode": "rising"
},
"KEY_2": {
"type": "GPIO",
"port": 20,
"dir": "irq",
"pull": "pullup",
"intMode": "rising"
},
"KEY_3": {
"type": "GPIO",
"port": 21,
"dir": "irq",
"pull": "pullup",
"intMode": "rising"
},
"KEY_4": {
"type": "GPIO",
"port": 26,
"dir": "irq",
"pull": "pullup",
"intMode": "rising"
},
"I2C1": {
"type": "I2C",
"port": 1,
"addrWidth": 7,
"freq": 100000,
"mode": "master",
"devAddr": 60
},
"led_g": {
"type": "GPIO",
"port": 35,
"dir": "output",
"pull": "pullup"
},
"led_b": {
"type": "GPIO",
"port": 34,
"dir": "output",
"pull": "pullup"
},
"SPI0": {
"type": "SPI",
"port": 0,
"mode": "master",
"freq": 2000000
},
"oled_spi": {
"type": "SPI",
"port": 1,
"mode": "master",
"freq": 26000000
},
"oled_dc": {
"type": "GPIO",
"port": 28,
"dir": "output",
"pull": "pullup"
},
"oled_res": {
"type": "GPIO",
"port": 30,
"dir": "output",
"pull": "pullup"
},
"serial2": {
"type": "UART",
"port": 2,
"dataWidth": 8,
"baudRate": 115200,
"stopBits": 1,
"flowControl": "disable",
"parity": "none"
}
},
"debugLevel": "ERROR",
"repl": "disable"
}
\ No newline at end of file
# -*- encoding: utf-8 -*-
'''
@File : main.py
@Description: 燃气检测和报警灯控制
@Author : ethan.lcz
@version : 1.0
'''
from aliyunIoT import Device # aliyunIoT组件是连接阿里云物联网平台的组件
import utime # 延时API所在组件
import ujson # json字串解析库
from driver import GPIO # 开发板 LED使用GPIO进行控制
from driver import ADC # ADC类,通过微处理器的ADC模块读取ADC通道输入电压
import netmgr as nm # netmgr是Wi-Fi网络连接的组件
import mq2 # 引入MQ2传感器驱动库
import ujson # json库
adcObj = 0 # ADC类型的外设对象
mq2Dev = 0 # MQ2燃气传感器对象
alarmLed = 0 # 报警LED对象
alarmOn = 0 # 记录报警状态
# 物联网平台连接标志位
iot_connected = False
# Wi-Fi SSID和Password设置
wifiSsid = "请填写您的路由器名称"
wifiPassword = "请填写您的路由器密码"
# 三元组信息
productKey = "产品密钥"
deviceName = "设备名称"
deviceSecret = "设备密钥"
wlan = None
# 物联网设备实例
device = None
# 等待Wi-Fi成功连接到路由器
def get_wifi_status():
nm.init()
nm.disconnect()
wifi_connected = nm.getStatus()
print("start to connect " + wifiSsid)
nm.connect(wifiSsid, wifiPassword) # 连接到指定的路由器(路由器名称为wifiSsid, 密码为:wifiPassword)
while True :
if wifi_connected == 5: # nm.getStatus()返回5代表连线成功
break
else:
wifi_connected = nm.getStatus() # 获取Wi-Fi连接路由器的状态信息
utime.sleep(0.5)
print("wifi_connected: " + wifi_connected)
print("Wi-Fi connected")
print('DeviceIP:' + nm.getInfo()['ip']) # 打印Wi-Fi的IP地址信息
# 设置props 事件接收函数(当云平台向设备下发属性时)
def on_props(request):
global alarmOn
# print(request)
# {"alarmLight":1} or {"alarmLight":0}
payload = ujson.loads(request['params'])
# 获取dict状态字段 注意要验证键存在 否则会抛出异常
if "alarmLight" in payload.keys():
print(payload)
alarmOn = payload["alarmLight"]
if (alarmOn):
print("开始报警")
else:
print("结束报警")
# 根据云端设置的报警灯状态改变本地LED状态
led_control(alarmOn)
# 要将更改后的状态同步上报到云平台
data = ujson.dumps({'alarmLight': alarmOn,})
uploadData = { 'params': data }
# 上报本地报警灯状态到云端
device.postProps(uploadData)
# 物联网平台连接成功的回调函数
def on_connect(data):
global iot_connected
iot_connected = True
def connect_lk(productKey, deviceName, deviceSecret):
global device, iot_connected
key_info = {
'region': 'cn-shanghai',
'productKey': productKey,
'deviceName': deviceName,
'deviceSecret': deviceSecret,
'keepaliveSec': 60
}
# 将三元组信息设置到iot组件中
device = Device()
# 设定连接到物联网平台的回调函数,如果连接物联网平台成功,则调用on_connect函数
device.on(Device.ON_CONNECT, on_connect)
# 配置收到云端属性控制指令的回调函数,如果收到物联网平台发送的属性控制消息,则调用on_props函数
device.on(Device.ON_PROPS, on_props)
# 启动连接阿里云物联网平台过程
device.connect(key_info)
# 等待设备成功连接到物联网平台
while(True):
if iot_connected:
print('物联网平台连接成功')
break
else:
print('sleep for 1 s')
utime.sleep(1)
def led_control(on):
global alarmLed
if on == 0:
alarmLed.write(0) # GPIO写入0,执行灭灯操作
else:
alarmLed.write(1) # GPIO写入 1,执行亮灯报警动作
# 上传燃气传感器测量结果和报警灯状态到物联网平台
def upload_gas_detector_state():
global device, alarmOn, mq2Dev
gasVoltage = 0
# 无限循环
while True:
gasVoltage = mq2Dev.getVoltage() # 呼叫mq2传感器库提供的getVoltage函数获取电压值,单位:mV
data = ujson.dumps({
'gasVoltage': gasVoltage,
'alarmLight': alarmOn
})
# 生成上报到物联网平台的属性值字串,此处的属性标识符"gasVoltage"和"alarmLight"必须和物联网平台的属性一致
# "gasVoltage" - 代表燃气传感器测量到的电压值
# "alarmLight" - 代表报警灯的当前状态
print('reporting data to cloud:', data)
uploadData = {'params': data}
# 上传燃气传感器测量结果和报警灯状态到物联网平台
device.postProps(uploadData)
# 每2秒钟上报一次
utime.sleep(2)
if __name__ == '__main__':
# 硬件初始化
# 初始化 ADC
adcObj = ADC() # ADC通道对象
adcObj.open("mq2") # 按照board.json中名为"mq2"节点的配置初始化ADC设备对象
mq2Dev = mq2.MQ2(adcObj) # 初始化MQ2设备对象
# 初始化红色LED所连接GPIO
alarmLed = GPIO()
alarmLed.open('led') # LED报警灯使用board.json中名为led的节点中定义的GPIO进行控制
led_control(alarmOn) # 关闭报警灯
# 请替换物联网平台申请到的产品和设备信息,可以参考文章:https://blog.csdn.net/HaaSTech/article/details/114360517
# global productKey, deviceName, deviceSecret ,on_request, on_play
get_wifi_status()
connect_lk(productKey, deviceName, deviceSecret)
upload_gas_detector_state()
adcObj.close()
alarmLed.close()
from driver import ADC
class MQ2(object):
def __init__(self, adcObj):
self.adcObj = None
if not isinstance(adcObj, ADC):
raise ValueError("parameter is not an ADC object")
self.adcObj = adcObj
def getVoltage(self):
if self.adcObj is None:
raise ValueError("invalid ADC object")
value = self.adcObj.readVoltage()
return value
# 甲醛浓度检测系统
&emsp;&emsp;
下图是本案例除硬件连线外的2步导学,每个步骤中实现的功能请参考图中的说明。在硬件连线完成之后我们建议您先使用“一分钟上云体验”功能预先体验本案例的实际运行效果。
<div align="center">
<img src=./../../../images/2_甲醛检测系统_步骤概述.jpg width=70%/>
</div>
## 1、简介
&emsp;&emsp;
甲醛(化学式HCHO或CH2O)是一种有特殊刺激气味的气体,对人的眼睛和鼻子有强烈的刺激作用。若空气中甲醛浓度过高,比如新装修的房子、新买的汽车等,可引起中毒反应,严重的可致癌。甲醛是空气中的杀手之一,需要时刻提防。
### 1.1、背景知识
&emsp;&emsp;
本系统的核心在于如何精准的检测家庭中的甲醛浓度。现在市面上有很多种甲醛检测的仪器,大多企业在用的都是通过电化学的方法来检测。
<div align="center">
<img src=./../../../images/2_电化学甲醛传感器.jpeg width=30%/>
</div>
&emsp;&emsp;
电化学甲醛传感器对比其它方式来检测的的抗干扰能力强,响应专一,灵敏度高,测量结果精确,检测下限低,恢复-响应特性佳。
&emsp;&emsp;
本节选用的是HCHO甲醛传感器,可精确测量空气中的甲醛浓度,并能抑制干扰气体,具有稳定性高、抗干扰气体能力强等特点。分辨率高达0.01ppm,支持3.3~6V宽电压输入,具备良好的兼容性,并且使用寿命长达2年。 简单易用的Gravity接口、宽输入电压、支持模拟电压或者串口输出,几乎可兼容所有的主控器。
<div align="center">
<img src=./../../../images/2_DFROBOT甲醛传感器.jpeg width=30%/>
</div>
> 甲醛传感器在使用之前需要预热5分钟以上。
### 1.2、准备
* [HaaS EDU K1开发板](https://haas.iot.aliyun.com/solution/detail/hardware?versionId=800C5AB3B8A4A88800000001&dataId=800C5AB3B8A4A888) 一台
* [HCHO甲醛传感器](https://haas.iot.aliyun.com/solution/detail/hardware?versionId=800C4F2BE3C4C4E800000002&dataId=800C4F2BE3C4C4E8) 一个
* 杜邦连接线若干
&emsp;&emsp;
硬件连线图如下图所示:
<div align="center">
<img src=./../../../images/2_甲醛检测_HaaS_EDU_K1_HCHO_DAC_连线.jpg width=90%/>
</div>
<br>
## 3、物联网平台开发
### 3.1、开通公共实例
&emsp;&emsp;
对于第一次使用物联网平台的读者,需要开通实例以使用物联网平台的功能。这里可以使用免费的公共实例进行开发。
&emsp;&emsp;
[物联网平台](https://iot.console.aliyun.com/lk/summary/new)中,左上角选择“华东2-上海”,点击“公共实例”,即可开通。
<div align="center">
<img src=./../../../images/5_3_开通公共实例.png
width=100%/>
</div>
&emsp;&emsp;
开通物联网平台功能之后,需要完成下面的3个步骤完成云端设备的创建:
1. 创建云端产品
2. 创建产品属性(物模型)
3. 创建云端设备(获取三元组)
### 3.2、创建云端产品
&emsp;&emsp;
点击上图中的“公共实例”,即可进入[控制台](https://iot.console.aliyun.com/lk/summary/new)进行产品创建。然后,点击创建产品按钮,如下图所示。
<div align="center">
<img src=./../../../images/1_创建产品.png
width=100%/>
</div>
&emsp;&emsp;
在新建产品设定页面按照下图所示,设定“产品名称”,选择所属的“自定义品类”(自定义品类的物模型为空,需要自己创建,也可以通过导入外部物模型的方式导入),节点类型选择“直连设备”,联网方式选择“Wi-Fi”,数据格式选择“ICA标准数据格式”,检验类型和认证方式选择默认设定即可。还可以根据开发者自己的需求在“产品描述”页面添加针对此产品的描述。
<div align="center">
<img src=./../../../images/2_新建甲醛检测设备.png width=50%/>
</div>
&emsp;&emsp;
选择之后,点击“确认”按钮,即可完成产品创建。返回“产品”页面之后可以看到产品类表中会出现刚刚创建的“甲醛检测”的产品,如下图所示。
<div align="center">
<img src=./../../../images/2_甲醛检测系统_产品列表页.png width=100%/>
</div>
<br>
### 3.3、创建产品属性(物模型)
&emsp;&emsp;
点击上图中的“查看”按钮,即可看到产品信息,Topic列表,功能定义,数据解析等跟产品相关功能的设定。点开“功能定义”标签页,可以看到设备物模型定义。
<div align="center">
<img src=./../../../images/2_甲醛检测系统_产品详情页面.png width=100%/>
</div>
&emsp;&emsp;
标识符是设备端上报设备属性状态的消息中需要使用的标识符,并且只有在设备上报的属性内容符合“数据定义”中的数据取值范围的时候才会被物联网平台记录,否则会被物联网平台认定为非法属性而过滤掉。
&emsp;&emsp;
本节我们选择创建自定义物模型的方式来创建此系统需要的物模型信息,点击上图中的”编辑草稿“按钮。然后按照下图的步骤,选择添加自定义功能。
<div align="center">
<img src=./../../../images/2_甲醛检测_创建自定义物模型1.png width=100%/>
</div>
然后按照下图选择甲醛浓度属性添加。
<div align="center">
<img src=./../../../images/2_甲醛检测_创建自定义物模型2.png width=50%/>
</div>
&emsp;&emsp;
物模型添加成功之后可以看到网页出现了我们刚刚创建的物模型属性。其中HCHO代表甲醛传感器检测到的浓度值,数据类型为double浮点型,单位为ppm。此时点击“发布”按钮,按照系统提示一步一步进行下去就可以将刚刚创建的物模型属性发布到产品中。
<div align="center">
<img src=./../../../images/2_甲醛检测_发布物模型.png width=100%/>
</div>
&emsp;&emsp;
产品及其物模型创建完成后就可以创建这个产品的设备了。
<br>
### 3.4、创建云端设备(获取三元组)
&emsp;&emsp;
在产品列表页面中,点击”甲醛检测“后的“管理设备”,就会进到设备管理页面。
<div align="center">
<img src=./../../../images/2_甲醛检测_产品页_管理设备.png width=100%/>
</div>
&emsp;&emsp;
在“设备”页面点击“添加设备”按钮,如下图所示。
<div align="center">
<img src=./../../../images/1_添加设备入口.png width=100%/>
</div>
&emsp;&emsp;
在“添加设备”页面中设定“deviceName”,这里开发者可以自己填入自己想设定的设备名称,也可以不填任何内容让系统自动生成设备名称,如下图所示。
<div align="center">
<img src=./../../../images/1_添加设备.png width=40%/>
</div>
&emsp;&emsp;
设备添加完成后,点击“前往查看”按钮,就可以看到此设备端详细信息了。
<div align="center">
<img src=./../../../images/1_完成添加设备.png width=40%/>
</div>
&emsp;&emsp;
设备信息中有两个信息需要和设备端开发相匹配:
1. 三元组(点击下图中的“查看”及可看到三元组信息)
2. 物模型属性信息
<div align="center">
<img src=./../../../images/2_甲醛检测设备详情.png width=100%/>
</div>
<br>
#### 3.4.1、**获取设备三元组**
&emsp;&emsp;
如上图所示,点击“查看”按钮,就可以看到设备的三元组信息(如下图所示),三元组是物联网设备端和物联网云端设备相关联的唯一标识符,在设备端连接云端的时候会使用三元组信息和云端进行鉴权,鉴权通过之后云端会认为设备已激活并上线。
<div align="center">
<img src=./../../../images/1_设备三元组_马赛克.png width=50%/>
</div>
<br>
#### 3.4.2、**查看设备属性信息**
&emsp;&emsp;
设备详情信息页中的“物模型数据”标签页中可以看到设备的所有属性信息、设备事件上报情况及设备服务调用情况,如下图所示。待物联网设备按照设备属性对应的标识符上报设备属性的时候,本图片中的“甲醛浓度“属性值就会显示设备最新的属性信息。
<div align="center">
<img src=./../../../images/2_甲醛检测设备详情.png width=100%/>
</div>
<br>
> 创建产品和设备的过程是按照面向对象的思想进行设计的,其中创建产品可以看成是新建一个类,其中的物模型则是类的对象,创建设备则是进行类的实例化。
<br>
## 3、设备端开发
### 3.1、开发环境
在进行下一步之前请确保HaaS EDU K1开发环境已经搭建完毕。详情请参考[HaaS EDU K1开发环境](../../../startup/HaaS_EDU_K1_startup.md)的说明。
&emsp;&emsp;
该款甲醛传感器支持Uart和ADC两种方式,本案例中采用了ADC模式。
> 使用ADC模式前,请先将拨码开关切换到ADC一端。
&emsp;&emsp;
输出模拟电压(V)与浓度(ppm)是线性关系,0.4V对应0ppm, 2.0V对应5ppm,因此电压与浓度的线性关系图如下图所示:
<div align="center">
<img src=./../../../images/2_甲醛检测_DAC电压曲线.png width=80%/>
</div>
### 3.2、创建解决方案
&emsp;&emsp;
如下图所示,在Haas Studio中创建项目。先选择左侧的“开发板型号”再从右侧的案例中选择“甲醛检测系统”案例点击“立即创建”即可。
<div align="center">
<img src=./../../../images/HaaS_Studio_创建工程示范.png width=100%/>
</div>
<br>
> Python脚本的详细说明请参考脚本内嵌的文字版注释
1. **修改路由器名称及密码**
&emsp;&emsp;
修改main.py中wifiSsid和wifiPassword的值为读者实际要连接的路由器的名称及密码(请注意名称和密码都需要放在""符号中间)。
```python
# Wi-Fi SSID和Password设置
wifiSsid = "请填写您的路由器名称"
wifiPassword = "请填写您的路由器密码"
```
&emsp;&emsp;
修改完成之后get_wifi_status函数中的nm.connect(wifiSsid, wifiPassword) 语句就会连接读者自己设定的路由器。
2. **修改设备端三元组**
&emsp;&emsp;
修改本工程里main.py中productKey、deviceName和deviceSecret的值为读者创建的物联网设备的三元组信息。
```python
# 三元组信息
productKey = "产品密钥"
deviceName = "设备名称"
deviceSecret = "设备密钥"
```
1. **修改设备端上报数据所用标识符**
&emsp;&emsp;
main.py中下面的代码实现的是上传甲醛检测结果到云端的功能。其中HCHO便是甲醛检测结果上报云端所用的标识符。
```python
data = get_hcho_value()
H_str = "Hcho : " + str(round(data,2))+'ppm'
print('Hcho :' + str(round(data,2)) +'ppm')
# "HCHO" - 代表甲醛传感器测量到的浓度值
upload_data = {'params': ujson.dumps({
'HCHO': round(data,2),
})
}
# 上传甲醛浓度信息到物联网平台
device.postProps(upload_data)
# 每2秒钟上报一次
utime.sleep(2)
```
确保这个标识符和物联网产品的物模型中属性标识符是一样的,如下图所示:
<div align="center">
<img src=./../../../images/2_甲醛检测_属性标识符修改.png
width=100%/>
</div>
<br>
## 4、运行结果
### 4.1、本地查看
&emsp;&emsp;
推送此脚本到HaaS EDU K1之后并运行,串口会周期性的打印如下日志,其中:
* Wi-Fi connected 代表Wi-Fi连线成功
* Hcho:0.02ppm 代表检测到的甲醛浓度为0.02ppm
```log
>>> execfile("/data/pyamp/main.py")
...
Welcome to AliOS Things
amp shakehand begin...
dev /dev/wifi0 is already add
==== python execute from /data/pyamp/main.py ====
Welcome to MicroPython
...
Wi-Fi connected
DeviceIP:192.168.0.107
sleep for 1 s
...
success to establish tcp, fd=4
物联网平台连接成功
Hcho :0.02ppm
Hcho :0.02ppm
Hcho :0.02ppm
```
### 4.2、物联网平台端设备信息查看
&emsp;&emsp;
物联网设备的系统启动成功并连接到物联网平台之后,物联网平台上对应的设备状态会从”未激活状态“变为”上线“,在物模型数据标签页上会显示设备上报到物联网平台的属性值。
<div align="center">
<img src=./../../../images/2_甲醛检测_设备状态及属性.png width=100%/>
</div>
&emsp;&emsp;
此时如果开发板周围的甲醛浓度发生变化,物联网平台的物模型数据会更新为设备上报的最新的属性值。通过点击查看数据,可以看到一段时间监测到的甲醛浓度值。
<div align="center">
<img src=./../../../images/2_甲醛检测_查看数据.png width=100%/>
</div>
<br>
&emsp;&emsp;
到此为止,甲醛检测系统的案例就已经完成了。如果想要学习甲醛检测系统更详细的操作步骤,请参考“[甲醛检测系统详解](https://gitee.com/haasedu/haasedu/blob/release_2.0/2-%E6%99%BA%E8%83%BD%E7%94%9F%E6%B4%BB/%E5%9C%BA%E6%99%AF1-%E5%AE%88%E6%8A%A4%E5%AE%B6%E5%BA%AD%E5%81%A5%E5%BA%B7/%E7%94%B2%E9%86%9B%E6%A3%80%E6%B5%8B/README.md)”中的说明。
{
"name": "haasedu",
"version": "1.0.0",
"io": {
"hcho": {
"type": "ADC",
"port": 2,
"sampling": 12000000
},
"led_r": {
"type": "GPIO",
"port": 36,
"dir": "output",
"pull": "pullup"
},
"ap3216c": {
"type": "I2C",
"port": 1,
"addrWidth": 7,
"freq": 100000,
"mode": "master",
"devAddr": 30
},
"mpu6050": {
"type": "I2C",
"port": 1,
"addrWidth": 7,
"freq": 100000,
"mode": "master",
"devAddr": 105
},
"si7006": {
"type": "I2C",
"port": 1,
"addrWidth": 7,
"freq": 400000,
"mode": "master",
"devAddr": 64
},
"spl06": {
"type": "I2C",
"port": 1,
"addrWidth": 7,
"freq": 400000,
"mode": "master",
"devAddr": 119
},
"qmc5883": {
"type": "I2C",
"port": 1,
"addrWidth": 7,
"freq": 400000,
"mode": "master",
"devAddr": 13
},
"ADC2": {
"type": "ADC",
"port": 0,
"sampling": 12000000
},
"GPIO2": {
"type": "GPIO",
"port": 2,
"dir": "irq",
"pull": "pullup",
"intMode": "rising"
},
"GPIO3": {
"type": "GPIO",
"port": 3,
"dir": "output",
"pull": "pullup"
},
"GPIO4": {
"type": "GPIO",
"port": 4,
"dir": "output",
"pull": "pullup"
},
"GPIO5": {
"type": "GPIO",
"port": 5,
"dir": "output",
"pull": "pullup"
},
"GPIO6": {
"type": "GPIO",
"port": 6,
"dir": "output",
"pull": "pullup"
},
"GPIO7": {
"type": "GPIO",
"port": 7,
"dir": "output",
"pull": "pullup"
},
"GPIO19": {
"type": "GPIO",
"port": 19,
"dir": "output",
"pull": "pullup"
},
"GPIO22": {
"type": "GPIO",
"port": 22,
"dir": "output",
"pull": "pullup"
},
"KEY_1": {
"type": "GPIO",
"port": 23,
"dir": "irq",
"pull": "pullup",
"intMode": "rising"
},
"KEY_2": {
"type": "GPIO",
"port": 20,
"dir": "irq",
"pull": "pullup",
"intMode": "rising"
},
"KEY_3": {
"type": "GPIO",
"port": 21,
"dir": "irq",
"pull": "pullup",
"intMode": "rising"
},
"KEY_4": {
"type": "GPIO",
"port": 26,
"dir": "irq",
"pull": "pullup",
"intMode": "rising"
},
"I2C1": {
"type": "I2C",
"port": 1,
"addrWidth": 7,
"freq": 100000,
"mode": "master",
"devAddr": 60
},
"led_g": {
"type": "GPIO",
"port": 35,
"dir": "output",
"pull": "pullup"
},
"led_b": {
"type": "GPIO",
"port": 34,
"dir": "output",
"pull": "pullup"
},
"SPI0": {
"type": "SPI",
"port": 0,
"mode": "master",
"freq": 2000000
},
"oled_spi": {
"type": "SPI",
"port": 1,
"mode": "master",
"freq": 26000000
},
"oled_dc": {
"type": "GPIO",
"port": 28,
"dir": "output",
"pull": "pullup"
},
"oled_res": {
"type": "GPIO",
"port": 30,
"dir": "output",
"pull": "pullup"
},
"serial2": {
"type": "UART",
"port": 2,
"dataWidth": 8,
"baudRate": 115200,
"stopBits": 1,
"flowControl": "disable",
"parity": "none"
}
},
"debugLevel": "ERROR",
"repl": "disable"
}
\ No newline at end of file
from driver import ADC
class HCHO(object):
def __init__(self, adcObj):
self.adcObj = None
if not isinstance(adcObj, ADC):
raise ValueError("parameter is not an ADC object")
self.adcObj = adcObj
def getPPM(self):
if self.adcObj is None:
raise ValueError("invalid ADC object")
min = 400
max = 0
value = 0
total = 0
i = 0
for i in range(32):
value = self.adcObj.readVoltage()
total += value
# print(value)
if (min >= value):
min = value
if (max <= value):
max = value
analogVoltage = (total - min - max) / 30
analogVoltage += 23
analogVoltage /= 1000.0
#linear relationship(0.4V for 0 ppm and 2V for 5ppm)
ppm = 3.125 * analogVoltage - 1.25
if ppm < 0 :
ppm = 0
return ppm
# -*- encoding: utf-8 -*-
from aliyunIoT import Device # iot组件是连接阿里云物联网平台的组件
import netmgr as nm # Wi-Fi功能所在库
import utime # 延时API所在组件
import ujson # json字串解析库
from driver import ADC # ADC类,通过微处理器的ADC模块读取ADC通道输入电压
import hcho # 甲醛hcho传感器类
adcObj = 0 # ADC通道对象
uartObj = 0 # UART通道对象
# 物联网平台连接标志位
iot_connected = False
# Wi-Fi SSID和Password设置
wifiSsid = "请填写您的路由器名称"
wifiPassword = "请填写您的路由器密码"
# 三元组信息
productKey = "产品密钥" #需要填入物联网云平台申请到的productKey信息
deviceName = "设备名称" #需要填入物联网云平台申请到的deviceName信息
deviceSecret = "设备密钥" #需要填入物联网云平台申请到的deviceSecret信息
# 物联网设备实例
device = None
hchoDev = 0
def hcho_init():
global adcObj,hchoDev
adcObj = ADC()
adcObj.open("hcho")
hchoDev = hcho.HCHO(adcObj)
def get_hcho_value():
global hchoDev
return hchoDev.getPPM()
# 等待Wi-Fi成功连接到路由器
def get_wifi_status():
nm.init()
nm.disconnect()
wifi_connected = nm.getStatus()
print("start to connect " , wifiSsid)
nm.connect(wifiSsid, wifiPassword) # 连接到指定的路由器(路由器名称为wifiSsid, 密码为:wifiPassword)
while True :
if wifi_connected == 5: # nm.getStatus()返回5代表连线成功
break
else:
wifi_connected = nm.getStatus() # 获取Wi-Fi连接路由器的状态信息
utime.sleep(0.5)
print("wifi_connected: " , wifi_connected)
print("Wi-Fi connected")
print('DeviceIP:' + nm.getInfo()['ip']) # 打印Wi-Fi的IP地址信息
# 物联网平台连接成功的回调函数
def on_connect(data):
global iot_connected
iot_connected = True
# 设置props 事件接收函数(当云平台向设备下发属性时)
def on_props(request):
pass
# 连接物联网平台
def connect_lk(productKey, deviceName, deviceSecret):
global device, iot_connected
key_info = {
'region': 'cn-shanghai',
'productKey': productKey,
'deviceName': deviceName,
'deviceSecret': deviceSecret,
'keepaliveSec': 60
}
# 将三元组信息设置到iot组件中
device = Device()
# 设定连接到物联网平台的回调函数,如果连接物联网平台成功,则调用on_connect函数
device.on(Device.ON_CONNECT, on_connect)
# 配置收到云端属性控制指令的回调函数,如果收到物联网平台发送的属性控制消息,则调用on_props函数
device.on(Device.ON_PROPS, on_props)
# 启动连接阿里云物联网平台过程
ret = device.connect(key_info)
# 等待设备成功连接到物联网平台
while(True):
if iot_connected:
print('物联网平台连接成功')
break
else:
print('sleep for 1 s')
utime.sleep(1)
# 上传甲醛浓度信息到物联网平台
def upload_hcho_detector_state():
global device
# 无限循环
while True:
data = get_hcho_value()
H_str = "Hcho : " + str(round(data,2))+'ppm'
print('Hcho :' + str(round(data,2)) +'ppm')
# "HCHO" - 代表甲醛传感器测量到的浓度值
upload_data = {'params': ujson.dumps({
'HCHO': round(data,2),
})}
# 上传甲醛浓度信息到物联网平台
device.postProps(upload_data)
# 每2秒钟上报一次
utime.sleep(2)
if __name__ == '__main__':
# 运行此demo之前务必保证模块已经上电5分钟以上
hcho_init()
# 请替换物联网平台申请到的产品和设备信息,可以参考文章:https://blog.csdn.net/HaaSTech/article/details/114360517
# global productKey, deviceName, deviceSecret ,on_request, on_play
get_wifi_status()
connect_lk(productKey, deviceName, deviceSecret)
upload_hcho_detector_state()
# 起夜灯
&emsp;&emsp;
下图是本案例除硬件连线外的2步导学,每个步骤中实现的功能请参考图中的说明。
<div align="center">
<img src=./../../../images/2_起夜灯_步骤概述.jpg width=60%/>
</div>
## 1、简介
### 1.1、背景
&emsp;&emsp;
**红外线**是一种人类肉眼看不到的光,虽然看不见,但他有一个显著的特性就是具有热效应,即所有高于绝对零度的物质都会产生红外线。人体热释电红外传感器就是一种能探测人体红外光谱变化的传感器,他能检测人发射的红外线,并转化为电信号输出。
&emsp;&emsp;
使用一个控制器来接收人体热释电红外传感器的信号,通过这个信号来控制灯的开启和关闭,就可以制作一个实用的起夜灯。
<div align="center">
<img src=./../../../images/2_起夜灯_起夜灯原理.png width=60%/>
</div>
<br>
### 1.1、准备
* [HaaS EDU K1开发板](https://haas.iot.aliyun.com/solution/detail/hardware?versionId=800C5AB3B8A4A88800000001&dataId=800C5AB3B8A4A888) 一套
* [人体热释电红外传感器](https://haas.iot.aliyun.com/solution/detail/hardware?versionId=800CE1DD3CE998E000000002&dataId=800CE1DD3CE998E0) 一个
* 连接线 若干
&emsp;&emsp;
硬件连线图如下图所示:
<div align="center">
<img src=./../../../images/2_2_HaaS_EDU_K1_起夜灯硬件接线图.png width=85%/>
</div>
<br>
## 2、物联网平台开发
&emsp;&emsp;
对于第一次使用物联网平台的读者,需要开通实例以使用物联网平台的功能。这里可以使用免费的公共实例进行开发。
&emsp;&emsp;
[物联网平台](https://iot.console.aliyun.com/lk/summary/new)中,左上角选择“华东2-上海”,点击“公共实例”,即可开通。
<div align="center">
<img src=./../../../images/5_3_开通公共实例.png
width=100%/>
</div>
&emsp;&emsp;
开通物联网平台功能之后,需要完成下面的3个步骤完成云端设备的创建:
1. 创建云端产品
2. 创建产品属性(物模型)
3. 创建云端设备(获取三元组)
<br>
### 2.1、创建云端产品
&emsp;&emsp;
点击上图中的“公共实例”,即可进入[控制台](https://iot.console.aliyun.com/lk/summary/new)进行产品创建。然后,点击创建产品按钮,如下图所示。
<div align="center">
<img src=./../../../images/1_创建产品.png
width=60%/>
</div>
&emsp;&emsp;
在新建产品设定页面按照下图所示,设定“产品名称”,选择所属的“自定义品类”(自定义品类的物模型为空,需要自己创建,也可以通过导入外部物模型的方式导入),节点类型选择“直连设备”,联网方式选择“Wi-Fi”,数据格式选择“ICA标准数据格式”,检验类型和认证方式选择默认设定即可。还可以根据开发者自己的需求在“产品描述”页面添加针对此产品的描述。
<div align="center">
<img src=./../../../images/2_2_新建起夜灯设备.png
width=60%/>
</div>
&emsp;&emsp;
选择之后,点击“确认”按钮,即可完成产品创建。返回“产品”页面之后可以看到产品类表中会出现刚刚创建的“起夜灯”的产品,如下图所示。
<div align="center">
<img src=./../../../images/2_2_起夜灯_产品列表页.png width=60%/>
</div>
<br>
### 2.2、创建产品属性(物模型)
&emsp;&emsp;
点击上图中的“查看”按钮,即可看到产品信息,Topic列表,功能定义,数据解析等跟产品相关功能的设定。点开“功能定义”标签页,可以看到设备物模型定义。
<div align="center">
<img src=./../../../images/2_2_起夜灯_产品详情页面.png width=60%/>
</div>
&emsp;&emsp;
其中标识符是设备端上报设备属性状态的消息中需要使用的标识符,并且只有在设备上报的属性内容符合“数据定义”中的数据取值范围的时候才会被物联网平台记录,否则会被物联网平台认定为非法属性而过滤掉。
&emsp;&emsp;
本节我们选择直接新建方式来创建此产品需要的物模型信息,点击上图中的”编辑草稿“按钮。然后如下图再点击“添加自定义功能”。
<div align="center">
<img src=./../../../images/2_2_起夜灯_编辑草稿.png width=60%/>
</div>
&emsp;&emsp;
如下图添加一个“LED开关”的默认功能。其中标识符为LEDSwith, 0表示LED灯关闭,1表示LED开启。
<div align="center">
<img src=./../../../images/2_2_起夜灯_添加自定义功能.png width=60%/>
</div>
点击确定后回到前一个页面,再点击“发布上线”
<div align="center">
<img src=./../../../images/2_2_起夜灯_发布物模型.png
width=60%/>
</div>
&emsp;&emsp;
产品及其物模型创建完成后就可以创建这个产品的设备了。
<br>
### 2.3、创建云端设备(获取三元组)
&emsp;&emsp;
在产品列表页面中,点击”起夜灯“后的“管理设备”,就会进到设备管理页面。
<div align="center">
<img src=./../../../images/2_2_起夜灯_产品页_管理设备.png width=100%/>
</div>
&emsp;&emsp;
在“设备”页面点击“添加设备”按钮,如下图所示。
<div align="center">
<img src=./../../../images/2_2_起夜灯_添加设备入口.png width=80%/>
</div>
&emsp;&emsp;
在“添加设备”页面中设定“deviceName”,这里开发者可以自己填入自己想设定的设备名称,也可以不填任何内容让系统自动生成设备名称,如下图所示。
<div align="center">
<img src=./../../../images/2_2_起夜灯_添加设备.png width=40%/>
</div>
&emsp;&emsp;
设备添加完成后,点击“前往查看”按钮,就可以看到此设备端详细信息了。
<div align="center">
<img src=./../../../images/2_2_起夜灯_完成添加设备.png width=40%/>
</div>
&emsp;&emsp;
设备信息中有两个信息需要和设备端开发相匹配:
1. 三元组
2. 物模型属性信息
<div align="center">
<img src=./../../../images/2_2_起夜灯_设备详情.png width=100%/>
</div>
<br>
#### 2.3.1、**获取设备三元组**
&emsp;&emsp;
如上图所示,点击deviceSecret后面的“查看”按钮,就可以看到设备的三元组信息,三元组是物联网设备端和物联网云端设备相关联的唯一标识符,在设备端连接云端的时候会使用三元组信息和云端进行鉴权,鉴权通过之后云端会认为设备已激活并上线。
<div align="center">
<img src=./../../../images/2_2_起夜灯_设备三元组.png width=50%/>
</div>
<br>
#### 2.3.2、**查看设备属性信息**
&emsp;&emsp;
设备详情信息页中的“物模型数据”标签页中可以看到设备的所有属性信息、设备时间上报情况及设备服务调用情况,如下图所示。待物联网设备按照设备属性对应的标识符上报设备属性的时候,本图片中的“LED开关“,属性值就会显示设备最新的属性信息。
<div align="center">
<img src=./../../../images/2_2_起夜灯_设备物模型数据.png width=80%/>
</div>
<br>
> 创建产品和设备的过程是按照面向对象的思想进行设计的,其中创建产品可以看成是新建一个类,其中的物模型则是类的对象,创建设备则是进行类的实例化。
<br>
## 3、设备端开发
### 3.1、开发环境
&emsp;&emsp;
在进行下一步之前请确保HaaS EDU K1开发环境已经搭建完毕。详情请参考[HaaS EDU K1快速开始](../../../startup/HaaS_EDU_K1_startup.md)的说明。
<br>
### 3.2、创建解决方案
&emsp;&emsp;
如下图所示,在Haas Studio中创建项目。先选择左侧的“开发板型号”再从右侧的案例中选择“起夜灯”案例点击“立即创建”即可。
<div align="center">
<img src=./../../../images/HaaS_Studio_创建工程示范.png width=100%/>
</div>
<br>
> Python脚本的详细说明请参考脚本内嵌的文字注释
1. **修改路由器名称及密码**
&emsp;&emsp;
修改main.py中wifiSsid和wifiPassword的值为读者实际要连接的路由器的名称及密码(请注意名称和密码都需要放在""符号中间)。
```python
# Wi-Fi SSID和Password设置
wifiSsid = "请输入您的路由器名称"
wifiPassword = "请输入您的路由器密码"
```
&emsp;&emsp;
修改完成之后get_wifi_status函数中的nm.connect(wifiSsid, wifiPassword) 语句就会连接读者自己设定的路由器。
2. **修改设备端三元组**
&emsp;&emsp;
修改main.py中productKey、deviceName和deviceSecret的值为读者创建的物联网设备的三元组信息。
```python
# 三元组信息
productKey = "产品密钥"
deviceName = "设备名称"
deviceSecret = "设备密钥"
```
3. **修改设备端上报数据所用标识符**
&emsp;&emsp;
main.py中下面的代码实现的是上传起夜灯状态到云端的功能。其中LEDSwitch便是起夜灯状态上报云端所用的标识符。
```python
while True :
have_human = irDev.irDetect() # 获取传感器的值
if (have_human == 1) :
print("human is here .....\r\n")
if (last_have_human != have_human) :
led_control(have_human) # 控制LED亮灭
# 生成上报到物联网平台的属性值字串,此处的属性标识符"LEDSwith"必须和物联网平台的属性一致
# "LEDSwitch" - 表示起夜灯的状态
upload_data = {'params': ujson.dumps({
'LEDSwitch': have_human,
})
}
# 上传LED状态到物联网平台
device.postProps(upload_data)
last_have_human = have_human # 记录当前状态
utime.sleep(1) # 休眠1秒
```
&emsp;&emsp;
确保这起夜灯的标识符和物联网产品的物模型中属性标识符是一样的.
<br>
## 4、运行结果
### 4.1、本地查看
&emsp;&emsp;
推送此脚本到HaaS EDU K1之后,串口会周期性的打印如下日志,其中:
• “物联网平台连接成功” 代表成功连接到物联网平台
• 人体感应传感器检测到有人体活动的时候变会打印“human is here”,同时HaaS EDU K1上面的红色LED会亮起。
• 人体感应传感器没检测到人体活动的时候变会停止打印“human is here”,Haas EDU K1的红色LED灯也会随之熄灭。
```log
...
Welcome to AliOS Things
amp shakehand begin...
dev /dev/wifi0 is already add
==== python execute from /data/pyamp/main.py ====
Welcome to MicroPython
...
Wi-Fi connected
DeviceIP:192.168.0.107
sleep for 1 s
...
success to establish tcp, fd=4
物联网平台连接成功
human is here ...
human is here ...
human is here ...
...
```
<br>
### 4.2、物联网平台端设备信息查看
&emsp;&emsp;
物联网设备的系统启动成功并连接到物联网平台之后,物联网平台上对应的设备状态会从”未激活状态“变为”上线“,在物模型数据标签页上会显示设备上报到物联网平台的属性值。
<div align="center">
<img src=./../../../images/2_2_起夜灯_设备状态及属性.png width=100%/>
</div>
&emsp;&emsp;
此时如果开发板周围有人经过,起夜灯就会开启,物联网平台的物模型数据会更新为设备上报的最新的属性值。
<br>
&emsp;&emsp;
到此为止,起夜灯数据上云的案例就已经完成了。如果想要学习起夜灯案例更详细的操作步骤,请参考“[起夜灯系统详解](https://gitee.com/haasedu/haasedu/blob/release_2.0/2-%E6%99%BA%E8%83%BD%E7%94%9F%E6%B4%BB/%E5%9C%BA%E6%99%AF2-%E8%B5%B7%E5%A4%9C%E7%81%AF/README.md)”中的说明。
{
"name": "haasedu",
"version": "1.0.0",
"io": {
"ir": {
"type": "GPIO",
"port": 7,
"dir": "input",
"pull": "pullup"
},
"led": {
"type": "GPIO",
"port": 36,
"dir": "output",
"pull": "pullup"
},
"led_r": {
"type": "GPIO",
"port": 36,
"dir": "output",
"pull": "pullup"
},
"ap3216c": {
"type": "I2C",
"port": 1,
"addrWidth": 7,
"freq": 100000,
"mode": "master",
"devAddr": 30
},
"mpu6050": {
"type": "I2C",
"port": 1,
"addrWidth": 7,
"freq": 100000,
"mode": "master",
"devAddr": 105
},
"si7006": {
"type": "I2C",
"port": 1,
"addrWidth": 7,
"freq": 400000,
"mode": "master",
"devAddr": 64
},
"spl06": {
"type": "I2C",
"port": 1,
"addrWidth": 7,
"freq": 400000,
"mode": "master",
"devAddr": 119
},
"qmc5883": {
"type": "I2C",
"port": 1,
"addrWidth": 7,
"freq": 400000,
"mode": "master",
"devAddr": 13
},
"ADC2": {
"type": "ADC",
"port": 0,
"sampling": 12000000
},
"GPIO2": {
"type": "GPIO",
"port": 2,
"dir": "irq",
"pull": "pullup",
"intMode": "rising"
},
"GPIO3": {
"type": "GPIO",
"port": 3,
"dir": "output",
"pull": "pullup"
},
"GPIO4": {
"type": "GPIO",
"port": 4,
"dir": "output",
"pull": "pullup"
},
"GPIO5": {
"type": "GPIO",
"port": 5,
"dir": "output",
"pull": "pullup"
},
"GPIO6": {
"type": "GPIO",
"port": 6,
"dir": "output",
"pull": "pullup"
},
"GPIO7": {
"type": "GPIO",
"port": 7,
"dir": "output",
"pull": "pullup"
},
"GPIO19": {
"type": "GPIO",
"port": 19,
"dir": "output",
"pull": "pullup"
},
"GPIO22": {
"type": "GPIO",
"port": 22,
"dir": "output",
"pull": "pullup"
},
"KEY_1": {
"type": "GPIO",
"port": 23,
"dir": "irq",
"pull": "pullup",
"intMode": "rising"
},
"KEY_2": {
"type": "GPIO",
"port": 20,
"dir": "irq",
"pull": "pullup",
"intMode": "rising"
},
"KEY_3": {
"type": "GPIO",
"port": 21,
"dir": "irq",
"pull": "pullup",
"intMode": "rising"
},
"KEY_4": {
"type": "GPIO",
"port": 26,
"dir": "irq",
"pull": "pullup",
"intMode": "rising"
},
"I2C1": {
"type": "I2C",
"port": 1,
"addrWidth": 7,
"freq": 100000,
"mode": "master",
"devAddr": 60
},
"led_g": {
"type": "GPIO",
"port": 35,
"dir": "output",
"pull": "pullup"
},
"led_b": {
"type": "GPIO",
"port": 34,
"dir": "output",
"pull": "pullup"
},
"SPI0": {
"type": "SPI",
"port": 0,
"mode": "master",
"freq": 2000000
},
"oled_spi": {
"type": "SPI",
"port": 1,
"mode": "master",
"freq": 26000000
},
"oled_dc": {
"type": "GPIO",
"port": 28,
"dir": "output",
"pull": "pullup"
},
"oled_res": {
"type": "GPIO",
"port": 30,
"dir": "output",
"pull": "pullup"
},
"serial2": {
"type": "UART",
"port": 2,
"dataWidth": 8,
"baudRate": 115200,
"stopBits": 1,
"flowControl": "disable",
"parity": "none"
}
},
"debugLevel": "ERROR",
"repl": "disable"
}
\ No newline at end of file
from driver import GPIO
class IR(object):
def __init__(self, gpioObj):
self.gpioObj = None
if not isinstance(gpioObj, GPIO):
raise ValueError("parameter is not a GPIO object")
self.gpioObj = gpioObj
def irDetect(self):
if self.gpioObj is None:
raise ValueError("invalid GPIO object")
value = self.gpioObj.read()
return value
# -*- coding: UTF-8 -*-
from aliyunIoT import Device # aliyunIoT组件是连接阿里云物联网平台的组件
import netmgr as nm # Wi-Fi功能所在库
import ujson # json字串解析库
import utime # 延时API所在组件
from driver import GPIO # GPIO类,用于控制微处理器的输入输出功能
import ir # ir人体红外传感器类
gpioirDev = 0
gpioledDev = 0
irDev = 0
# 物联网平台连接标志位
iot_connected = False
wlan = None
# Wi-Fi SSID和Password设置
wifiSsid = "请输入您的路由器名称"
wifiPassword = "请输入您的路由器密码"
# 三元组信息
productKey = "产品密钥"
deviceName = "设备名称"
deviceSecret = "设备密钥"
# 物联网设备实例
device = None
# 等待Wi-Fi成功连接到路由器
def get_wifi_status():
nm.init()
nm.disconnect()
wifi_connected = nm.getStatus()
print("start to connect " + wifiSsid)
nm.connect(wifiSsid, wifiPassword) # 连接到指定的路由器(路由器名称为wifiSsid, 密码为:wifiPassword)
while True :
if wifi_connected == 5: # nm.getStatus()返回5代表连线成功
break
else:
wifi_connected = nm.getStatus() # 获取Wi-Fi连接路由器的状态信息
utime.sleep(0.5)
print("wifi_connected: " + str(wifi_connected))
print("Wi-Fi connected")
print('DeviceIP:' + nm.getInfo()['ip']) # 打印Wi-Fi的IP地址信息
# 物联网平台连接成功的回调函数
def on_connect(data):
global iot_connected
iot_connected = True
# 设置props 事件接收函数(当云平台向设备下发属性时)
def on_props(request):
pass
def connect_lk(productKey, deviceName, deviceSecret):
global device, iot_connected
key_info = {
'region': 'cn-shanghai',
'productKey': productKey,
'deviceName': deviceName,
'deviceSecret': deviceSecret,
'keepaliveSec': 60
}
# 将三元组信息设置到iot组件中
device = Device()
# 设定连接到物联网平台的回调函数,如果连接物联网平台成功,则调用on_connect函数
device.on(Device.ON_CONNECT, on_connect)
# 配置收到云端属性控制指令的回调函数,如果收到物联网平台发送的属性控制消息,则调用on_props函数
device.on(Device.ON_PROPS, on_props)
# 启动连接阿里云物联网平台过程
device.connect(key_info)
# 等待设备成功连接到物联网平台
while(True):
if iot_connected:
print('物联网平台连接成功')
break
else:
print('sleep for 1 s')
utime.sleep(1)
print('sleep for 2s')
utime.sleep(2)
def human_check_init():
global gpioirDev,gpioledDev,irDev
gpioirDev = GPIO()
gpioirDev.open("ir")
irDev = ir.IR(gpioirDev)
gpioledDev = GPIO()
gpioledDev.open("led")
def led_control(on):
global gpioledDev
if on == 0:
gpioledDev.write(0) # GPIO写入0,灭灯
else:
gpioledDev.write(1) # GPIO写入1,亮灯
def human_detector() :
global gpioirDev,gpioledDev,irDev
last_have_human = 0 # 记录初始值
while True :
have_human = irDev.irDetect() # 获取传感器的值
if (have_human == 1) :
print("human is here ...\r\n")
if (last_have_human != have_human) :
led_control(have_human) # 控制LED亮灭
# 生成上报到物联网平台的属性值字串,此处的属性标识符"LEDSwith"必须和物联网平台的属性一致
# "LEDSwitch" - 表示起夜灯的状态
upload_data = {'params': ujson.dumps({
'LEDSwitch': have_human,
})
}
# 上传LED状态到物联网平台
device.postProps(upload_data)
last_have_human = have_human # 记录当前状态
utime.sleep(1) # 休眠1秒
gpioirDev.close()
gpioledDev.close()
if __name__ == '__main__' :
# 请替换物联网平台申请到的产品和设备信息,可以参考文章:https://blog.csdn.net/HaaSTech/article/details/114360517
# global productKey, deviceName, deviceSecret ,on_request, on_play
get_wifi_status()
connect_lk(productKey, deviceName, deviceSecret)
human_check_init()
human_detector()
# 智慧路灯系统
&emsp;&emsp;
下图是本案例除硬件连线外的2步导学,每个步骤中实现的功能请参考图中的说明。在硬件连线完成之后我们建议您先使用“一分钟上云体验”功能预先体验本案例的实际运行效果。
<div align="center">
<img src=./../../../images/4_智慧路灯_步骤概述.jpg width=60%/>
</div>
## 1、简介
&emsp;&emsp;
随着城市化不断扩大,城市的灯光秀越来越多,让我们居住的城市生活变得五彩缤纷,灯火辉煌,让城市显得越来越繁华。但是,我们也会经常发现有些路边的灯,在天很黑了不会自动打开,或者到清晨,天很亮了,马路边的灯也不会自动熄灭,只会在固定时间统一开关控制,这样既不人性化也不环保。于是现在很多地方的路灯会根据周围环境亮度自动开启或关闭路边灯光,那这样的路灯就非常人性化,同时也非常环保,这才是真正的城市智慧路灯。
### 1.1、背景知识
&emsp;&emsp;
本章课程打造的智慧路灯控制系统是当检测到周围环境亮度变暗的时候,系统会自动打开灯,当周围环境亮度变亮的时候,系统就会自动关闭灯。本智慧路灯控制系统默认设置一个亮度阈值,当亮度值超过阈值,关灯,当亮度值低于阈值,开灯,当然,这个阈值可以根据每个人对光的亮度感觉不同而调整。
### 1.2、准备
&emsp;&emsp;
本案例之需要HaaS EDU K1开发一套。实验过程中会使用HaaS EDU K1内置的AP3216C传感器和红色LED。
## 2、物联网平台开发
### 2.1、开通公共实例
&emsp;&emsp;
对于第一次使用物联网平台的读者,需要开通实例以使用物联网平台的功能。这里可以使用免费的公共实例进行开发。
&emsp;&emsp;
[物联网平台](https://iot.console.aliyun.com/lk/summary/new)中,左上角选择“华东2-上海”,点击“公共实例”,即可开通。
<div align="center">
<img src=./../../../images/5_3_开通公共实例.png
width=100%/>
</div>
### 2.2、云端创建产品
1. **创建产品**
&emsp;&emsp;
点击上图中的“公共实例”,即可进入[控制台](https://iot.console.aliyun.com/lk/summary/new)进行产品创建。然后,点击创建产品按钮,如下图所示。
<div align="center">
<img src=./../../../images/2_3_创建产品.jpg
width=40%/>
</div>
&emsp;&emsp;
这里创建了一个名称为“智慧路灯”的产品。
<div align="center">
<img src=./../../../images/5_1_新建智慧路灯产品.jpg
width=40%/>
</div>
&emsp;&emsp;
点击确认,就可以在产品列表中出现智慧路灯。
<div align="center">
<img src=./../../../images/5_1_完成产品创建.jpg
width=40%/>
</div>
&emsp;&emsp;
查看产品详情。
<div align="center">
<img src=./../../../images/5_1_产品功能定义.jpg
width=40%/>
</div>
&emsp;&emsp;
编辑草稿,选择自定义功能。
<div align="center">
<img src=./../../../images/5_1_自定义产品物模型定义.jpg
width=40%/>
</div>
&emsp;&emsp;
如下图设置对应产品的物模型,分别设置光强度电压值和灯开关两个模型。
<div align="center">
<img src=./../../../images/5_1_2产品物模型.jpg
width=40%/>
<img src=./../../../images/5_1_3产品物模型onoff.jpg
width=40%/>
</div>
&emsp;&emsp;
在产品详情中增加产品的功能,可以看到刚才设置的光强度电压值和灯开关两个功能属性,点击发布上线。
<div align="center">
<img src=./../../../images/5_1_3完成物模型设置.jpg
width=80%/>
</div>
&emsp;&emsp;
这样,整个智慧路灯产品在物联网平台也创建好了。
2. **设备管理**
&emsp;&emsp;
选中最左边栏设备管理中的设备,然后添加设备,
<div align="center">
<img src=./../../../images/2_3_添加设备.jpg
width=40%/>
</div>
&emsp;&emsp;
选择的产品为刚刚创建的智慧路灯,设备名设置为smartlight,点击确认,
<div align="center">
<img src=./../../../images/5_1_智慧路灯设备.jpg
width=40%/>
</div>
&emsp;&emsp;
进入设备详情,获取设备三元组信息,
<div align="center">
<img src=./../../../images/5_1_设备三元组信息.jpg
width=40%/>
</div>
&emsp;&emsp;
将设备三元组信息一键拷贝出来,在设备开发中会用到。
<div align="center">
<img src=./../../../images/2_3_设备证书.jpg
width=40%/>
</div>
3. **设备引擎**
&emsp;&emsp;
如何通过光感自适应控制灯的亮灭呢?可以在物联网平台上通过引擎规则实现,本课程使用的光强度传感器AP3216C返回值越低,说明周围的环境光强度越低;反之,返回值越高,说明周围的环境光强度越高。
<div align="center">
<img src=./../../../images/5_1_haaseduk1_规则引擎1.jpg
width=70%/>
</div>
&emsp;&emsp;
当光强度大于100,说明周围的环境变亮,执行关灯的指令。<br />
<div align="center">
<img src=./../../../images/5_1_haaseduk1_规则引擎2.jpg
width=70%/>
</div>
&emsp;&emsp;
当光强度电压值小于100,说明周围的环境变暗,执行开灯的指令。<br />
<div align="center">
<img src=./../../../images/5_1_haaseduk1_规则引擎3.jpg
width=70%/>
</div>
&emsp;&emsp;
规则引擎创建完成以后,启动引擎即可。<br />
<div align="center">
<img src=./../../../images/5_1_haaseduk1_规则引擎4.jpg
width=70%/>
</div>
## 3、设备端开发
### 3.1、开发环境
&emsp;&emsp;
在进行下一步之前请确保HaaS EDU K1开发环境已经搭建完毕。详情请参考“[HaaS EDU K1快速开始](https://hli.aliyuncs.com/haas-static/haasapi/index.html#/Python/docs/zh-CN/startup/HaaS_EDU_K1_startup)”的说明。
### 3.2、创建解决方案
&emsp;&emsp;
如下图所示,在Haas Studio中创建项目。先选择左侧的“开发板型号”再从右侧的案例中选择“智能路灯系统”案例点击“立即创建”即可。
<div align="center">
<img src=./../../../images/HaaS_Studio_创建工程示范.png width=100%/>
</div>
<br>
> Python脚本的详细说明请参考脚本内嵌的文字注释
&emsp;&emsp;
之后对代码进行如下修改。
1. **修改路由器名称及密码**
&emsp;&emsp;
修改main.py中wifiSsid和wifiPassword的值为读者实际要连接的路由器的名称及密码(请注意名称和密码都需要放在""符号中间)。
```python
# wifi连接的的wifiSsid和wifiPassword定义
wifiSsid = "请填写您的路由器名称"
wifiPassword = "请填写您的路由器密码"
```
&emsp;&emsp;
修改完成之后get_wifi_status函数中的nm.connect(wifiSsid, wifiPassword) 语句就会连接读者自己设定的路由器。
1. **修改设备端三元组**
&emsp;&emsp;
修改main.py中productKey、deviceName和deviceSecret的值为上面物联网平台创建的物联网设备的三元组信息。
```python
# 物联网平台相关的key和serect定义
productKey = "产品密钥"
deviceName = "设备名"
deviceSecret = "设备密钥"
```
3. **修改设备端上报光强度和下发开关标识符**
&emsp;&emsp;
main.py中下面的代码实现的是上传光强度值到云端的功能,以及云端下发对应的开关命令。其中Brightness便是光强度值上报时所用的标识符,onoff为云端下发的开关标识符。
```python
def report_light_data(l_data):
# 生成上报到物联网平台的属性值字串
prop = ujson.dumps({
'Brightness': l_data,
})
upload_data = {'params': prop}
# 上传光强度信息到物联网平台
device.postProps(upload_data)
```
&emsp;&emsp;
下面是采集光照数据的时候,采样值是跨阈值100就需要进行上报。
```python
# 采集打印光照强度电压值
def show_lightness():
global lightness,i_light,last_light,system_reset
i_light = int(lightness)
if system_reset == 1:
print("system first come")
system_reset = 0 # 系统第一次启动
report_light_data(i_light)
elif (i_light >= 1000 and last_light < 1000) or (i_light < 1000 and last_light >= 1000):
report_light_data(i_light)
print("light has change")
else:
print('no need report')
if i_light < 10:
T_str = "亮度:" + str(round(i_light, 1))
elif i_light >= 10 and i_light < 100:
T_str = "亮度:" + str(round(i_light, 2))
elif i_light >= 100 and i_light < 1000:
T_str = "亮度:" + str(round(i_light, 3))
elif i_light >= 1000 and i_light < 10000:
T_str = "亮度:" + str(round(i_light, 4))
print(T_str)
last_light = i_light
```
&emsp;&emsp;
处理物联网平台过来的数据。
```python
# 设置props 事件接收函数(当云平台向设备下发属性时)
def on_props(request):
global alarm_on, device
print(request)
payload = ujson.loads(request['params'])
# 获取dict状态字段 注意要验证键存在 否则会抛出异常
if "onoff" in payload.keys():
alarm_on = payload["onoff"]
if (alarm_on):
print("开灯")
ledOn()
else:
print("关灯")
ledOff()
# 要将更改后的状态同步上报到云平台
upload_data = {'params': ujson.dumps({
'onoff': alarm_on,
})
}
# 上报本地报警灯状态到云端
device.postProps(upload_data)
```
&emsp;&emsp;
确保Brightness和onoff两个标识符和物联网产品的物模型中属性标识符是一样的,如下图所示:
<div align="center">
<img src=./../../../images/5_1_完成物模型设置.jpg
width=40%/>
</div>
## 4、运行结果
&emsp;&emsp;
推送此脚本到HaaS EDU K1之后,设备上打印光强度值,同时将相关的光强度值上传到云端,并且当光强度大于100,打开HaaS EDU K1开发板上的LED灯;当光强度小于100,关闭HaaS EDU K1开发板上的LED灯,如下日志所示:
```bash
...
物联网平台连接成功
...
ap3216c init finished
system first come
...
uploading data to the cloud: {'params': '{"Brightness": 304}'}
亮度:304
led off
uploading data to the cloud: {'params': '{"Brightness": 0}'}
light has change
亮度:0
led on
no need report
亮度:53
uploading data to the cloud: {'params': '{"Brightness": 302}'}
light has change
亮度:302
led off
uploading data to the cloud: {'params': '{"Brightness": 5}'}
light has change
亮度:5
led on
...
```
&emsp;&emsp;
到此为止,智慧路灯系统案例就已经完成了。如果想要学习智慧路灯实验更详细的操作步骤,请参考“[智慧路灯系统详解](https://gitee.com/haasedu/haasedu/blob/release_2.0/5-%E6%99%BA%E6%85%A7%E5%9F%8E%E5%B8%82/%E5%9C%BA%E6%99%AF1-%E6%99%BA%E6%85%A7%E8%B7%AF%E7%81%AF/README.md)”中的说明。
"""
Copyright (C) 2015-2020 Alibaba Group Holding Limited
The driver for AP3216C chip, The AP3216C is an integrated ALS & PS module
that includes a digital ambient light sensor [ALS], a proximity sensor [PS],
and an IR LED in a single package.
"""
from micropython import const
from driver import I2C
from utime import sleep_ms
# System Register
AP3216C_SYS_CONFIGURATION_REG = const(0x00)
AP3216C_SYS_INT_STATUS_REG = const(0x01)
AP3216C_SYS_INT_CLEAR_MANNER_REG = const(0x02)
AP3216C_IR_DATA_L_REG = const(0x0A)
AP3216C_IR_DATA_H_REG = const(0x0B)
AP3216C_ALS_DATA_L_REG = const(0x0C)
AP3216C_ALS_DATA_H_REG = const(0x0D)
AP3216C_PS_DATA_L_REG = const(0x0E)
AP3216C_PS_DATA_H_REG = const(0x0F)
# ALS Register
AP3216C_ALS_CONFIGURATION_REG = const(0x10)
AP3216C_ALS_CALIBRATION_REG = const(0x19)
AP3216C_ALS_THRESHOLD_LOW_L_REG = const(0x1A)
AP3216C_ALS_THRESHOLD_LOW_H_REG = const(0x1B)
AP3216C_ALS_THRESHOLD_HIGH_L_REG = const(0x1C)
AP3216C_ALS_THRESHOLD_HIGH_H_REG = const(0x1D)
# PS Register
AP3216C_PS_CONFIGURATION_REG = const(0x20)
AP3216C_PS_LED_DRIVER_REG = const(0x21)
AP3216C_PS_INT_FORM_REG = const(0x22)
AP3216C_PS_MEAN_TIME_REG = const(0x23)
AP3216C_PS_LED_WAITING_TIME_REG = const(0x24)
AP3216C_PS_CALIBRATION_L_REG = const(0x28)
AP3216C_PS_CALIBRATION_H_REG = const(0x29)
AP3216C_PS_THRESHOLD_LOW_L_REG = const(0x2A)
AP3216C_PS_THRESHOLD_LOW_H_REG = const(0x2B)
AP3216C_PS_THRESHOLD_HIGH_L_REG = const(0x2C)
AP3216C_PS_THRESHOLD_HIGH_H_REG = const(0x2D)
#mode value
AP3216C_MODE_POWER_DOWN = const(0x0)
AP3216C_MODE_ALS = const(0x1)
AP3216C_MODE_PS = const(0x2)
AP3216C_MODE_ALS_AND_PS = const(0x3)
AP3216C_MODE_SW_RESET = const(0x4)
AP3216C_MODE_ALS_ONCE = const(0x5)
AP3216C_MODE_PS_ONCE = const(0x6)
AP3216C_MODE_ALS_AND_PS_ONCE = const(0x7)
#ap3216c_int_clear_manner
AP3216C_INT_CLEAR_MANNER_BY_READING = const(0x0)
AP3216C_ALS_CLEAR_MANNER_BY_SOFTWARE = const(0x1)
#als_range
AP3216C_ALS_RANGE_20661 = const(0x0)
AP3216C_ALS_RANGE_5162 = const(0x1)
AP3216C_ALS_RANGE_1291 = const(0x2)
AP3216C_ALS_RANGE_323 = const(0x3)
#als_range
AP3216C_PS_GAIN1 = const(0x0)
AP3216C_PS_GAIN2 = const(0x1)
AP3216C_PS_GAIN4 = const(0x2)
AP3216C_PS_GAIN8 = const(0x3)
AP3216C_SYSTEM_MODE = const(0x0)
AP3216C_INT_PARAM = const(0x1)
AP3216C_ALS_RANGE = const(0x2)
AP3216C_ALS_PERSIST = const(0x3)
AP3216C_ALS_CALIBRATION = const(0x4)
AP3216C_ALS_LOW_THRESHOLD_L = const(0x5)
AP3216C_ALS_LOW_THRESHOLD_H = const(0x6)
AP3216C_ALS_HIGH_THRESHOLD_L = const(0x7)
AP3216C_ALS_HIGH_THRESHOLD_H = const(0x8)
AP3216C_PS_INTEGRATED_TIME = const(0x9)
AP3216C_PS_GAIN = const(0xa)
AP3216C_PS_PERSIST = const(0xb)
AP3216C_PS_LED_CONTROL = const(0xc)
AP3216C_PS_LED_DRIVER_RATIO = const(0xd)
AP3216C_PS_INT_MODE = const(0xe)
AP3216C_PS_MEAN_TIME = const(0xf)
AP3216C_PS_WAITING_TIME = const(0x10)
AP3216C_PS_CALIBRATION_L = const(0x11)
AP3216C_PS_CALIBRATION_H = const(0x12)
AP3216C_PS_LOW_THRESHOLD_L = const(0x13)
AP3216C_PS_LOW_THRESHOLD_H = const(0x14)
AP3216C_PS_HIGH_THRESHOLD_L = const(0x15)
AP3216C_PS_HIGH_THRESHOLD_H = const(0x16)
class AP3216C(object):
"""
This class implements ap3216c chip's defs.
"""
def __init__(self, i2cDev):
self._i2cDev = None
if not isinstance(i2cDev, I2C):
raise ValueError("parameter is not an I2C object")
# make AP3216C's internal object points to i2cDev
self._i2cDev = i2cDev
self.init()
# 写寄存器的值
def write_reg(self, addr, data):
msgbuf = bytearray([data])
self._i2cDev.memWrite(msgbuf, addr, 8)
# print("--> write addr " + str(addr) + ", value = " + str(msgbuf))
# 读寄存器的值
def read_regs(self, addr, len):
buf = bytearray(len)
self._i2cDev.memRead(buf, addr, 8)
# print("--> read " + str(len) + " bytes from addr " + str(addr) + ", " + str(len) + " bytes value = " + str(buf))
return buf
# 软件复位传感器
def reset_sensor(self):
self.write_reg(AP3216C_SYS_CONFIGURATION_REG, AP3216C_MODE_SW_RESET); # reset
def read_low_and_high(self, reg, len):
data = self.read_regs(reg, len)[0] | (self.read_regs(reg + 1, len)[0] << len * 8) # 读低字节 - 读高字节 - 合并数据
if (data > (1 << 15)):
data = data - (1<<16)
return data
def ap3216c_get_IntStatus(self):
# 读中断状态寄存器
IntStatus = self.read_regs(AP3216C_SYS_INT_STATUS_REG, 1)[0]
# IntStatus 第 0 位表示 ALS 中断,第 1 位表示 PS 中断。
return IntStatus # 返回状态
def ap3216c_int_init(self):
#print("ap3216c_int_init")
pass
#配置 中断输入引脚
def ap3216c_int_Config(self):
#print("ap3216c_int_Config")
pass
#初始化入口
def init(self):
# reset ap3216c
self.reset_sensor()
sleep_ms(100)
self.ap3216c_set_param(AP3216C_SYSTEM_MODE, AP3216C_MODE_ALS_AND_PS)
sleep_ms(150) # delay at least 112.5ms
self.ap3216c_int_Config()
self.ap3216c_int_init()
# This function reads light by ap3216c sensor measurement
# @param no
# @return the ambient light converted to float data.
#
def ap3216c_read_ambient_light(self):
read_data = self.read_low_and_high(AP3216C_ALS_DATA_L_REG, 1)
range = self.ap3216c_get_param(AP3216C_ALS_RANGE)
#print("ap3216c_read_ambient_light read_data is " , read_data, range)
if (range == AP3216C_ALS_RANGE_20661):
brightness = 0.35 * read_data # sensor ambient light converse to reality
elif (range == AP3216C_ALS_RANGE_5162):
brightness = 0.0788 * read_data # sensor ambient light converse to reality
elif (range == AP3216C_ALS_RANGE_1291):
brightness = 0.0197 * read_data # sensor ambient light converse to reality
elif (range == AP3216C_ALS_RANGE_323):
brightness = 0.0049 * read_data # sensor ambient light converse to reality
return brightness
#This function reads proximity by ap3216c sensor measurement
#@param no
#@return the proximity data.
def ap3216c_read_ps_data(self):
read_data = self.read_low_and_high(AP3216C_PS_DATA_L_REG, 1) # read two data
#print("ap3216c_read_ps_data read_data is " , read_data);
if (1 == ((read_data >> 6) & 0x01 or (read_data >> 14) & 0x01)) :
return 55555 # 红外过高(IR),PS无效 返回一个 55555 的无效数据
proximity = (read_data & 0x000f) + (((read_data >> 8) & 0x3f) << 4)
# sensor proximity converse to reality
if (proximity > (1 << 15)) :
proximity = proximity - (1<<16)
proximity |= read_data & 0x8000 # 取最高位,0 表示物体远离,1 表示物体靠近
return proximity # proximity 后十位是数据位,最高位为状态位
#This function reads ir by ap3216c sensor measurement
#@param no
#@return the ir data.
def ap3216c_read_ir_data(self):
read_data = self.read_low_and_high(AP3216C_IR_DATA_L_REG, 1) # read two data
#print("ap3216c_read_ir_data read_data is" , read_data);
proximity = (read_data & 0x0003) + ((read_data >> 8) & 0xFF)
# sensor proximity converse to reality
if (proximity > (1 << 15)) :
proximity = proximity - (1<<16)
return proximity
#This function sets parameter of ap3216c sensor
#@param cmd the parameter cmd of device
#@param value for setting value in cmd register
#@return the setting parameter status,RT_EOK reprensents setting successfully.
def ap3216c_set_param(self, cmd, value):
if cmd == AP3216C_SYSTEM_MODE:
# default 000,power down
self.write_reg(AP3216C_SYS_CONFIGURATION_REG, value)
elif cmd == AP3216C_INT_PARAM:
self.write_reg(AP3216C_SYS_INT_CLEAR_MANNER_REG, value)
elif cmd == AP3216C_ALS_RANGE:
args = self.read_regs(AP3216C_ALS_CONFIGURATION_REG, 1)[0]
args &= 0xcf
args |= value << 4
self.write_reg(AP3216C_ALS_CONFIGURATION_REG, args)
elif cmd == AP3216C_ALS_PERSIST:
args = self.read_regs(AP3216C_ALS_CONFIGURATION_REG, 1)[0]
args &= 0xf0
args |= value
self.write_reg(AP3216C_ALS_CONFIGURATION_REG, args)
elif cmd == AP3216C_ALS_LOW_THRESHOLD_L:
self.write_reg(AP3216C_ALS_THRESHOLD_LOW_L_REG, value)
elif cmd == AP3216C_ALS_LOW_THRESHOLD_H:
self.write_reg(AP3216C_ALS_THRESHOLD_LOW_H_REG, value)
elif cmd == AP3216C_ALS_HIGH_THRESHOLD_L:
self.write_reg(AP3216C_ALS_THRESHOLD_HIGH_L_REG, value)
elif cmd == AP3216C_ALS_HIGH_THRESHOLD_H:
self.write_reg(AP3216C_ALS_THRESHOLD_HIGH_H_REG, value)
elif cmd == AP3216C_PS_GAIN:
args = self.read_regs(AP3216C_PS_CONFIGURATION_REG, 1)[0]
args &= 0xf3
args |= value
self.write_reg(AP3216C_PS_CONFIGURATION_REG, args)
elif cmd == AP3216C_PS_PERSIST:
args = self.read_regs(AP3216C_PS_CONFIGURATION_REG, 1)[0]
args &= 0xfc
args |= value
self.write_reg(AP3216C_PS_CONFIGURATION_REG, args)
elif cmd == AP3216C_PS_LOW_THRESHOLD_L:
self.write_reg(AP3216C_PS_THRESHOLD_LOW_L_REG, value)
elif cmd == AP3216C_PS_LOW_THRESHOLD_H:
self.write_reg(AP3216C_PS_THRESHOLD_LOW_H_REG, value)
elif cmd == AP3216C_PS_HIGH_THRESHOLD_L:
self.write_reg(AP3216C_PS_THRESHOLD_HIGH_L_REG, value)
elif cmd == AP3216C_PS_HIGH_THRESHOLD_H:
self.write_reg(AP3216C_PS_THRESHOLD_HIGH_H_REG, value)
#This function gets parameter of ap3216c sensor
#@param cmd the parameter cmd of device
#@param value to get value in cmd register
#@return the getting parameter status,RT_EOK reprensents getting successfully.
def ap3216c_get_param(self, cmd):
if cmd == AP3216C_SYSTEM_MODE:
value = self.read_regs(AP3216C_SYS_CONFIGURATION_REG, 1)[0]
elif cmd == AP3216C_INT_PARAM:
value = self.read_regs(AP3216C_SYS_INT_CLEAR_MANNER_REG, 1)[0]
elif cmd == AP3216C_ALS_RANGE:
value = self.read_regs(AP3216C_ALS_CONFIGURATION_REG, 1)[0]
temp = (value & 0xff) >> 4
value = temp
elif cmd == AP3216C_ALS_PERSIST:
temp = self.read_regs(AP3216C_ALS_CONFIGURATION_REG, 1)[0]
temp = value & 0x0f
value = temp
elif cmd == AP3216C_ALS_LOW_THRESHOLD_L:
value = self.read_regs(AP3216C_ALS_THRESHOLD_LOW_L_REG, 1)[0]
elif cmd == AP3216C_ALS_LOW_THRESHOLD_H:
value = self.read_regs(AP3216C_ALS_THRESHOLD_LOW_H_REG, 1)[0]
elif cmd == AP3216C_ALS_HIGH_THRESHOLD_L:
value = self.read_regs(AP3216C_ALS_THRESHOLD_HIGH_L_REG, 1)[0]
elif cmd == AP3216C_ALS_HIGH_THRESHOLD_H:
value = self.read_regs(AP3216C_ALS_THRESHOLD_HIGH_H_REG, 1)[0]
elif cmd == AP3216C_PS_GAIN:
temp = self.read_regs(AP3216C_PS_CONFIGURATION_REG, 1)[0]
value = (temp & 0xc) >> 2
elif cmd == AP3216C_PS_PERSIST:
temp = self.read_regs(AP3216C_PS_CONFIGURATION_REG, 1)[0]
value = temp & 0x3
elif cmd == AP3216C_PS_LOW_THRESHOLD_L:
value = self.read_regs(AP3216C_PS_THRESHOLD_LOW_L_REG, 1)[0]
elif cmd == AP3216C_PS_LOW_THRESHOLD_H:
value = self.read_regs(AP3216C_PS_THRESHOLD_LOW_H_REG, 1)[0]
elif cmd == AP3216C_PS_HIGH_THRESHOLD_L:
value = self.read_regs(AP3216C_PS_THRESHOLD_HIGH_L_REG, 1)[0]
elif cmd == AP3216C_PS_HIGH_THRESHOLD_H:
value = self.read_regs(AP3216C_PS_THRESHOLD_HIGH_H_REG, 1)[0]
return value
def getData(self):
ap3216c_dict = {'brightness': 0, 'ir': 0, 'ps': 0}
brightness = self.ap3216c_read_ambient_light()
ir_data = self.ap3216c_read_ir_data()
ps_data = self.ap3216c_read_ps_data()
ap3216c_dict['brightness'] = brightness
ap3216c_dict['ir'] = ir_data
ap3216c_dict['ps'] = ps_data
return ap3216c_dict
# 获取光照强度值
def getIlluminance(self):
if not self._i2cDev:
raise ValueError("i2cObj is not initialized")
return self.ap3216c_read_ambient_light()
# 获取接近状态:接近返回True,否则返回False
def isProximate(self):
if not self._i2cDev:
raise ValueError("i2cObj is not initialized")
ps = self.ap3216c_read_ps_data()
if ((ps >> 15) & 1):
return True
else:
return False
if __name__ == "__main__":
'''
The below i2c configuration is needed in your board.json.
"ap3216c": {
"type": "I2C",
"port": 1,
"addrWidth": 7,
"freq": 100000,
"mode": "master",
"devAddr": 30
}
'''
print("Testing ap3216c ...")
i2cDev = I2C()
i2cDev.open("ap3216c")
ap3216cDev = AP3216C(i2cDev)
illuminance = ap3216cDev.getIlluminance()
print("The illuminance is:", illuminance)
proxi = ap3216cDev.isProximate()
print("The proximity state is", proxi)
data = ap3216cDev.getData()
print("The total datais: ", data)
i2cDev.close()
del ap3216cDev
print("Test ap3216c done!")
{
"name": "haasedu",
"version": "1.0.0",
"io": {
"led": {
"type": "GPIO",
"port": 36,
"dir": "output",
"pull": "pullup"
},
"ap3216c": {
"type": "I2C",
"port": 1,
"addrWidth": 7,
"freq": 100000,
"mode": "master",
"devAddr": 30
},
"mpu6050": {
"type": "I2C",
"port": 1,
"addrWidth": 7,
"freq": 100000,
"mode": "master",
"devAddr": 105
},
"si7006": {
"type": "I2C",
"port": 1,
"addrWidth": 7,
"freq": 400000,
"mode": "master",
"devAddr": 64
},
"spl06": {
"type": "I2C",
"port": 1,
"addrWidth": 7,
"freq": 400000,
"mode": "master",
"devAddr": 119
},
"qmc5883": {
"type": "I2C",
"port": 1,
"addrWidth": 7,
"freq": 400000,
"mode": "master",
"devAddr": 13
},
"GPIO2": {
"type": "GPIO",
"port": 2,
"dir": "irq",
"pull": "pullup",
"intMode": "rising"
},
"GPIO3": {
"type": "GPIO",
"port": 3,
"dir": "output",
"pull": "pullup"
},
"GPIO4": {
"type": "GPIO",
"port": 4,
"dir": "output",
"pull": "pullup"
},
"GPIO5": {
"type": "GPIO",
"port": 5,
"dir": "output",
"pull": "pullup"
},
"GPIO6": {
"type": "GPIO",
"port": 6,
"dir": "output",
"pull": "pullup"
},
"GPIO7": {
"type": "GPIO",
"port": 7,
"dir": "output",
"pull": "pullup"
},
"GPIO19": {
"type": "GPIO",
"port": 19,
"dir": "output",
"pull": "pullup"
},
"GPIO22": {
"type": "GPIO",
"port": 22,
"dir": "output",
"pull": "pullup"
},
"KEY_1": {
"type": "GPIO",
"port": 23,
"dir": "irq",
"pull": "pullup",
"intMode": "rising"
},
"KEY_2": {
"type": "GPIO",
"port": 20,
"dir": "irq",
"pull": "pullup",
"intMode": "rising"
},
"KEY_3": {
"type": "GPIO",
"port": 21,
"dir": "irq",
"pull": "pullup",
"intMode": "rising"
},
"KEY_4": {
"type": "GPIO",
"port": 26,
"dir": "irq",
"pull": "pullup",
"intMode": "rising"
},
"I2C1": {
"type": "I2C",
"port": 1,
"addrWidth": 7,
"freq": 100000,
"mode": "master",
"devAddr": 60
},
"led_r": {
"type": "GPIO",
"port": 36,
"dir": "output",
"pull": "pullup"
},
"led_g": {
"type": "GPIO",
"port": 35,
"dir": "output",
"pull": "pullup"
},
"led_b": {
"type": "GPIO",
"port": 34,
"dir": "output",
"pull": "pullup"
},
"SPI0": {
"type": "SPI",
"port": 0,
"mode": "master",
"freq": 2000000
},
"oled_spi": {
"type": "SPI",
"port": 1,
"mode": "master",
"freq": 26000000
},
"oled_dc": {
"type": "GPIO",
"port": 28,
"dir": "output",
"pull": "pullup"
},
"oled_res": {
"type": "GPIO",
"port": 30,
"dir": "output",
"pull": "pullup"
},
"serial2": {
"type": "UART",
"port": 2,
"dataWidth": 8,
"baudRate": 115200,
"stopBits": 1,
"flowControl": "disable",
"parity": "none"
}
},
"debugLevel": "ERROR",
"repl": "disable"
}
\ No newline at end of file
from aliyunIoT import Device # aliyunIoT组件是连接阿里云物联网平台的组件
import utime
import ap3216c
from driver import I2C
from driver import GPIO
import netmgr as nm
import ujson
# 三元组信息
productKey = "产品密钥"
deviceName = "设备名称"
deviceSecret = "设备密钥"
# Wi-Fi SSID和Password设置
wifiSsid = "请填写您的路由器名称"
wifiPassword = "请填写您的路由器密码"
adcDev = 0
ledDev = 0
lightness = 0
# 物联网平台连接标志位
iot_connected = False
device = None
on_connected = False
# EDU K1上灯的初始化
def ledInit():
global ledDev
ledDev = GPIO()
ledDev.open("led")
# 点亮灯
def ledOn():
global ledDev
ledDev.write(1)
# 熄灭灯
def ledOff():
global ledDev
ledDev.write(0)
# ap3216c光照传感器初始化
def ap3216c_init():
global ap3216cDev
i2cDev = I2C()
i2cDev.open("ap3216c")
ap3216cDev = ap3216c.AP3216C(i2cDev)
print("ap3216c init finished")
# ap3216c光照传感器亮度获取
def ap3216c_light():
global ap3216cDev,lightness
lightness = ap3216cDev.getIlluminance()
# 关闭ap3216c光照传感器
def ap3216c_close():
global ap3216cDev
i2cDev.close()
del ap3216cDev
# 上报光照强度数据
def show_lightness():
global lightness,i_light,last_light,system_reset
i_light = int(lightness)
if system_reset == 1:
print("system first come")
system_reset = 0 # 系统第一次启动
report_light_data(i_light)
elif (i_light >= 100 and last_light < 100) or (i_light < 100 and last_light >= 100):
report_light_data(i_light) # 每次采样值是跨阈值100就上报
print("light has change")
else:
print('no need report')
if i_light < 10:
T_str = "亮度:" + str(round(i_light, 1))
elif i_light >= 10 and i_light < 100:
T_str = "亮度:" + str(round(i_light, 2))
elif i_light >= 100 and i_light < 1000:
T_str = "亮度:" + str(round(i_light, 3))
elif i_light >= 1000 and i_light < 10000:
T_str = "亮度:" + str(round(i_light, 4))
print(T_str)
last_light = i_light
# 等待Wi-Fi成功连接到路由器
def get_wifi_status():
nm.init()
nm.disconnect()
wifi_connected = nm.getStatus()
nm.connect(wifiSsid,wifiPassword)
while True :
if wifi_connected:
break
else:
wifi_connected = nm.getStatus()
utime.sleep(0.5)
print("Wi-Fi connected")
print('DeviceIP:' + nm.getInfo()['ip'])
# 物联网平台连接成功的回调函数
def on_connect(data):
global iot_connected
iot_connected = True
# 接收到到物联网服务端平台数据信息的回调函数
def on_props(request):
#服务端返回的json转换成dict
payload = ujson.loads(request['params'])
#获取dict中的灯物模型状态字段
stat = payload["onoff"]
if stat == 1:
print('led on')
ledOn()
else:
print('led off')
ledOff()
utime.sleep_ms(200)
# 连接物联网平台
def connect_lk(productKey, deviceName, deviceSecret):
global device, iot_connected
# 初始化linkkit sdk
key_info = {
'region': 'cn-shanghai',
'productKey': productKey,
'deviceName': deviceName,
'deviceSecret': deviceSecret,
'keepaliveSec': 60
}
# 将三元组信息设置到iot组件中
device = Device()
# 设定连接到物联网平台的回调函数,如果连接物联网平台成功,则调用on_connect函数
device.on(Device.ON_CONNECT, on_connect)
device.on(Device.ON_PROPS, on_props)
# 启动连接阿里云物联网平台过程
device.connect(key_info)
# 等待设备成功连接到物联网平台
while(True):
if iot_connected:
print('物联网平台连接成功')
break
else:
print('sleep for 1 s')
utime.sleep(1)
def report_light_data(l_data):
# 生成上报到物联网平台的属性值字串
upload_data = {'params': ujson.dumps({
'Brightness': l_data,
})
}
print("uploading data to the cloud:", upload_data)
# 上传光强度信息到物联网平台
device.postProps(upload_data)
def main():
global i_light,last_light,system_reset
i_light = 0
last_light = 0
system_reset = 1
get_wifi_status()
connect_lk(productKey, deviceName, deviceSecret)
ap3216c_init()
ledInit()
while True:
ap3216c_light()
show_lightness() #光照信息
utime.sleep(1.5) #每隔1.5s去刷新光照信息
if __name__ == '__main__':
main()
# 车辆定位系统
&emsp;&emsp;
下图是本案例除硬件连线外的3步导学,每个步骤中实现的功能请参考图中的说明。
<div align="center">
<img src=./../../../images/2_车辆定位系统_步骤概述.jpg width=60%/>
</div>
## 1、简介
&emsp;&emsp;
近年来,全球掀起了一股自动驾驶热潮,无论是传统车企,还是科技企业,甚至很多初创公司都竞相加入这一行业赛道。进入2021年,自动驾驶热度不减,且“吸金”不断,据不完全统计,从今年年初至今,自动驾驶行业投融资事件超过50起,投融资金额近1000亿元,已超过2018年,达到历年最高值。
### 1.1、背景知识
#### 1.1.1、卫星定位系统
&emsp;&emsp;
定位系统是以确定空间位置为目标而构成的相互关联的一个集合体或装置(部件)。这个系统可以保证在任意时刻,地球上任意一点都可以同时观测到至少4颗卫星,以保证卫星可以采集到该观测点的经纬度和高度,以便实现导航、定位、授时等功能。这项技术可以用来引导飞机、船舶、车辆以及个人,安全、准确地沿着选定的路线,准时到达目的地。
&emsp;&emsp;
目前主流定位系有除美国的GPS,中国的北斗卫星导航系统、欧盟的伽利略卫星导航系统、俄罗斯全球导航卫星系统等。
* GPS
&emsp;&emsp;
GPS是美国第二代卫星导航系统。按目前的方案,GPS的空间部分使用24颗高度约2.02万千米的卫星组成卫星星座。24颗卫星均为近圆形轨道,运行周期约为11小时58分,分布在6个轨道面上(每轨道面4颗),轨道倾角为55度。卫星的分布使得在全球任何地方、任何时间都可观测到4颗以上的卫星,并能保持良好定位解算精度的几何图形。这就提供了在时间上连续的全球导航能力。
&emsp;&emsp;
想了解GPS详细信息请参考[文章]((https://baike.baidu.com/item/%E5%85%A8%E7%90%83%E5%AE%9A%E4%BD%8D%E7%B3%BB%E7%BB%9F/1240960?fromtitle=GPS&fromid=214654&fr=aladdin))。
* 北斗卫星导航系统
&emsp;&emsp;
中国北斗卫星导航系统(英文名称:BeiDou Navigation Satellite System,简称BDS)是中国自行研制的全球卫星导航系统,也是继GPS、GLONASS之后的第三个成熟的卫星导航系统。北斗卫星导航系统(BDS)和美国GPS、俄罗斯GLONASS、欧盟GALILEO,是联合国卫星导航委员会已认定的供应商。
&emsp;&emsp;
北斗卫星导航系统由空间段、地面段和用户段三部分组成,可在全球范围内全天候、全天时为各类用户提供高精度、高可靠定位、导航、授时服务,并且具备短报文通信能力,已经初步具备区域导航、定位和授时能力,定位精度为分米、厘米级别,测速精度0.2米/秒,授时精度10纳秒。
&emsp;&emsp;
全球范围内已经有137个国家与北斗卫星导航系统签下了合作协议。随着全球组网的成功,北斗卫星导航系统未来的国际应用空间将会不断扩展。
&emsp;&emsp;
想了解北斗卫星导航系统详细信息请参考[文章]((https://baike.baidu.com/item/%E5%8C%97%E6%96%97%E5%8D%AB%E6%98%9F%E5%AF%BC%E8%88%AA%E7%B3%BB%E7%BB%9F/10390403?fr=aladdin))
#### 1.1.2、基站定位
&emsp;&emsp;
除了像GPS这种卫星定位之外,我们身边的设备如:智能手机、智能手表、穿戴设备等能联网的设备都可以用来定位,通过连接运营商(电信、联通、移动)基站来定位的方式称之为基站定位,或者又称之为移动位置服务(LBS--Location Based Service)。
### 1.2、准备
&emsp;&emsp;
完成本案例需要准备如下硬件:
* [HaaS EDU K1开发板](https://haas.iot.aliyun.com/solution/detail/hardware?versionId=800C5AB3B8A4A88800000001&dataId=800C5AB3B8A4A888) 1台
* [GNSS定位模块 - HT2828Z3G5L](https://haas.iot.aliyun.com/solution/detail/hardware?versionId=800C7C08112B0A0700000002&dataId=800C7C08112B0A07) 1个
* 杜邦线若干
如下图实物图所示:
<div align="center">
<img src=./../../../images/5_3_车辆定位硬件连接_HaaS_EDU_K1.png width=90%/>
</div>
<br>
## 2、物联网平台开发
&emsp;&emsp;
整个过程包含以下3个步骤:
1. 创建产品(设备模型)
2. 定义产品功能(物模型)
3. 创建设备及获取三元组
<br>
&emsp;&emsp;
对于第一次使用物联网平台的读者,需要开通实例以使用物联网平台的功能。这里可以使用免费的公共实例进行开发。
&emsp;&emsp;
[物联网平台](https://iot.console.aliyun.com/lk/summary/new)中,左上角选择“华东2-上海”,点击“公共实例”,即可开通。
<div align="center">
<img src=./../../../images/5_3_开通公共实例.png
width=100%/>
</div>
### 2.1、创建产品(设备模型)
&emsp;&emsp;
点击上图中的“公共实例”,即可进入[控制台](https://iot.console.aliyun.com/lk/summary/new)进行产品创建。然后,点击“创建产品”按钮,即可进入[新建产品页面](https://iot.console.aliyun.com/product)
<div align="center">
<img src=./../../../images/5_3_公共实例控制台.png
width=100%/>
</div>
&emsp;&emsp;
进入[新建产品页面](https://iot.console.aliyun.com/product)后,设定“产品名称”,这里我们命名为“**车辆定位器**”,读者也可以根据自己的喜好来命名。
&emsp;&emsp;
在“所属品类”中,选择“标准品类”。“标准品类”收录了一些常用的设备模型,其会为我们定义好后续的产品功能(物模型)。我们也可以选择“自定义品类”,来自己定义物模型。在右侧栏中搜索并选择“定位器”。
<div align="center">
<img src=./../../../images/5_3_创建标准品类产品.png
width=100%/>
</div>
&emsp;&emsp;
产品的节点类型选择“直连设备”,数据格式选择“ICA标准数据格式”,检验类型和认证方式选择默认设定即可。开发者可根据自己的需求在“产品描述”页面添加针对此产品的描述。
&emsp;&emsp;
对于 HaaS EDU K1 等搭载 Wi-Fi 的设备而言,联网方式可以选择“Wi-Fi”。
<div align="center">
<img src=./../../../images/5_3_创建WIFI产品.png
width=40%/>
</div>
&emsp;&emsp;
选择之后,点击“确认”按钮,即可完成产品创建。返回“产品”页面之后可以看到产品类表中会出现刚刚创建的“车辆定位器”的产品,如下图所示。
<div align="center">
<img src=./../../../images/5_3_产品列表车辆定位器.png width=100%/>
</div>
<br>
### 2.2、定义产品功能(物模型)
&emsp;&emsp;
点击上图中的“查看”按钮,即可看到产品信息,Topic列表,功能定义,数据解析等跟产品相关功能的设定。点开“功能定义”标签页,可以看到设备物模型定义。
<div align="center">
<img src=./../../../images/5_3_车辆定位器产品详情.png width=100%/>
</div>
&emsp;&emsp;
因为在创建产品的过程中选择了标准的产品品类,这里会出现标准品类中自带的物模型设定,即“地理位置”。点击“查看”,我们可以看到该属性的详情。可以看到,该属性的数据类型是一个结构体(struct),其成员描述了设备的精度(浮点)、维度(浮点)、海拔(浮点)、坐标系统(枚举量,1:WGS_84/2:GCJ_02)
<div align="center">
<img src=./../../../images/5_3_属性详情.png width=100%/>
</div>
&emsp;&emsp;
定义好物模型后,记得点击“发布”以使变更生效。
<div align="center">
<img src=./../../../images/5_3_发布物模型.png width=100%/>
</div>
&emsp;&emsp;
产品及其物模型创建完成后就可以创建这个产品的设备了。
<br>
### 2.3、创建设备及获取三元组
&emsp;&emsp;
点击左侧栏中“产品“,回到产品列表。在产品列表页面中,点击“管理设备”,就会进到设备管理页面。
<div align="center">
<img src=./../../../images/5_3_产品列表管理设备.png width=100%/>
</div>
&emsp;&emsp;
在“设备”页面点击“批量添加”按钮,如下图所示。填入需要生成的设备数量。例如图中物联网平台会为我们生成3台设备,以及随机的deviceName。
<div align="center">
<img src=./../../../images/5_3_批量添加.png width=100%/>
</div>
&emsp;&emsp;
批量生成的设备如下
<div align="center">
<img src=./../../../images/5_3_设备列表.png width=100%/>
</div>
&emsp;&emsp;
设备添加完成后,点击“前往查看”按钮,就可以看到此设备的详细信息了。
<div align="center">
<img src=./../../../images/5_3_设备端详细信息.png width=100%/>
</div>
<br>
&emsp;&emsp;
如下图所示,点击“查看”按钮,就可以看到设备的三元组信息,三元组是物联网设备端和物联网云端设备相关联的唯一标识符,在设备端连接云端的时候会使用三元组信息和云端进行鉴权,鉴权通过之后云端会认为设备已激活并上线。
<div align="center">
<img src=./../../../images/5_3_三元组信息.png width=100%/>
</div>
<br>
## 3、设备端开发
### 3.1、开发环境
&emsp;&emsp;
在进行下一步之前请确保HaaS EDU K1开发环境已经搭建完毕。详情请参考[HaaS EDU K1开发环境](../../../startup/HaaS_EDU_K1_startup.md)的说明。
<br>
### 3.2、创建解决方案
&emsp;&emsp;
如下图所示,在Haas Studio中创建项目。先选择左侧的“开发板型号”再从右侧的案例中选择“车辆定位系统”案例点击“立即创建”即可。
<div align="center">
<img src=./../../../images/HaaS_Studio_创建工程示范.png width=100%/>
</div>
<br>
> Python脚本的详细说明请参考脚本内嵌的文字版注释
>
&emsp;&emsp;
之后,在代码中填入对应的信息
1. **填写Wi-Fi名称及密码**
&emsp;&emsp;
在main.py中,填写可用的Wi-Fi名称及密码。
``` python
# wifi连接的的ssid和pwd定义
wifiSsid = "请填写您的路由器名称"
wifiPassword = "请填写您的路由器密码"
```
2. **修改设备端三元组**
&emsp;&emsp;
在main.py中,填写创建的设备三元组信息。关于设备三元组的获取,请参考[获取设备三元组](./README.md "获取设备三元组")中的步骤。
``` python
# 三元组信息
productKey = "产品key"
deviceName = "设备名称"
deviceSecret = "设备密钥"
```
<br>
## 4、运行结果
### 4.1、本地查看
&emsp;&emsp;
推送此脚本到HaaS EDU K1之后,串口会输出获取到的定位信息,同时按照脚本中的逻辑,当定位信息发生变化时,会将更新的定位信息上报至物联网平台。如下面这段串口日志所示。
```log
物联网平台连接成功
sleep for 2s
[0.0, 'W'] [0.0, 'N'] 0.0
bytearray(b'$GNGGA,,,,,,0,00,25.5,,,,,,*64\r\n$GNGLL,,,,,,V,M*79\r\n$GNGSA,A,1,,,,,,,,,,,,,25.5,25.5,25.5,1*01\r\n$GNG')
[0.0, 'W'] [0.0, 'N'] 0.0
[0.0, 'W'] [0.0, 'N'] 0.0
bytearray(b'SA,A,1,,,,,,,,,,,,,25.5,25.5,25.5,4*04\r\n$GPGSV,1,1,00,0*65\r\n$BDGSV,1,1,00,0*74\r\n$GNRMC,,V,,,,,,,,,,M')
[0.0, 'W'] [0.0, 'N'] 0.0
[0.0, 'W'] [0.0, 'N'] 0.0
bytearray(b',V*34\r\n$GNVTG,,,,,,,,,M*2D\r\n$GNZDA,,,,,,*56\r\n$GPTXT,01,01,01,ANTENNA OPEN*25\r\n\r\n$GNRMC,,V,,,,,,,,,,M')
[0.0, 'W'] [0.0, 'N'] 0.0
[0.0, 'W'] [0.0, 'N'] 0.0
bytearray(b',V*34\r\n$GNVTG,,,,,,,,,M*2D\r\n$GNZDA,,,,,,*56\r\n$GPTXT,01,01,01,ANTENNA OPEN*25\r\n$GNGGA,,,,,,0,00,25.5,')
[0.0, 'W'] [0.0, 'N'] 0.0
[0.0, 'W'] [0.0, 'N'] 0.0
```
&emsp;&emsp;
出现此段日志时,说明定位信息上报成功,读者此时可以前往物联网平台查看设备的实时定位数据。
### 4.2、在物联网平台上查看设备数据
&emsp;&emsp;
当设备启动成功并第一次连接到物联网平台之后,物联网平台上对应的设备状态会从”未激活状态“变为”上线“。点击“物模型数据”标签页,能够看到设备上报到物联网平台的属性值。
<div align="center">
<img src=./../../../images/5_3_LP显示定位数据.png width=100%/>
</div>
&emsp;&emsp;
点击卡片上的“查看数据”,可以看到完整的历史数据。
<div align="center">
<img src=./../../../images/5_3_LP显示定位数据详情.png width=100%/>
</div>
&emsp;&emsp;
此时,如果设备上报新的地理位置信息,则物联网平台上能够实时显示。至此,定位数据上云完成。
<br>
## 5、物联网应用开发(定位数据可视化)
&emsp;&emsp;
在前面的章节中,我们已经成功将地位信息实时上传到了云端。本节中,我们将介绍如何快速实现将设备的位置实时显示在地图中。
&emsp;&emsp;
IoT Studio 提供了应用快速开发的能力,可以很方便地与物联网平台进行联动。本节的开发工作也将围绕 IoT Studio展开。
<br>
### 5.1、新建“普通项目”
&emsp;&emsp;
打开[IoT Studio官网](https://studio.iot.aliyun.com/),在项目管理中新建一个空白项目,如下图所示,将此项目命名为“车辆实时定位系统”。
<div align="center">
<img src=./../../../images/5_3_普通项目.png width=100%/>
<img src=./../../../images/5_3_创建空白项目.png width=100%/>
</div>
<br>
### 5.2、关联产品
&emsp;&emsp;
为了使本项目能够获取到目标设备的定位信息,我们首先需要将该项目和我们在前一节创建的产品“车辆定位器”绑定。
&emsp;&emsp;
在项目控制台,点击左侧的“产品”,点击“关联物联网平台产品”。此时可以看见我们创建的“车辆定位器”。点击选中,并勾选“关联产品同时关联其下所有设备”,以便该项目可以访问到所有设备的定位信息。
<div align="center">
<img src=./../../../images/5_3_关联产品.png width=100%/>
</div>
### 5.3、创建“Web应用”
&emsp;&emsp;
在项目控制台,点击左侧的“主页”,在新项目的首页新建一个Web应用,命名为“车辆实时定位监控”。
<div align="center">
<img src=./../../../images/5_3_Web应用.png width=100%/>
</div>
&emsp;&emsp;
Web应用创建成功后自动跳转到应用界面设计页面。点击左侧栏中的“组件”按钮图标,就可以看到可用的组件列表。各组件的说明请参考[IoT Studio组件说明](https://help.aliyun.com/document_detail/125196.html)
&emsp;&emsp;
为了能够将设备显示在地图上,我们选用“设备地图”组件。鼠标悬停在该组件上以查看详细信息。
<div align="center">
<img src=./../../../images/5_3_设备地图.png width=100%/>
</div>
&emsp;&emsp;
将“设备地图”组件拖拽至画布,并调整大小。此时,地图上还未能正确显示设备。我们需要点击右侧“编辑设备地图”。
<div align="center">
<img src=./../../../images/5_3_界面布局.png width=100%/>
</div>
&emsp;&emsp;
点击右侧“编辑设备地图”后,我们可以在右侧“选择设备”中,选择我们刚刚和项目绑定的“车辆定位器”。点击确定。
<div align="center">
<img src=./../../../images/5_3_选择产品.png width=100%/>
</div>
&emsp;&emsp;
此时,地图上已经出现了定位点,正是我们之前上线的设备。在右侧选择“车辆定位器”,可以看到绑定的所有设备。点击对应设备可以在地图上定位到每个设备的具体位置。至此,“设备地图“配置完成,点击页面右上角”√“保存配置。
<div align="center">
<img src=./../../../images/5_3_选择设备.png width=100%/>
</div>
<br>
### 5.4、发布上线
<div align="center">
<img src=./../../../images/5_3_发布和上线.png width=100%/>
</div>
&emsp;&emsp;
回到WEB应用页面,可以看到地图上的设备定位已经正常显示,可以进行发布。在发布之前可以点击上图的“预览”查看应用的实际运行效果。实际运行效果如下图所示:
<div align="center">
<img src=./../../../images/5_3_预览.png width=100%/>
</div>
<br>
&emsp;&emsp;
这样我们就完成了一个车辆实时定位系统从设备端定位信息获取、物联网云平台开发及物联网应用开发全链路的开发。
&emsp;&emsp;
如果想要学习车辆定位系统案例更详细的操作步骤,请参考“[车辆定位系统详解](https://gitee.com/haasedu/haasedu/blob/release_2.0/5-%E6%99%BA%E6%85%A7%E5%9F%8E%E5%B8%82/%E5%9C%BA%E6%99%AF3-%E8%BD%A6%E8%BE%86%E5%AE%9A%E4%BD%8D%E7%B3%BB%E7%BB%9F/README.md)”中的说明。
{
"name": "haasedu",
"version": "1.0.0",
"io": {
"gnss": {
"type": "UART",
"port": 2,
"dataWidth": 8,
"baudRate": 9600,
"stopBits": 1,
"flowControl": "disable",
"parity": "none"
},
"hcho": {
"type": "ADC",
"port": 0,
"sampling": 12000000
},
"led_r": {
"type": "GPIO",
"port": 36,
"dir": "output",
"pull": "pullup"
},
"ap3216c": {
"type": "I2C",
"port": 1,
"addrWidth": 7,
"freq": 100000,
"mode": "master",
"devAddr": 30
},
"mpu6050": {
"type": "I2C",
"port": 1,
"addrWidth": 7,
"freq": 100000,
"mode": "master",
"devAddr": 105
},
"si7006": {
"type": "I2C",
"port": 1,
"addrWidth": 7,
"freq": 400000,
"mode": "master",
"devAddr": 64
},
"spl06": {
"type": "I2C",
"port": 1,
"addrWidth": 7,
"freq": 400000,
"mode": "master",
"devAddr": 119
},
"qmc5883": {
"type": "I2C",
"port": 1,
"addrWidth": 7,
"freq": 400000,
"mode": "master",
"devAddr": 13
},
"ADC2": {
"type": "ADC",
"port": 0,
"sampling": 12000000
},
"GPIO2": {
"type": "GPIO",
"port": 2,
"dir": "irq",
"pull": "pullup",
"intMode": "rising"
},
"GPIO3": {
"type": "GPIO",
"port": 3,
"dir": "output",
"pull": "pullup"
},
"GPIO4": {
"type": "GPIO",
"port": 4,
"dir": "output",
"pull": "pullup"
},
"GPIO5": {
"type": "GPIO",
"port": 5,
"dir": "output",
"pull": "pullup"
},
"GPIO6": {
"type": "GPIO",
"port": 6,
"dir": "output",
"pull": "pullup"
},
"GPIO7": {
"type": "GPIO",
"port": 7,
"dir": "output",
"pull": "pullup"
},
"GPIO19": {
"type": "GPIO",
"port": 19,
"dir": "output",
"pull": "pullup"
},
"GPIO22": {
"type": "GPIO",
"port": 22,
"dir": "output",
"pull": "pullup"
},
"KEY_1": {
"type": "GPIO",
"port": 23,
"dir": "irq",
"pull": "pullup",
"intMode": "rising"
},
"KEY_2": {
"type": "GPIO",
"port": 20,
"dir": "irq",
"pull": "pullup",
"intMode": "rising"
},
"KEY_3": {
"type": "GPIO",
"port": 21,
"dir": "irq",
"pull": "pullup",
"intMode": "rising"
},
"KEY_4": {
"type": "GPIO",
"port": 26,
"dir": "irq",
"pull": "pullup",
"intMode": "rising"
},
"I2C1": {
"type": "I2C",
"port": 1,
"addrWidth": 7,
"freq": 100000,
"mode": "master",
"devAddr": 60
},
"led_g": {
"type": "GPIO",
"port": 35,
"dir": "output",
"pull": "pullup"
},
"led_b": {
"type": "GPIO",
"port": 34,
"dir": "output",
"pull": "pullup"
},
"SPI0": {
"type": "SPI",
"port": 0,
"mode": "master",
"freq": 2000000
},
"oled_spi": {
"type": "SPI",
"port": 1,
"mode": "master",
"freq": 26000000
},
"oled_dc": {
"type": "GPIO",
"port": 28,
"dir": "output",
"pull": "pullup"
},
"oled_res": {
"type": "GPIO",
"port": 30,
"dir": "output",
"pull": "pullup"
},
"serial2": {
"type": "UART",
"port": 2,
"dataWidth": 8,
"baudRate": 115200,
"stopBits": 1,
"flowControl": "disable",
"parity": "none"
}
},
"debugLevel": "ERROR",
"repl": "disable"
}
\ No newline at end of file
from driver import UART
from micropyGNSS import MicropyGNSS
class Gnss(object):
def __init__(self, uartObj):
self.uartObj = None
if not isinstance(uartObj, UART):
raise ValueError("parameter is not a GPIO object")
# 初始化定位模组串口
self.uartObj = uartObj
self.gnss = MicropyGNSS(location_formatting='dd')
def getLocation(self):
if self.uartObj is None:
raise ValueError("invalid UART object")
# 创建定位信息解析器
sentence = bytearray(100)
recvsize = self.uartObj.read(sentence)
if(recvsize):
print(sentence)
sentence = sentence.decode()
# 解析地理位置信息
for c in sentence:
self.gnss.update(c)
print(self.gnss.longitude, self.gnss.latitude, self.gnss.altitude)
return self.gnss
from aliyunIoT import Device # aliyunIoT组件是连接阿里云物联网平台的组件
import utime
import netmgr as nm
from driver import UART
import gnss
import ujson
uartDev = 0
gnssDev = 0
# wifi连接的的ssid和pwd定义
wifiSsid = "请填写您的路由器名称"
wifiPassword = "请填写您的路由器密码"
# 物联网平台相关的key和serect定义
productKey = "产品key"
deviceName = "设备名称"
deviceSecret = "设备密钥"
# 物联网平台连接状态标识
iot_connected = False
# 物联网设备实例
device = None
# 物联网平台连接成功时触发 on_connect
def on_connect():
global on_connected
on_connected = True
def gnssInit():
global gnssDev,uartDev
# 初始化定位模组串口
uartDev = UART()
uartDev.open('gnss')
# 创建定位信息解析器
gnssDev = gnss.Gnss(uartDev)
# 等待Wi-Fi成功连接到路由器
def get_wifi_status():
nm.init()
wifi_connected = nm.getStatus()
print("start to connect " , wifiSsid)
nm.connect(wifiSsid, wifiPassword) # 连接到指定的路由器(路由器名称为wifiSsid, 密码为:wifiPassword)
while True :
if wifi_connected == 5: # nm.getStatus()返回5代表连线成功
break
else:
wifi_connected = nm.getStatus() # 获取Wi-Fi连接路由器的状态信息
utime.sleep(0.5)
# utime.sleep(5)
print("Wi-Fi connected")
print('DeviceIP:' + nm.getInfo()['ip']) # 打印Wi-Fi的IP地址信息
# 物联网平台连接成功时触发 on_connect
def on_connect(data):
global iot_connected
iot_connected = True
# 设置props 事件接收函数(当云平台向设备下发属性时)
def on_props(request):
pass
def connect_lk(productKey, deviceName, deviceSecret):
global device, iot_connected
key_info = {
'region': 'cn-shanghai',
'productKey': productKey,
'deviceName': deviceName,
'deviceSecret': deviceSecret,
'keepaliveSec': 60
}
# 将三元组信息设置到iot组件中
device = Device()
# 设定连接到物联网平台的回调函数,如果连接物联网平台成功,则调用on_connect函数
device.on(Device.ON_CONNECT, on_connect)
# 配置收到云端属性控制指令的回调函数,如果收到物联网平台发送的属性控制消息,则调用on_props函数
device.on(Device.ON_PROPS, on_props)
# 启动连接阿里云物联网平台过程
device.connect(key_info)
# 等待设备成功连接到物联网平台
while(True):
if iot_connected:
print('物联网平台连接成功')
break
else:
print('sleep for 1 s')
utime.sleep(1)
# 主程序入口
if __name__ == '__main__':
gnssInit()
# 请替换物联网平台申请到的产品和设备信息,可以参考文章:https://blog.csdn.net/HaaSTech/article/details/114360517
get_wifi_status()
connect_lk(productKey, deviceName, deviceSecret)
# 定义经纬度及海拔
longitude = 0
latitude = 0
altitude = 0
# 连续从串口种读取信息
while True:
# 串口读取定位模块语句
location = gnssDev.getLocation()
if(location):
print(location.longitude, location.latitude, location.altitude)
# 判断定位信息是否发生变化
if(longitude != location.longitude[0] or latitude != location.latitude[0] or altitude != location.altitude):
longitude = location.longitude[0]
latitude = location.latitude[0]
altitude = location.altitude
print(longitude, latitude, altitude)
# 如果有变化,则上报地理位置信息至物联网平台
loc_data = {
'params': ujson.dumps({
'GeoLocation': {
'Longitude': longitude,
'Latitude': latitude,
'Altitude': altitude,
'CoordinateSystem': 1
}
})
}
device.postProps(loc_data)
utime.sleep(2)
device.close()
uartDev.close()
"""
# MicropyGPS - a GPS NMEA sentence parser for Micropython/Python 3.X
# Copyright (c) 2017 Michael Calvin McCoy (calvin.mccoy@protonmail.com)
# The MIT License (MIT) - see LICENSE file
"""
"""
MIT License
Copyright (c) 2017 Calvin McCoy
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
"""
# TODO:
# Time Since First Fix
# Distance/Time to Target
# More Helper Functions
# Dynamically limit sentences types to parse
from math import floor, modf
# Import utime or time for fix time handling
try:
# Assume running on MicroPython
import utime
except ImportError:
# Otherwise default to time module for non-embedded implementations
# Should still support millisecond resolution.
import time
class MicropyGNSS(object):
"""NMEA Sentence Parser. Creates object that stores all relevant GPS data and statistics.
Parses sentences one character at a time using update(). """
# Max Number of Characters a valid sentence can be (based on GGA sentence)
SENTENCE_LIMIT = 90
__HEMISPHERES = ('N', 'S', 'E', 'W')
__NO_FIX = 1
__FIX_2D = 2
__FIX_3D = 3
__DIRECTIONS = ('N', 'NNE', 'NE', 'ENE', 'E', 'ESE', 'SE', 'SSE', 'S', 'SSW', 'SW', 'WSW', 'W',
'WNW', 'NW', 'NNW')
__MONTHS = ('January', 'February', 'March', 'April', 'May',
'June', 'July', 'August', 'September', 'October',
'November', 'December')
def __init__(self, local_offset=0, location_formatting='ddm'):
"""
Setup GPS Object Status Flags, Internal Data Registers, etc
local_offset (int): Timzone Difference to UTC
location_formatting (str): Style For Presenting Longitude/Latitude:
Decimal Degree Minute (ddm) - 40° 26.767′ N
Degrees Minutes Seconds (dms) - 40° 26′ 46″ N
Decimal Degrees (dd) - 40.446° N
"""
#####################
# Object Status Flags
self.sentence_active = False
self.active_segment = 0
self.process_crc = False
self.gps_segments = []
self.crc_xor = 0
self.char_count = 0
self.fix_time = 0
#####################
# Sentence Statistics
self.crc_fails = 0
self.clean_sentences = 0
self.parsed_sentences = 0
#####################
# Logging Related
self.log_handle = None
self.log_en = False
#####################
# Data From Sentences
# Time
self.timestamp = [0, 0, 0]
self.date = [0, 0, 0]
self.local_offset = local_offset
# Position/Motion
self._latitude = [0, 0.0, 'N']
self._longitude = [0, 0.0, 'W']
self.coord_format = location_formatting
self.speed = [0.0, 0.0, 0.0]
self.course = 0.0
self.altitude = 0.0
self.geoid_height = 0.0
# GPS Info
self.satellites_in_view = 0
self.satellites_in_use = 0
self.satellites_used = []
self.last_sv_sentence = 0
self.total_sv_sentences = 0
self.satellite_data = dict()
self.hdop = 0.0
self.pdop = 0.0
self.vdop = 0.0
self.valid = False
self.fix_stat = 0
self.fix_type = 1
########################################
# Coordinates Translation Functions
########################################
@property
def latitude(self):
"""Format Latitude Data Correctly"""
if self.coord_format == 'dd':
decimal_degrees = self._latitude[0] + (self._latitude[1] / 60)
return [decimal_degrees, self._latitude[2]]
elif self.coord_format == 'dms':
minute_parts = modf(self._latitude[1])
seconds = round(minute_parts[0] * 60)
return [self._latitude[0], int(minute_parts[1]), seconds, self._latitude[2]]
else:
return self._latitude
@property
def longitude(self):
"""Format Longitude Data Correctly"""
if self.coord_format == 'dd':
decimal_degrees = self._longitude[0] + (self._longitude[1] / 60)
return [decimal_degrees, self._longitude[2]]
elif self.coord_format == 'dms':
minute_parts = modf(self._longitude[1])
seconds = round(minute_parts[0] * 60)
return [self._longitude[0], int(minute_parts[1]), seconds, self._longitude[2]]
else:
return self._longitude
########################################
# Logging Related Functions
########################################
def start_logging(self, target_file, mode="append"):
"""
Create GPS data log object
"""
# Set Write Mode Overwrite or Append
mode_code = 'w' if mode == 'new' else 'a'
try:
self.log_handle = open(target_file, mode_code)
except AttributeError:
print("Invalid FileName")
return False
self.log_en = True
return True
def stop_logging(self):
"""
Closes the log file handler and disables further logging
"""
try:
self.log_handle.close()
except AttributeError:
print("Invalid Handle")
return False
self.log_en = False
return True
def write_log(self, log_string):
"""Attempts to write the last valid NMEA sentence character to the active file handler
"""
try:
self.log_handle.write(log_string)
except TypeError:
return False
return True
########################################
# Sentence Parsers
########################################
def gprmc(self):
"""Parse Recommended Minimum Specific GPS/Transit data (RMC)Sentence.
Updates UTC timestamp, latitude, longitude, Course, Speed, Date, and fix status
"""
# UTC Timestamp
try:
utc_string = self.gps_segments[1]
if utc_string: # Possible timestamp found
hours = (int(utc_string[0:2]) + self.local_offset) % 24
minutes = int(utc_string[2:4])
seconds = float(utc_string[4:])
self.timestamp = (hours, minutes, seconds)
else: # No Time stamp yet
self.timestamp = (0, 0, 0)
except ValueError: # Bad Timestamp value present
return False
# Date stamp
try:
date_string = self.gps_segments[9]
# Date string printer function assumes to be year >=2000,
# date_string() must be supplied with the correct century argument to display correctly
if date_string: # Possible date stamp found
day = int(date_string[0:2])
month = int(date_string[2:4])
year = int(date_string[4:6])
self.date = (day, month, year)
else: # No Date stamp yet
self.date = (0, 0, 0)
except ValueError: # Bad Date stamp value present
return False
# Check Receiver Data Valid Flag
if self.gps_segments[2] == 'A': # Data from Receiver is Valid/Has Fix
# Longitude / Latitude
try:
# Latitude
l_string = self.gps_segments[3]
lat_degs = int(l_string[0:2])
lat_mins = float(l_string[2:])
lat_hemi = self.gps_segments[4]
# Longitude
l_string = self.gps_segments[5]
lon_degs = int(l_string[0:3])
lon_mins = float(l_string[3:])
lon_hemi = self.gps_segments[6]
except ValueError:
return False
if lat_hemi not in self.__HEMISPHERES:
return False
if lon_hemi not in self.__HEMISPHERES:
return False
# Speed
try:
spd_knt = float(self.gps_segments[7])
except ValueError:
return False
# Course
try:
if self.gps_segments[8]:
course = float(self.gps_segments[8])
else:
course = 0.0
except ValueError:
return False
# TODO - Add Magnetic Variation
# Update Object Data
self._latitude = [lat_degs, lat_mins, lat_hemi]
self._longitude = [lon_degs, lon_mins, lon_hemi]
# Include mph and hm/h
self.speed = [spd_knt, spd_knt * 1.151, spd_knt * 1.852]
self.course = course
self.valid = True
# Update Last Fix Time
self.new_fix_time()
else: # Clear Position Data if Sentence is 'Invalid'
self._latitude = [0, 0.0, 'N']
self._longitude = [0, 0.0, 'W']
self.speed = [0.0, 0.0, 0.0]
self.course = 0.0
self.valid = False
return True
def gpgll(self):
"""Parse Geographic Latitude and Longitude (GLL)Sentence. Updates UTC timestamp, latitude,
longitude, and fix status"""
# UTC Timestamp
try:
utc_string = self.gps_segments[5]
if utc_string: # Possible timestamp found
hours = (int(utc_string[0:2]) + self.local_offset) % 24
minutes = int(utc_string[2:4])
seconds = float(utc_string[4:])
self.timestamp = (hours, minutes, seconds)
else: # No Time stamp yet
self.timestamp = (0, 0, 0)
except ValueError: # Bad Timestamp value present
return False
# Check Receiver Data Valid Flag
if self.gps_segments[6] == 'A': # Data from Receiver is Valid/Has Fix
# Longitude / Latitude
try:
# Latitude
l_string = self.gps_segments[1]
lat_degs = int(l_string[0:2])
lat_mins = float(l_string[2:])
lat_hemi = self.gps_segments[2]
# Longitude
l_string = self.gps_segments[3]
lon_degs = int(l_string[0:3])
lon_mins = float(l_string[3:])
lon_hemi = self.gps_segments[4]
except ValueError:
return False
if lat_hemi not in self.__HEMISPHERES:
return False
if lon_hemi not in self.__HEMISPHERES:
return False
# Update Object Data
self._latitude = [lat_degs, lat_mins, lat_hemi]
self._longitude = [lon_degs, lon_mins, lon_hemi]
self.valid = True
# Update Last Fix Time
self.new_fix_time()
else: # Clear Position Data if Sentence is 'Invalid'
self._latitude = [0, 0.0, 'N']
self._longitude = [0, 0.0, 'W']
self.valid = False
return True
def gpvtg(self):
"""Parse Track Made Good and Ground Speed (VTG) Sentence. Updates speed and course"""
try:
course = float(self.gps_segments[1])
spd_knt = float(self.gps_segments[5])
except ValueError:
return False
# Include mph and km/h
self.speed = (spd_knt, spd_knt * 1.151, spd_knt * 1.852)
self.course = course
return True
def gpgga(self):
"""Parse Global Positioning System Fix Data (GGA) Sentence. Updates UTC timestamp, latitude, longitude,
fix status, satellites in use, Horizontal Dilution of Precision (HDOP), altitude, geoid height and fix status"""
try:
# UTC Timestamp
utc_string = self.gps_segments[1]
# Skip timestamp if receiver doesn't have on yet
if utc_string:
hours = (int(utc_string[0:2]) + self.local_offset) % 24
minutes = int(utc_string[2:4])
seconds = float(utc_string[4:])
else:
hours = 0
minutes = 0
seconds = 0.0
# Number of Satellites in Use
satellites_in_use = int(self.gps_segments[7])
# Get Fix Status
fix_stat = int(self.gps_segments[6])
except (ValueError, IndexError):
return False
try:
# Horizontal Dilution of Precision
hdop = float(self.gps_segments[8])
except (ValueError, IndexError):
hdop = 0.0
# Process Location and Speed Data if Fix is GOOD
if fix_stat:
# Longitude / Latitude
try:
# Latitude
l_string = self.gps_segments[2]
lat_degs = int(l_string[0:2])
lat_mins = float(l_string[2:])
lat_hemi = self.gps_segments[3]
# Longitude
l_string = self.gps_segments[4]
lon_degs = int(l_string[0:3])
lon_mins = float(l_string[3:])
lon_hemi = self.gps_segments[5]
except ValueError:
return False
if lat_hemi not in self.__HEMISPHERES:
return False
if lon_hemi not in self.__HEMISPHERES:
return False
# Altitude / Height Above Geoid
try:
altitude = float(self.gps_segments[9])
geoid_height = float(self.gps_segments[11])
except ValueError:
altitude = 0
geoid_height = 0
# Update Object Data
self._latitude = [lat_degs, lat_mins, lat_hemi]
self._longitude = [lon_degs, lon_mins, lon_hemi]
self.altitude = altitude
self.geoid_height = geoid_height
# Update Object Data
self.timestamp = [hours, minutes, seconds]
self.satellites_in_use = satellites_in_use
self.hdop = hdop
self.fix_stat = fix_stat
# If Fix is GOOD, update fix timestamp
if fix_stat:
self.new_fix_time()
return True
def gpgsa(self):
"""Parse GNSS DOP and Active Satellites (GSA) sentence. Updates GPS fix type, list of satellites used in
fix calculation, Position Dilution of Precision (PDOP), Horizontal Dilution of Precision (HDOP), Vertical
Dilution of Precision, and fix status"""
# Fix Type (None,2D or 3D)
try:
fix_type = int(self.gps_segments[2])
except ValueError:
return False
# Read All (up to 12) Available PRN Satellite Numbers
sats_used = []
for sats in range(12):
sat_number_str = self.gps_segments[3 + sats]
if sat_number_str:
try:
sat_number = int(sat_number_str)
sats_used.append(sat_number)
except ValueError:
return False
else:
break
# PDOP,HDOP,VDOP
try:
pdop = float(self.gps_segments[15])
hdop = float(self.gps_segments[16])
vdop = float(self.gps_segments[17])
except ValueError:
return False
# Update Object Data
self.fix_type = fix_type
# If Fix is GOOD, update fix timestamp
if fix_type > self.__NO_FIX:
self.new_fix_time()
self.satellites_used = sats_used
self.hdop = hdop
self.vdop = vdop
self.pdop = pdop
return True
def gpgsv(self):
"""Parse Satellites in View (GSV) sentence. Updates number of SV Sentences,the number of the last SV sentence
parsed, and data on each satellite present in the sentence"""
try:
num_sv_sentences = int(self.gps_segments[1])
current_sv_sentence = int(self.gps_segments[2])
sats_in_view = int(self.gps_segments[3])
except ValueError:
return False
# Create a blank dict to store all the satellite data from this sentence in:
# satellite PRN is key, tuple containing telemetry is value
satellite_dict = dict()
# Calculate Number of Satelites to pull data for and thus how many segment positions to read
if num_sv_sentences == current_sv_sentence:
# Last sentence may have 1-4 satellites; 5 - 20 positions
sat_segment_limit = (
sats_in_view - ((num_sv_sentences - 1) * 4)) * 5
else:
# Non-last sentences have 4 satellites and thus read up to position 20
sat_segment_limit = 20
# Try to recover data for up to 4 satellites in sentence
for sats in range(4, sat_segment_limit, 4):
# If a PRN is present, grab satellite data
if self.gps_segments[sats]:
try:
sat_id = int(self.gps_segments[sats])
except (ValueError, IndexError):
return False
try: # elevation can be null (no value) when not tracking
elevation = int(self.gps_segments[sats+1])
except (ValueError, IndexError):
elevation = None
try: # azimuth can be null (no value) when not tracking
azimuth = int(self.gps_segments[sats+2])
except (ValueError, IndexError):
azimuth = None
try: # SNR can be null (no value) when not tracking
snr = int(self.gps_segments[sats+3])
except (ValueError, IndexError):
snr = None
# If no PRN is found, then the sentence has no more satellites to read
else:
break
# Add Satellite Data to Sentence Dict
satellite_dict[sat_id] = (elevation, azimuth, snr)
# Update Object Data
self.total_sv_sentences = num_sv_sentences
self.last_sv_sentence = current_sv_sentence
self.satellites_in_view = sats_in_view
# For a new set of sentences, we either clear out the existing sat data or
# update it as additional SV sentences are parsed
if current_sv_sentence == 1:
self.satellite_data = satellite_dict
else:
self.satellite_data.update(satellite_dict)
return True
##########################################
# Data Stream Handler Functions
##########################################
def new_sentence(self):
"""Adjust Object Flags in Preparation for a New Sentence"""
self.gps_segments = ['']
self.active_segment = 0
self.crc_xor = 0
self.sentence_active = True
self.process_crc = True
self.char_count = 0
def update(self, new_char):
"""Process a new input char and updates GPS object if necessary based on special characters ('$', ',', '*')
Function builds a list of received string that are validate by CRC prior to parsing by the appropriate
sentence function. Returns sentence type on successful parse, None otherwise"""
valid_sentence = False
# Validate new_char is a printable char
ascii_char = ord(new_char)
if 10 <= ascii_char <= 126:
self.char_count += 1
# Write Character to log file if enabled
if self.log_en:
self.write_log(new_char)
# Check if a new string is starting ($)
if new_char == '$':
self.new_sentence()
return None
elif self.sentence_active:
# Check if sentence is ending (*)
if new_char == '*':
self.process_crc = False
self.active_segment += 1
self.gps_segments.append('')
return None
# Check if a section is ended (,), Create a new substring to feed
# characters to
elif new_char == ',':
self.active_segment += 1
self.gps_segments.append('')
# Store All Other printable character and check CRC when ready
else:
self.gps_segments[self.active_segment] += new_char
# When CRC input is disabled, sentence is nearly complete
if not self.process_crc:
if len(self.gps_segments[self.active_segment]) == 2:
try:
final_crc = int(
self.gps_segments[self.active_segment], 16)
if self.crc_xor == final_crc:
valid_sentence = True
else:
self.crc_fails += 1
except ValueError:
pass # CRC Value was deformed and could not have been correct
# Update CRC
if self.process_crc:
self.crc_xor ^= ascii_char
# If a Valid Sentence Was received and it's a supported sentence, then parse it!!
if valid_sentence:
self.clean_sentences += 1 # Increment clean sentences received
self.sentence_active = False # Clear Active Processing Flag
if self.gps_segments[0] in self.supported_sentences:
# parse the Sentence Based on the message type, return True if parse is clean
if self.supported_sentences[self.gps_segments[0]](self):
# Let host know that the GPS object was updated by returning parsed sentence type
self.parsed_sentences += 1
return self.gps_segments[0]
# Check that the sentence buffer isn't filling up with Garage waiting for the sentence to complete
if self.char_count > self.SENTENCE_LIMIT:
self.sentence_active = False
# Tell Host no new sentence was parsed
return None
def new_fix_time(self):
"""Updates a high resolution counter with current time when fix is updated. Currently only triggered from
GGA, GSA and RMC sentences"""
try:
self.fix_time = utime.ticks_ms()
except NameError:
self.fix_time = time.time()
#########################################
# User Helper Functions
# These functions make working with the GPS object data easier
#########################################
def satellite_data_updated(self):
"""
Checks if the all the GSV sentences in a group have been read, making satellite data complete
:return: boolean
"""
if self.total_sv_sentences > 0 and self.total_sv_sentences == self.last_sv_sentence:
return True
else:
return False
def unset_satellite_data_updated(self):
"""
Mark GSV sentences as read indicating the data has been used and future updates are fresh
"""
self.last_sv_sentence = 0
def satellites_visible(self):
"""
Returns a list of of the satellite PRNs currently visible to the receiver
:return: list
"""
return list(self.satellite_data.keys())
def time_since_fix(self):
"""Returns number of millisecond since the last sentence with a valid fix was parsed. Returns 0 if
no fix has been found"""
# Test if a Fix has been found
if self.fix_time == 0:
return -1
# Try calculating fix time using utime; if not running MicroPython
# time.time() returns a floating point value in secs
try:
current = utime.ticks_diff(utime.ticks_ms(), self.fix_time)
except NameError:
current = (time.time() - self.fix_time) * 1000 # ms
return current
def compass_direction(self):
"""
Determine a cardinal or inter-cardinal direction based on current course.
:return: string
"""
# Calculate the offset for a rotated compass
if self.course >= 348.75:
offset_course = 360 - self.course
else:
offset_course = self.course + 11.25
# Each compass point is separated by 22.5 degrees, divide to find lookup value
dir_index = floor(offset_course / 22.5)
final_dir = self.__DIRECTIONS[dir_index]
return final_dir
def latitude_string(self):
"""
Create a readable string of the current latitude data
:return: string
"""
if self.coord_format == 'dd':
formatted_latitude = self.latitude
lat_string = str(
formatted_latitude[0]) + '° ' + str(self._latitude[2])
elif self.coord_format == 'dms':
formatted_latitude = self.latitude
lat_string = str(formatted_latitude[0]) + '° ' + str(formatted_latitude[1]) + "' " + str(
formatted_latitude[2]) + '" ' + str(formatted_latitude[3])
else:
lat_string = str(
self._latitude[0]) + '° ' + str(self._latitude[1]) + "' " + str(self._latitude[2])
return lat_string
def longitude_string(self):
"""
Create a readable string of the current longitude data
:return: string
"""
if self.coord_format == 'dd':
formatted_longitude = self.longitude
lon_string = str(
formatted_longitude[0]) + '° ' + str(self._longitude[2])
elif self.coord_format == 'dms':
formatted_longitude = self.longitude
lon_string = str(formatted_longitude[0]) + '° ' + str(formatted_longitude[1]) + "' " + str(
formatted_longitude[2]) + '" ' + str(formatted_longitude[3])
else:
lon_string = str(
self._longitude[0]) + '° ' + str(self._longitude[1]) + "' " + str(self._longitude[2])
return lon_string
def speed_string(self, unit='kph'):
"""
Creates a readable string of the current speed data in one of three units
:param unit: string of 'kph','mph, or 'knot'
:return:
"""
if unit == 'mph':
speed_string = str(self.speed[1]) + ' mph'
elif unit == 'knot':
if self.speed[0] == 1:
unit_str = ' knot'
else:
unit_str = ' knots'
speed_string = str(self.speed[0]) + unit_str
else:
speed_string = str(self.speed[2]) + ' km/h'
return speed_string
def date_string(self, formatting='s_mdy', century='20'):
"""
Creates a readable string of the current date.
Can select between long format: Januray 1st, 2014
or two short formats:
11/01/2014 (MM/DD/YYYY)
01/11/2014 (DD/MM/YYYY)
:param formatting: string 's_mdy', 's_dmy', or 'long'
:param century: int delineating the century the GPS data is from (19 for 19XX, 20 for 20XX)
:return: date_string string with long or short format date
"""
# Long Format Januray 1st, 2014
if formatting == 'long':
# Retrieve Month string from private set
month = self.__MONTHS[self.date[1] - 1]
# Determine Date Suffix
if self.date[0] in (1, 21, 31):
suffix = 'st'
elif self.date[0] in (2, 22):
suffix = 'nd'
elif self.date[0] == (3, 23):
suffix = 'rd'
else:
suffix = 'th'
day = str(self.date[0]) + suffix # Create Day String
year = century + str(self.date[2]) # Create Year String
date_string = month + ' ' + day + ', ' + year # Put it all together
else:
# Add leading zeros to day string if necessary
if self.date[0] < 10:
day = '0' + str(self.date[0])
else:
day = str(self.date[0])
# Add leading zeros to month string if necessary
if self.date[1] < 10:
month = '0' + str(self.date[1])
else:
month = str(self.date[1])
# Add leading zeros to year string if necessary
if self.date[2] < 10:
year = '0' + str(self.date[2])
else:
year = str(self.date[2])
# Build final string based on desired formatting
if formatting == 's_dmy':
date_string = day + '/' + month + '/' + year
else: # Default date format
date_string = month + '/' + day + '/' + year
return date_string
# All the currently supported NMEA sentences
supported_sentences = {'GPRMC': gprmc, 'GLRMC': gprmc, 'BDGSA': gpgsa,
'GPGGA': gpgga, 'GLGGA': gpgga, 'BDGSV': gpgsv,
'GPVTG': gpvtg, 'GLVTG': gpvtg,
'GPGSA': gpgsa, 'GLGSA': gpgsa,
'GPGSV': gpgsv, 'GLGSV': gpgsv,
'GPGLL': gpgll, 'GLGLL': gpgll,
'GNGGA': gpgga, 'GNRMC': gprmc,
'GNVTG': gpvtg, 'GNGLL': gpgll,
'GNGSA': gpgsa,
}
if __name__ == "__main__":
pass
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册