一、方案说明:
这次有幸获得腾讯物联网和NXP联合推出的TencentOS Tiny EVB_AIoT开发板,是基于NXP的i.MX RT1062跨界MCU,其功能非常强大。因为时间关系,还有很多强大的功能尚在学习中,这次先运用学习的基础知识,做了一个有些趣味性的系统。
办公区的厕所,不用的时候,没什么人用,等到自己想去的时候,往往人满为患,甚至有瘾君子,蹲连往返,让真真有需要的人痛苦不堪。那我们的这个趣味型系统,就是:办公区厕所蹲位监控系统。
我们要实现的基本功能,就是检测到有人如厕,就会进行计数。至于计数后,有什么用,这个各位自己想了,例如可以把控制界面发给老板,老板看哪个蹲位占用时间比较长,就点一个警告按钮,催一催:):):)
二、系统设计:
有了想法以后,就进行了详细的规划,具体的逻辑流程如下:
在这个方案中,用到了两个人体感应传感器。主要是我手头刚好有E53_IS1的扩展板,带有人体感应传感器,而我同时还有一块单独的人体感应传感器模块。这两个人体感应传感器,都是触发型的,也就是检测到有人体靠近,就会触发高电平输出,但是当人体保持在传感器前面时,不能持续保持输出,所以只能想了一个变通的方案。
第一个人体感应传感器,位于蹲位单间门口,但要套上壳,只允许正前方的感应,而不接收片侧面方向的感应,避免误触,也就是当人体正常经过的时候,会进行触发。
第二个人体感应传感器,则位于蹲位单间内,当第一个人体感应传感器检测到数据,第二个也检测到,说明正式如厕,蹲位被占用,可以上报信息到云平台,并开始计数了。
对一个具体的蹲位,我们使用了三个IoT属性:蹲位是否占用、蹲位计数、蹲位警告开关,具体如下:
以上的内容,需要在腾讯IoT平台进行设置,具体请参考官方的入门指导。
三、硬件设计:
在该方案中,使用到了如下的硬件:
- TencentOS Tiny EVB_AIoT开发板
- E53_IS1扩展板
- PIR人体感应传感器
- LED两个
- 扩展板一个:主要是为了方便传感器、LED的供电和接地
TencentOS Tiny EVB_AIoT开发板上提供的直插接口,为E53接口,如果把E53_IS1扩展板直接插上,则接口不够,所以把E53_IS1扩展板取下来,用杜邦线进行连接。
具体的连线如下:
开发板E53接口的5V、GND,连接扩展板的5V、GND
E53_IS1扩展板的5V、GND,连接到扩展板的5V、GND,GPIO2接口,连接到开发板E53接口的GPIO2
PIR人体感应传感器的5V、GND,连接到扩展板的5V、GND,D-OUT接口,连接到开发板E53接口的GPIO3
两个LED的GND连接到扩展板的GND,LED1、LED2的控制引脚分别连接到开发板E53接口的GPIO5、GPIO1
通过查看TencentOS Tiny官方提供的《20211027_RT1062_Core_RevA03(确认).pdf》、《20211027_Tencent_AIoT_RevC03(确认).pdf》文档,我们可以得知开发板E53接口的GPIO2、GPIO3、GPIO5、GPIO1,分别对应RT1062的C14、B14、P2、J2。
因此需要在MCUXPresso IDE中,使用引脚功能进行配置,具体配置如下:
四、软件设计:
首先,通过MCUXPresso IDE的例子程序进行了学习,然后通过学习TencentOS Tiny官方提供的IoT实例,进行了扩展。
在上面的工程中,分别为:
- hello world
- gpio_input_interrupt:按键输入
- gpio_led_output:点灯
- 官方IoT实例:连接到腾讯物联网平台
通过MCUXPresso IDE的引脚配置,最终我们定义了调用的头文件pin_config.h:
代码语言:javascript复制#ifndef TOS_PIN_CONFIG_H
#define TOS_PIN_CONFIG_H
/*******************************************************************************
* 引脚定义:
* 1. E53的GPIO3、GPIO2做为人体感应器的输入
* 2. E53的GPIO1、GPIO5控制LED
******************************************************************************/
#define EXAMPLE_SW_GPIO1 BOARD_INITPINS_E53_GPIO2_GPIO
#define EXAMPLE_SW_GPIO1_PIN BOARD_INITPINS_E53_GPIO2_GPIO_PIN
#define EXAMPLE_SW_GPIO2 BOARD_INITPINS_E53_GPIO3_GPIO
#define EXAMPLE_SW_GPIO2_PIN BOARD_INITPINS_E53_GPIO3_GPIO_PIN
#define EXAMPLE_SW_NAME1 "SW1"
#define EXAMPLE_SW_NAME2 "SW2"
#define EXAMPLE_LED_GPIO1 BOARD_INITPINS_E53_GPIO5_GPIO
#define EXAMPLE_LED_GPIO1_PIN BOARD_INITPINS_E53_GPIO5_GPIO_PIN
#define EXAMPLE_LED_GPIO2 BOARD_INITPINS_E53_GPIO1_GPIO
#define EXAMPLE_LED_GPIO2_PIN BOARD_INITPINS_E53_GPIO1_GPIO_PIN
#endif
然后进行了基础的测试:main.c
代码语言:javascript复制#......
#include "pin_config.h"
#define IOT_CLOUD_ENABLE 0 // 1-启用物联网 0-不启用物联网
static int sw_flags[2] = {0,0};
int main(void)
{
/* 输入和输出参数定义 */
gpio_pin_config_t sw_config = {kGPIO_DigitalInput, 0, kGPIO_IntRisingEdge};
gpio_pin_config_t led_config = {kGPIO_DigitalOutput, 0, kGPIO_NoIntmode};
lpuart_config_t config;
/* 开发板引脚、时钟、调试等初始化 */
BOARD_ConfigMPU();
BOARD_InitPins();
BOARD_BootClockRUN();
BOARD_InitDebugConsole();
/* 输出欢迎信息 */
PRINTF("Welcome to TencentOS tiny @ i.MX RT1062rn");
/* 初始化输入 GPIO. */
GPIO_PinInit(EXAMPLE_SW_GPIO1, EXAMPLE_SW_GPIO1_PIN, &sw_config);
GPIO_PinInit(EXAMPLE_SW_GPIO2, EXAMPLE_SW_GPIO2_PIN, &sw_config);
/* 初始化输出 GPIO. */
GPIO_PinInit(EXAMPLE_LED_GPIO1, EXAMPLE_LED_GPIO1_PIN, &led_config);
GPIO_PinInit(EXAMPLE_LED_GPIO2, EXAMPLE_LED_GPIO2_PIN, &led_config);
/* 初始化点亮LED. */
GPIO_PinWrite(EXAMPLE_LED_GPIO1, EXAMPLE_LED_GPIO1_PIN, 0U);
GPIO_PinWrite(EXAMPLE_LED_GPIO2, EXAMPLE_LED_GPIO2_PIN, 0U);
/* 人体感应传感器与LED 测试 */
#if IOT_CLOUD_ENABLE == 0
while (1)
{
tos_task_delay(1000);
if (1 == GPIO_PinRead(EXAMPLE_SW_GPIO1, EXAMPLE_SW_GPIO1_PIN))
{
if(sw_flags[0]!=1) {
sw_flags[0]=1;
PRINTF("%s is turned on.rn", EXAMPLE_SW_NAME1);
GPIO_PinWrite(EXAMPLE_LED_GPIO1, EXAMPLE_LED_GPIO1_PIN, 0U);
}
} else {
if(sw_flags[0]!=0) {
sw_flags[0]=0;
PRINTF("%s is turned off.rn", EXAMPLE_SW_NAME1);
GPIO_PinWrite(EXAMPLE_LED_GPIO1, EXAMPLE_LED_GPIO1_PIN, 1U);
}
}
if (1 == GPIO_PinRead(EXAMPLE_SW_GPIO2, EXAMPLE_SW_GPIO2_PIN))
{
if(sw_flags[1]!=1) {
sw_flags[1]=1;
PRINTF("%s is turned on.rn", EXAMPLE_SW_NAME2);
GPIO_PinWrite(EXAMPLE_LED_GPIO2, EXAMPLE_LED_GPIO2_PIN, 0U);
}
} else {
if(sw_flags[1]!=0) {
sw_flags[1]=0;
PRINTF("%s is turned off.rn", EXAMPLE_SW_NAME2);
GPIO_PinWrite(EXAMPLE_LED_GPIO2, EXAMPLE_LED_GPIO2_PIN, 1U);
}
}
}
#else
// 使能 TencentOS Tiny
osKernelInitialize(); // 内核初始化
osThreadCreate(osThread(application_entry), NULL); // 创建任务
osKernelStart(); // 启动
#endif
}
在上面的代码中,sw_flags用来记录传感器的状态;因为人体感应传感器模块,触发后,会有一段时间的延时才会将为低电平,所以使用sw_flags来检测状态是否改变,才进行具体的处理。
这段代码的核心,就是输入和输出GPIO的初始化,以及GPIO读写。
运行这段代码,连接串口调试,用手分别接近两个传感器,就能够收到对应的输出信息。
基础的传感器信号读取和LED控制完成后,就可以开始mqtt连接IoT平台的部分了。
官方提供了一个py文件,来生成对应的mqtt调用信息,我进行了修改后,方便直接生成对应的头文件,以及对应的调用代码:
代码语言:javascript复制#!/usr/bin/python
# -*- coding: UTF-8 -*-
import base64
import hashlib
import hmac
import random
import string
import time
from string import Template
# bind raw_input to input in Python 2
try:
input = raw_input
except NameError:
pass
product_id = input("product id:")
dev_name = input("device name:")
passwd = input("password:")
sub = input("subscribe topic:[default:control]")
if sub == "":
sub = "control"
pub = input("publish topic:[default:event]")
if pub == "":
pub = "event"
client_id = product_id dev_name
# expire time: 2^32 - 1 = 4294967295
username = client_id ";21010406;12365;{}".format(4294967295)
def hmacsha1(content, passwd):
passwd_byte = base64.b64decode(passwd)
return hmac.new(passwd_byte, content, digestmod=hashlib.sha1).hexdigest()
username = username.encode("utf-8")
passwd = passwd.encode("utf-8")
sign = hmacsha1(username, passwd)
template_header = ('#ifndef TOS_MQTT_CONFIG_Hn'
'#define TOS_MQTT_CONFIG_Hn'
'n'
'#define MQTT_SERVER_IP "111.230.189.156"n'
'#define MQTT_SERVER_PORT "1883"n'
'#define MQTT_PRODUCT_ID "$product"n'
'#define MQTT_DEV_NAME "$dev"n'
'#define MQTT_CLIENT_ID "$product$dev"n'
'#define MQTT_USR_NAME "$product$dev;21010406;12365;4294967295"n'
'#define MQTT_PASSWORD "$sign;hmacsha1"n'
'#define MQTT_SUBSCRIBE_TOPIC "$product/$dev/$sub"n'
'#define MQTT_PUBLISH_TOPIC "$product/$dev/$pub"n'
'n'
'#define MQTT_SERVER_DOMAIN "$product.iotcloud.tencentdevices.com"n'
'#define MQTT_TOPIC "$product/$dev"n'
'#define MQTT_SUBSCRIBE_TOPIC_DOWN "$thing/down/property/$product/$dev"n'
'#define MQTT_PUBLISH_TOPIC_UP "$thing/up/property/$product/$dev"n'
'n'
'#endifn'
'n')
template_c = ('#ifndef TOS_MQTT_CONFIG_Hn'
'tos_sal_module_parse_domain(MQTT_SERVER_DOMAIN,host_ip,sizeof(host_ip));n'
'n'
'mqtt_set_port(client, MQTT_SERVER_PORT);n'
'mqtt_set_host(client, host_ip);n'
'mqtt_set_client_id(client, MQTT_CLIENT_ID);n'
'mqtt_set_user_name(client, MQTT_USR_NAME);n'
'mqtt_set_password(client, MQTT_PASSWORD);n'
'mqtt_set_clean_session(client, 1);n'
'n'
'error = mqtt_subscribe(client, MQTT_SUBSCRIBE_TOPIC_DOWN, QOS0, tos_topic_handler);n'
'n'
'error = mqtt_publish(client, MQTT_PUBLISH_TOPIC_UP, &msg);n'
'n'
'#endifn'
'n')
src_header = Template(template_header)
src_c = Template(template_c)
d = {
'product':product_id,
'dev':dev_name,
'sign':sign,
'sub':sub,
'pub':pub,
'thing':'$thing'
}
#do the substitution
dst_header = src_header.substitute(d)
dst_c = src_c.substitute(d)
print("===============Generate mqtt_config.h==================")
print(dst_header)
with open('mqtt_config.h', 'w') as f:
f.write(dst_header)
print("===============Generate mqtt_connect_demo.c==================")
print(dst_c)
with open('mqtt_connect_demo.h', 'w') as f:
f.write(dst_c)
参考官方的调用方法运行后,得到的头文件如下:
代码语言:javascript复制#ifndef TOS_MQTT_CONFIG_H
#define TOS_MQTT_CONFIG_H
#define MQTT_SERVER_IP "111.230.189.156"
#define MQTT_SERVER_PORT "1883"
#define MQTT_PRODUCT_ID "FVR9566ISL"
#define MQTT_DEV_NAME "dev01"
#define MQTT_CLIENT_ID "FVR9566ISLdev01"
#define MQTT_USR_NAME "FVR9566ISLdev01;21010406;12365;4294967295"
#define MQTT_PASSWORD "b07060f02090e090c030f05010c000c050505020;hmacsha1"
#define MQTT_SUBSCRIBE_TOPIC "FVR9566ISL/dev01/control"
#define MQTT_PUBLISH_TOPIC "FVR9566ISL/dev01/event"
#define MQTT_SERVER_DOMAIN "FVR9566ISL.iotcloud.tencentdevices.com"
#define MQTT_TOPIC "FVR9566ISL/dev01"
#define MQTT_SUBSCRIBE_TOPIC_DOWN "$thing/down/property/FVR9566ISL/dev01"
#define MQTT_PUBLISH_TOPIC_UP "$thing/up/property/FVR9566ISL/dev01"
#endif
对应的mqttclient_iot_explorer.c文件内容如下:
代码语言:javascript复制#include "pin_mux.h"
#include "clock_config.h"
#include "board.h"
#include "fsl_debug_console.h"
#include "fsl_gpio.h"
#include "cmsis_os.h"
#include "fsl_lpuart.h"
#include "sal_module_wrapper.h"
#include "ec600s.h"
#include "tos_at.h"
#include "mqttclient.h"
#include "cjson.h"
#include "sal_module_wrapper.h"
#include "mqtt_config.h"
#include "pin_config.h"
#define USE_ESP8266
#if defined(USE_ESP8266)
#include "esp8266.h"
#endif
#ifdef USE_ESP8266
static hal_uart_port_t esp8266_port = HAL_UART_PORT_2;
void mqtt_set_esp8266_port(hal_uart_port_t port) {
esp8266_port = port;
}
#endif
extern volatile bool g_pinSet;
static int sw_flags[2];
k_event_t report_result_event;
k_event_flag_t report_success = 1<<0;
k_event_flag_t report_fail = 1<<1;
// 消息接收回调
static void tos_topic_handler(void* client, message_data_t* msg)
{
(void) client;
cJSON* cjson_root = NULL;
cJSON* cjson_status = NULL;
cJSON* cjson_method = NULL;
cJSON* cjson_params = NULL;
char* status = NULL;
char* method = NULL;
int power_switch = 0;
k_event_flag_t event_flag = report_fail;
/* 打印收到的消息 */
MQTT_LOG_I("-----------------------------------------------------------------------------------");
MQTT_LOG_I("%s:%d %s()...ntopic: %s, qos: %d. nmessage:nt%sn", __FILE__, __LINE__, __FUNCTION__,
msg->topic_name, msg->message->qos, (char*)msg->message->payload);
MQTT_LOG_I("-----------------------------------------------------------------------------------n");
/* 解析收到的消息内容,转换为JSON */
cjson_root = cJSON_Parse((char*)msg->message->payload);
if (cjson_root == NULL) {
printf("report reply message parser failrn");
event_flag = report_fail;
goto exit;
}
/* 获取本次收到消息的method */
cjson_method = cJSON_GetObjectItem(cjson_root, "method");
method = cJSON_GetStringValue(cjson_method);
if (method == NULL || method == NULL) {
printf("message method parser failrn");
event_flag = report_fail;
goto exit;
}
MQTT_LOG_I("report mthod is %srn", method);
if(strcmp(method, "report_reply")==0) {
// 服务端对报告信息的返回
/* 获取消息中的status */
cjson_status = cJSON_GetObjectItem(cjson_root, "status");
status = cJSON_GetStringValue(cjson_status);
if (cjson_status == NULL || status == NULL) {
printf("report reply status parser failrn");
event_flag = report_fail;
goto exit;
}
/* 检查回报状态是否为success */
if (strstr(status,"success")) {
event_flag = report_success;
}else {
event_flag = report_fail;
}
} else if(strcmp(method, "control")==0) {
event_flag = report_success;
cjson_params = cJSON_GetObjectItem(cjson_root, "params");
power_switch = cJSON_GetObjectItem(cjson_params, "toilet_switch_1")->valueint;
MQTT_LOG_I("Control set toilet_switch_1:%drn", power_switch);
if(power_switch) {
GPIO_PinWrite(EXAMPLE_LED_GPIO2, EXAMPLE_LED_GPIO2_PIN, 1U);
} else {
GPIO_PinWrite(EXAMPLE_LED_GPIO2, EXAMPLE_LED_GPIO2_PIN, 0U);
}
} else {
printf("message method is know: %srn", method);
event_flag = report_fail;
goto exit;
}
// 清理并返回
exit:
cJSON_Delete(cjson_root);
cjson_root = NULL;
status = NULL;
method = NULL;
tos_event_post(&report_result_event, event_flag);
return;
}
#define REPORT_DATA_TEMPLATE "{"method":"report","clientToken":"00000001",
"params":{
"toilet_status_1":%d,
"toilet_count_1":%d
}
}"
char report_buf[500];
// mqtt客户端任务
void mqttclient_task(void)
{
int error;
mqtt_client_t *client = NULL;
mqtt_message_t msg;
k_event_flag_t match_flag;
char host_ip[20];
memset(&msg, 0, sizeof(msg));
// 使用ESP8266联网
esp8266_sal_init(esp8266_port);
esp8266_join_ap("OpenBSD", "********");
mqtt_log_init();
client = mqtt_lease();
tos_event_create(&report_result_event, (k_event_flag_t)0u);
// 设置mqtt连接信息
tos_sal_module_parse_domain(MQTT_SERVER_DOMAIN,host_ip,sizeof(host_ip));
mqtt_set_port(client, MQTT_SERVER_PORT);
mqtt_set_host(client, host_ip);
mqtt_set_client_id(client, MQTT_CLIENT_ID);
mqtt_set_user_name(client, MQTT_USR_NAME);
mqtt_set_password(client, MQTT_PASSWORD);
mqtt_set_clean_session(client, 1);
// 连接服务端
error = mqtt_connect(client);
if(error!=0) {
MQTT_LOG_D("mqtt connect error is %#0x", error);
} else {
MQTT_LOG_D("mqtt connect success.");
}
// 订阅服务端发送的消息
error = mqtt_subscribe(client, MQTT_SUBSCRIBE_TOPIC_DOWN, QOS0, tos_topic_handler);
if(error!=0) {
MQTT_LOG_D("mqtt subscribe error is %#0x", error);
} else {
MQTT_LOG_D("mqtt subscribe success.");
}
int first = 1;
int step = 0;
int count = 0;
while (1) {
// 读取1号状态
if (1 == GPIO_PinRead(EXAMPLE_SW_GPIO1, EXAMPLE_SW_GPIO1_PIN))
{
if(sw_flags[0]!=1) {
if(step<=1) {
// 进入处理逻辑,需要进行循环处理
step = 2;
}
if(step>=3) {
// 再次通过1号传感器
step = 0;
GPIO_PinWrite(EXAMPLE_LED_GPIO2, EXAMPLE_LED_GPIO1_PIN, 0U);
}
sw_flags[0]=1;
MQTT_LOG_D("%s is turned on.rn", EXAMPLE_SW_NAME1);
}
} else {
if(step==2) {
// 通过1号传感器
step = 3;
}
if(sw_flags[0]!=0) {
sw_flags[0]=0;
MQTT_LOG_D("%s is turned off.rn", EXAMPLE_SW_NAME1);
}
}
if(step>=3) {
// 读取2号状态
if (1 == GPIO_PinRead(EXAMPLE_SW_GPIO2, EXAMPLE_SW_GPIO2_PIN))
{
if(sw_flags[1]!=1) {
if(step==3) {
step = 4;
}
sw_flags[1]=1;
PRINTF("%s is turned on.rn", EXAMPLE_SW_NAME2);
GPIO_PinWrite(EXAMPLE_LED_GPIO2, EXAMPLE_LED_GPIO1_PIN, 1U);
}
} else {
if(sw_flags[1]!=0) {
sw_flags[1]=0;
PRINTF("%s is turned off.rn", EXAMPLE_SW_NAME2);
}
}
}
MQTT_LOG_D("step = %d", step);
if(step>=4) {
count = count 1;
}
if(first==1 || step==0 || step == 4 || (count>0 && count%5 == 0)) {
// 上报数据
first = 0;
if(step==0) {
count = 0;
step = 1;
}
else {
step = 5;
}
// 生成上报数据信息
snprintf(report_buf, sizeof(report_buf), REPORT_DATA_TEMPLATE, step >= 4 ? 1 : 0, count/5);
memset(&msg, 0, sizeof(msg));
// 发送消息
msg.qos = QOS0;
msg.payload = (void *) report_buf;
error = mqtt_publish(client, MQTT_PUBLISH_TOPIC_UP, &msg);
if(error) {
MQTT_LOG_D("mqtt publish error is %#0x", error);
} else {
MQTT_LOG_D("mqtt publish success: >>>%s<<<", report_buf);
}
tos_event_pend(&report_result_event,
report_success|report_fail,
&match_flag,
TOS_TIME_FOREVER,
TOS_OPT_EVENT_PEND_ANY | TOS_OPT_EVENT_PEND_CLR);
if (match_flag == report_success) {
printf("report to Tencent IoT Explorer successrn");
}else if (match_flag == report_fail){
printf("report to Tencent IoT Explorer failrn");
}
}
tos_task_delay(200);
}
}
// tos app入口
void application_entry(void *arg)
{
mqttclient_task();
while (1) {
tos_task_delay(1000);
}
}
官方提供的IoT例子程序中,接收到消息后,只有对上报回复消息的处理,没有对控制指令下发消息的处理。所以在上述代码中,tos_topic_handler()回调部分,包含了对接收到的消息中,method的处理,如果为control,则表示为下发的控制指令,根据实际情况,进行具体的处理。
上面的演示代码中,读取人体感应传感器状态、控制LED,都基于main.c中的基础。把main.c中的IOT_CLOUD_ENABLE设置为1,就能启用IoT部分的代码。
五、运行效果:
因为是演示,所以要遵循一定的使用流程:
- 先将整套设备,置于不容易被人体感应误触的地方和方向
- 用手滑过E53_IS1对应的人体感应传感器,该扩展板上的指示灯会亮,表示感应到了;表示经过了蹲位的门
- 再用手滑过PIR人体感应传感器,该传感器上的指示灯会亮,同时,LED1会亮;表示正式如厕。
- 用微信的腾讯连连,打开对应的调试界面,就会看到蹲位的占用情况,以及计数情况;可以点蹲位警告灯,此时LED2会亮,以示警告。
- 经过一段时间后,再用手滑过E53_IS1对应的人体感应传感器,该扩展板上的指示灯会亮,表示感应到了,同时LED1会熄灭;表示如厕完毕,走人了。
六、后续展望
目前完成的功能,还比较基础。后续进一步完善的话,会更换人体感应传感器,用单个传感器,持续感应即可。
另外,还可以通过附带的屏幕,展示当前蹲位的情况,让想要如厕的同事一目了然。
当然,这个项目只是一个趣味型的实例,可以很方便的切换到其他场景使用。