不得不再次感叹一下,ddl是第一生产力!!
历经了7天的磨练,这个小东西终于被搞定了。
其实是每天下班后,还不想加班的我,回来完成一个小任务后开始躺尸。
看着群里面的大佬们每天在讨论H5啥啥啥的高端的玩意儿,总觉得自己菜的不行……
言归正传,鹅厂这次搞得【腾讯连连IoT开发大赛】不得不说是个特别好玩的东西
其实作为一个滑坡灾害监测设备的话,一般应该是安装在户外,也就是用4G或者NB-IoT的通讯方式更好。
でもね、手头目前缺少设备- -,就用了上次从鹅厂嫖到的TOS_EVB_G0开发板作为核心。
主要设备:
接下来是个全家福
先上一个视频
首先做好硬件开发准备工作,把传感器连接到核心板上,写好驱动代码,读取传感器数值。
位移传感器是一个三线制传感器,行程1000mm,供电5-10V(实测3V3工作正常,反正是个滑动变阻器……)
传感器基本上是线性的,所以用ADC读取后,可以直接用公式进行转换
输出端口接到ADC1上,然后在软件中对ADC1进行配置
hadc1.Instance = ADC1;
hadc1.Init.ClockPrescaler = ADC_CLOCK_SYNC_PCLK_DIV2;
hadc1.Init.Resolution = ADC_RESOLUTION_12B;
hadc1.Init.DataAlign = ADC_DATAALIGN_RIGHT;
hadc1.Init.ScanConvMode = ADC_SCAN_DISABLE;
hadc1.Init.EOCSelection = ADC_EOC_SINGLE_CONV;
hadc1.Init.LowPowerAutoWait = DISABLE;
hadc1.Init.LowPowerAutoPowerOff = DISABLE;
hadc1.Init.ContinuousConvMode = DISABLE;
hadc1.Init.NbrOfConversion = 1;
hadc1.Init.DiscontinuousConvMode = DISABLE;
hadc1.Init.ExternalTrigConv = ADC_SOFTWARE_START;
hadc1.Init.ExternalTrigConvEdge = ADC_EXTERNALTRIGCONVEDGE_NONE;
hadc1.Init.DMAContinuousRequests = DISABLE;
hadc1.Init.Overrun = ADC_OVR_DATA_PRESERVED;
hadc1.Init.SamplingTimeCommon1 = ADC_SAMPLETIME_1CYCLE_5;
hadc1.Init.SamplingTimeCommon2 = ADC_SAMPLETIME_1CYCLE_5;
hadc1.Init.OversamplingMode = DISABLE;
hadc1.Init.TriggerFrequencyMode = ADC_TRIGGER_FREQ_HIGH;
if (HAL_ADC_Init(&hadc1) != HAL_OK)
{
Error_Handler();
}
然后驱动ADC1,读取数值,将其转换成实际位移
//读取ADC的值,数据存放在param_dis_adc
HAL_ADC_Start(&hadc1);
HAL_Delay(1000);
printf("Distance:\r\n");
//获取adc的值
param_dis_adc=HAL_ADC_GetValue(&hadc1);
printf("adc value %d \r\n", param_dis_adc);
//将ADC的值转换为位移量
adcConvertedDist =(double)param_dis_adc*1000/4096;
printf("dist value %.2f mm \r\n", adcConvertedDist);
倾斜度传感器选用的是SCA100T,这是一个SPI总线的倾斜度传感器
数字式传感器可以有效解决因为噪声导致的干扰
在驱动了板载硬件SPI后,调试未果,便采用了模拟SPI时序的方法进行驱动
以X轴加速度读取为例:
CS_ONE();
tos_task_delay(4);
CS_ZERO(); //这一阶段开始读数据
tos_task_delay(4);
for(i=0; i<8; i++){
Temp = RDA & 0X80;
RDA <<= 1;
//开始加载数据
if(Temp == 0x80){SDI_ONE();}
else{SDI_ZERO();}
SCLK_ZERO();
tos_task_delay(4);
SCLK_ONE(); //时钟给定上升沿
tos_task_delay(4);
}
tos_task_delay(4);
for(i=0; i<11; i++){
SCLK_ONE(); //数据在下降沿改变
tos_task_delay(4);
SCLK_ZERO();
tos_task_delay(4);
if(HAL_GPIO_ReadPin(GPIOB,GPIO_PIN_7)){
ACC_X <<= 1; ACC_X += 1;
}else{
ACC_X <<= 1;
}
}
CS_ONE();
tos_task_delay(4);
最后获得到当前的XY轴加速度,然后根据下面的公式进行换算
其中
然后进行一个计算校准
int miay;
miay = (ACC_Y-1024);
Angle_Y = ((float)miay/1638)*180.0/PI;
printf("Angle_Y = %.2f\r\n", Angle_Y);
这里很奇怪的是,直接做减法后强制类型转换得到的结果值有错误……
所以用了个中间变量做转换……疑惑脸🧐
最后利用串口输出X和Y轴的角度值
//读取SCA100T的值,数据存放在Angle_X,Angle_Y
SCA_GPIO_INIT();
SCA100T_Angle_X();
printf("Angle_X = %f\r\n", Angle_X);
HAL_Delay(1000);
SCA100T_Angle_Y();
printf("Angle_Y = %f\r\n", Angle_Y);
HAL_Delay(1000);
因为灾害预警一般是看其变化的情况,所以在程序里面还有对差值进行计算判断
首先把当前的位移和倾斜度的值存储在一个变量中
然后把每次读到的值和上一次的值求差值计算
//求取与上次位移量的差值
minus = abs( adcConvertedDist - lastdist);
printf("dist minus value %.2f mm \r\n", minus);
//求取与上次角度的差值
minusangx = fabs(Angle_X - lastangx);
minusangy = fabs(Angle_Y - lastangy);
printf("anglex minus value %.2f rad \r\n", minusangx);
printf("angley minus value %.2f rad \r\n", minusangy);
//刷新上一次的寄存器
lastdist = adcConvertedDist;
lastangx = Angle_X;
lastangy = Angle_Y;
接下来进行差值判断,首先定义报警阈值
//定义报警阈值
#define blue_dist 20
#define yellow_dist 40
#define orange_dist 60
#define red_dist 80
#define blue_ang 3.0
#define yellow_ang 4.0
#define orange_ang 5.0
#define red_ang 6.0
//定义报警变量
char*red_warn_data="red";
char*orange_warn_data="orange";
char*yellow_warn_data="yellow";
char*blue_warn_data="blue";
char*safe_warn_data="safe";
char warn_data[10]={0};
int warn_num;
报警一共分为四个等级(对应报警值为模拟,实际使用时会根据现场情况进行调整)
等级 | 对应数值 |
---|---|
蓝色警报 | 位移差值>20mm 或 角度变化>3° |
黄色警报 | 位移差值>40mm 或 角度变化>4° |
橙色警报 | 位移差值>60mm 或 角度变化>5° |
红色警报 | 位移差值>80mm 或 角度变化>6° |
然后开始进行条件判断
if(minus>blue_dist||minusangx>blue_ang||minusangy>blue_ang) {
if(minus>yellow_dist||minusangx>yellow_ang||minusangy>yellow_ang){
if(minus>orange_dist||minusangx>orange_ang||minusangy>orange_ang){
if(minus>red_dist||minusangx>red_ang|minusangy>red_ang) {
warn_num = 4;
sprintf(warn_data,"%s",red_warn_data);
}else{
warn_num = 3;
sprintf(warn_data,"%s",orange_warn_data);
}
}else{
warn_num = 2;
sprintf(warn_data,"%s",yellow_warn_data);
}
}else{
warn_num = 1;
sprintf(warn_data,"%s",blue_warn_data);
}
printf("%s,warning!\r\n",warn_data);
}else{
warn_num = 0;
sprintf(warn_data,"%s",safe_warn_data);
}
拉动位移传感器,改变传感器的值,主控自动进行报警操作
准备好硬件以后,就开始在云平台构建物模型了
首先在腾讯云物联网开发平台中依次点击“产品开发”—“新建产品”
然后依次填写信息,因为这里我才用的鹅厂开发板板载的ESP8266,所以通信方式选择WiFi
然后点击卡片进入
接下来定义数据模板,可以选择导入json的方式,也可以一步一步自己添加
下面是我的产品所需要的属性,和其对应的json信息
{
"version": "1.0",
"profile": {
"ProductId": "E0UJSCFA1X",
"CategoryId": "1"
},
"properties": [
{
"id": "dist",
"name": "位移值",
"desc": "",
"mode": "r",
"define": {
"type": "float",
"min": "0",
"max": "1000",
"start": "0",
"step": "0.01",
"unit": "mm"
},
"required": false
},
{
"id": "warning",
"name": "报警事件",
"desc": "",
"mode": "r",
"define": {
"type": "string",
"min": "0",
"max": "2048"
},
"required": false
},
{
"id": "warn_num",
"name": "等级数据",
"desc": "",
"mode": "r",
"define": {
"type": "enum",
"mapping": {
"0": "safe",
"1": "blue",
"2": "yellow",
"3": "orange",
"4": "red"
}
},
"required": false
},
{
"id": "angx",
"name": "X角度",
"desc": "",
"mode": "r",
"define": {
"type": "float",
"min": "-90",
"max": "90",
"start": "0",
"step": "0.001",
"unit": "°"
},
"required": false
},
{
"id": "angy",
"name": "Y角度",
"desc": "",
"mode": "r",
"define": {
"type": "float",
"min": "-90",
"max": "90",
"start": "0",
"step": "0.001",
"unit": "°"
},
"required": false
},
{
"id": "int_time",
"name": "采集间隔时间",
"desc": "",
"mode": "rw",
"define": {
"type": "enum",
"mapping": {
"1": "间隔10s采集",
"2": "间隔20s采集",
"3": "间隔30s采集",
"4": "间隔40s采集"
}
},
"required": false
}
],
"events": [],
"actions": []
}
那么简单来说一下吧
在我的模块中,位移和倾斜度的值,都属于浮点型数据,所以我可以新建浮点型数据属性,来读取设备上传的值
例如你的变量范围是-30°~30°的一个数据范围,就可以如上新建数据属性
这里值得注意的是,标识符一定要和数据上报的字符串中的数据一致
例如我的倾斜度X上报的数据如下:
#define REPORT_ANGX_DATA_TEMPLATE "{\\\"method\\\":\\\"report\\\"\\,\\\"clientToken\\\":\\\"00000001\\\"\\,\\\"params\\\":{\\\"angx\\\":%.3f}}"
那么我的标识符就要保持一致
因为一开始我想直接通过MCU判断当前是属于什么警报类型,直接上传到云平台,所以建立了一个如下的数据模板
#define REPORT_WARNING_DATA_TEMPLATE "{\\\"method\\\":\\\"report\\\"\\,\\\"clientToken\\\":\\\"00000001\\\"\\,\\\"params\\\":{\\\"warning\\\":\\\"%s\\\"}}"
上报的数据如下
char*red_warn_data="red";
char*orange_warn_data="orange";
char*yellow_warn_data="yellow";
char*blue_warn_data="blue";
char*safe_warn_data="safe";
char warn_data[10]={0};
通过测试,的确服务器可以收到相应的报警数据
但是腾讯连连竟然不支持字符串的显示!!!!!!!
在极度的气愤和无奈下ヾ(≧へ≦)〃
咨询了鹅厂的工程师,他们给出的建议是采用枚举类型
果然柳暗花明又一村!!
新建枚举类型数据,然后把每个报警值对应到一个数字(见1.4部分)
新建一个数据模板
#define REPORT_WARNING_NUM_TEMPLATE "{\\\"method\\\":\\\"report\\\"\\,\\\"clientToken\\\":\\\"00000001\\\"\\,\\\"params\\\":{\\\"warn_num\\\":\\\"%d\\\"}}"
这样就会获得一个值,然后云平台会根据这个值来对应报警类型。
点击交互开发按钮
在下面的属性中找到面板配置
这里就是每天看各位大佬讨论的H5开发了,对于我一个硬件开发者来说,属实有点点难度,暂且先略过
目前采用的是标准面板
这里可以把每一个值选择其显示的方式
比如浮点数的变量会自动匹配一个棒图
那么这个里面为了让当前状态更明显,所以我把报警等级放做大图标,其他的数据都作为小图标看数值即可。
其实接下来就基本大功告成了,剩下的就是配置软硬件接口部分了
首先点击设备调试,找到对应的设备,点击调试
当然,一开始你可能没有设备,那么就点击新建设备
点击设备信息,然后就可以看到你的一些关键信息
然后在硬件程序中配置你的这几个信息
// 物联网开发平台设备信息
#define PRODUCT_ID "E0U***A1X"
#define DEVICE_NAME "geo_iot"
#define DEVICE_KEY "wj38L3****uWFm3A=="
在程序中订阅topic
// 3. 设置设备信息:产品ID,设备名,设备密钥
strncpy(dev_info.product_id, product_id, PRODUCT_ID_MAX_SIZE);
strncpy(dev_info.device_name, device_name, DEVICE_NAME_MAX_SIZE);
strncpy(dev_info.device_serc, key, DEVICE_SERC_MAX_SIZE);
tos_tf_module_info_set(&dev_info, TLS_MODE_PSK);
// 4. 连接IoT Explorer
mqtt_param_t init_params = DEFAULT_MQTT_PARAMS;
if (tos_tf_module_mqtt_conn(init_params) != 0) {
printf("module mqtt conn fail\n");
} else {
printf("module mqtt conn success\n");
}
if (tos_tf_module_mqtt_state_get(&state) != -1) {
printf("MQTT: %s\n", state == MQTT_STATE_CONNECTED ? "CONNECTED" : "DISCONNECTED");
}
// 5. 订阅数据模板 topic
size = snprintf(report_reply_topic_name, TOPIC_NAME_MAX_SIZE, "$thing/down/property/%s/%s", product_id, device_name);
if (size < 0 || size > sizeof(report_reply_topic_name) - 1) {
printf("sub topic content length not enough! content size:%d buf size:%d", size, (int)sizeof(report_reply_topic_name));
}
if (tos_tf_module_mqtt_sub(report_reply_topic_name, QOS0, default_message_handler) != 0) {
printf("module mqtt sub fail\n");
} else {
printf("module mqtt sub success\n");
}
memset(report_topic_name, sizeof(report_topic_name), 0);
size = snprintf(report_topic_name, TOPIC_NAME_MAX_SIZE, "$thing/up/property/%s/%s", product_id, device_name);
if (size < 0 || size > sizeof(report_topic_name) - 1) {
printf("pub topic content length not enough! content size:%d buf size:%d", size, (int)sizeof(report_topic_name));
}
然后定义上报数据类型
#define REPORT_ALL_TEMPLATE "{\\\"method\\\":\\\"report\\\"\\,\\\"clientToken\\\":\\\"00000001\\\"\\,\\\"params\\\":{\\\"dist\\\":%.3f\\,\\\"angx\\\":%.3f\\,\\\"angy\\\":%.3f\\,\\\"warning\\\":\\\"%s\\\"\\,\\\"warn_num\\\":%d}}"
// 1. 构造上报的JSON
snprintf(payload, sizeof(payload), REPORT_ALL_TEMPLATE, adcConvertedDist,Angle_X,Angle_Y,warn_data,warn_num);
// 2. 向数据模板topic发布信息
if (tos_tf_module_mqtt_pub(report_topic_name, QOS0, payload) != 0) {
printf("module mqtt pub fail\n");
break;
} else {
printf("module mqtt pub success\n");
}
点击在线调试
就可以实时看到你的设备上报的数据了
定义一个读写数据
然后在腾讯连连小程序中,就会出现一个可以修改的数据变量
在MCU程序中编写对数据的读取解析
首先看一下平台会返回到的数据payload
topic:$thing/down/property/E0UJSCFA1X/geo_iot
payload:"{"method":"control","clientToken":"clientToken-X6rxefd-H","params":{"int_time":1}}"
然后对其进行解析
static void default_message_handler(mqtt_message_t* msg)
{
cJSON *root;
cJSON *params;
cJSON *token;
cJSON *method;
cJSON *int_time;
int int_time1;
printf("callback:\r\n");
printf("---------------------------------------------------------\r\n");
printf("\ttopic:%s\r\n", msg->topic);
printf("\tpayload:%s\r\n", msg->payload);
printf("---------------------------------------------------------\r\n");
// 1. 解析从云端收到的控制信息,示例控制信息为
//"{"method":"control","clientToken":"clientToken-Xx-N_enad","params":{"int_time":0}}"
root = cJSON_Parse(msg->payload +1);
if (!root){
printf("Invalid json root\r\n");
return;
}
// 2. 解析出method
method = cJSON_GetObjectItem(root, "method");
if (!method){
printf("Invalid json method\r\n");
cJSON_Delete(root);
return;
}
// 3. 仅处理云端下发的 control 数据,report_reply暂不处理
if (0 != strncmp(method->valuestring, "control", sizeof("control") - 1)){
cJSON_Delete(root);
return;
}
// 4. 解析出params
params = cJSON_GetObjectItem(root, "params");
if (!params){
printf("Invalid json params\r\n");
cJSON_Delete(root);
return;
}
// 1. 解析出int_time
int_time = cJSON_GetObjectItem(params, "int_time");
int_time1 = int_time -> valueint;
// 2. 根据int_time控制时间间隔
if (1 == int_time1){
int_time_val = 10000;
printf("get server data success\r\n");
printf("int_time_val = 10000\r\n");
}
if (2 == int_time1){
int_time_val = 20000;
printf("get server data success\r\n");
printf("int_time_val = 20000\r\n");
}
if (3 == int_time1){
int_time_val = 30000;
printf("get server data success\r\n");
printf("int_time_val = 30000\r\n");
}
if (4 == int_time1){
int_time_val = 40000;
printf("get server data success\r\n");
printf("int_time_val = 40000\r\n");
}
// 4. 设置clientToken回复
token = cJSON_GetObjectItem(root, "clientToken");
if (token) {
strcpy(client_token_cache, token->valuestring);
is_client_token_received = true;
}
cJSON_Delete(root);
}
int_time_val
里面存放的是数据采集间隔时间,然后在主循环中编写相应程序
tos_sleep_ms(int_time_val); //根据服务器时间调整间隔
鹅厂的IoT系统中有一个数据流,挺有意思的
左侧选择 数据开发 — 新建数据流
例如我这里 新建一个 警报推送
交互数据里面有输入、处理、输出 三大部分内容
首先添加一个设备数据输入,选择相应产品,然后选择属性
这里的属性就是我们之前定义的数据属性,这里我们选择报警事件
(其实也可以用warn_num,但是这个string类型的数据不能浪费他呀)
添加一个数据过滤,然后加一个条件。
我们一共有四个条件(red、orange、yellow、blue),分别添加四个数据过滤
最下面添加一个输出—公众号推送
然后分别把他们连接起来
然后启用这个数据流
这样的话,我们就可以在腾讯连连 微信公众号中看到报警信息
最后再来一句感慨
鹅厂的IoT平台真的是好用,但是为什么string类型数据不能显示(╯▔皿▔)╯
上面的部分代码是腾讯云物联实验室给的示例代码,本来不想贴出来,但是感觉很多人还是在走弯路,所以还是贴出来大家一起参考一下
然后就是关于面板,因为做硬件开发,很少开发前端,发现H5的开发不只是js部分,还需要SDK配置,这个只能说学无止境,展望一下后续吧
腾讯云IoT Explorer平台和TencentOS-tiny简直是提供了太大的开发便利,一个产品利用腾讯IoT平台进行开发,可以大大缩短周期,能够尽快上线!
不过也有不足,这次开发是基于ESP8266进行开发的,我的设备应用场景多在户外,因此可能更需要用4G和NB-IoT来进行s据传输。这次时间仓促,手边没有相应模块,只能先用WiFi调试了,看来接下来还有的事情做~
短时间内堆起了这么一个小家伙,还是没有把预想的功能全部做完,不足之处还请各位看官多多见谅,大家相互交流。
最后,感谢鹅厂的这次活动~
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。