本文主要是介绍基于GEC6818开发板的华为云物联网数据监控项目整理,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!
一、项目架构
1、熟悉MQTT协议
在做项目之前,我们首先需要知道项目需要用到什么模块,什么协议,还有用哪个云端存储上传的数据。
在此分享我做项目时的要求:
根据要求我们可以知道,在设备端我们需要利用开发板上的ADC旋钮,LED灯和蜂鸣器,并且想办法把这些设备的状态上传到云端,那如何将设备的状态上传到云端呢?这就利用到了MQTT协议。
2、什么是MQTT协议?
MQTT(Message Queuing Telemetry Transport,消息队列遥测传输协议),是一种基于发布/订阅(publish/subscribe)模式的"轻量级"通讯协议,该协议构建于TCP/IP协议上。MQTT最大优点在于,可以以极少的代码和有限的带宽,为连接远程设备提供实时可靠的消息服务。作为一种低开销、低带宽占用的即时通讯协议,使其在物联网、小型设备、移动应用等方面有较广泛的应用。
上面的说法是一个概括,如果想要具体了解MQTT协议,可以根据下面链接了解:零基础入门学用物联网 – MQTT基础篇 – 目录 – 太极创客 (taichi-maker.com)
3、如何利用MQTT
在了解完什么是MQTT协议后,我们会冒出一个疑问,难道要我们自己写一个使用MQTT协议的代码与云端进行通信吗?那当然不是,很明显我们目前的能力还不够,所以我们就需要大厂的帮助。在此项目中,我选择使用华为云进行物联网连接实验,为了帮助设备快速连接到物联网平台,华为提供了IoT Device SDK。支持TCP/IP协议栈的设备集成IoT Device SDK后,可以直接与物联网平台通信。不支持TCP/IP协议栈的设备,例如蓝牙设备、ZigBee设备等需要利用网关将设备数据转发给物联网平台,此时网关需要事先集成IoT Device SDK。
在这里,我们的想法是,先登录华为云,创建一个云设备,再通过华为云提供的SDK做一定的修改,放在ubuntu上运行,先连通ubuntu和云端的通信。再想办法从开发板那获取到真实设备端的数据。或许有同学会问,那为啥不直接把华为云提供的SDK放到开发板上呢,毕竟开发板也可以连通网络。答案是可以的,但是开发板是基于ARM架构的,华为云提供的SDK还需要进行修改才能在开发板上部署,其中需要用到不同的库,笔者自己试着弄了一下,发现华为云上面提供的操作指导并不完整,或者资源连接到外网下载,没有梯子的话访问不了,而且在处理某一些用到的库的时候,又会报莫名其妙的错误,所以笔者是放弃了。大家有兴趣也可以自己尝试弄一下。在此我们就在ubuntu上部署SDK,让Ubuntu作为一个中间网关,接收开发板的真实设备数据,再转发上华为云。
4、如何利用华为云
首先,我们需要登录华为云,再搜索框搜索“设备接入”。
选择第一个,因为笔者已经创建好了设备,所以直接选择第一个。如果没有创建设备过的话,直接搜索“设备接入 IoTDA”,界面应该是这样的:
直接选免费试用
这个区域是有讲究的,你以后登录华为云的时候,要记得在哪个区域创建了设备,就像你玩游戏在哪个大区创建了角色一样。
实例名称可以随便填,但是最好填一个跟项目相关的名称。然后进来是这样子的:
因为当前的实例是基础版的,我们需要切换到刚刚创建的专业版实例,这里笔者的实例名称是“互联网实验”,点击详情,然后是这样子的:
点击切换实例,再点击总览:
进来后再点击向导式极速体验快速创建一个云端设备
进来后是这样的:
点击创建产品:
点击注册设备:
点击下一步:
点击下载设备演示包,就会下载一个华为云为你写好的SDK
到此,我们需要回头改一些我们匆匆创建好的设备信息,先修改一下设备的密码:
修改完后点确定
修改完密码后我们需要修改一下设备的属性,把它改成我们需要的属性:
命令可以像这样子设置,也可以发送不同的数据类型,笔者是发的bool型数据用于开关灯,如果命令有多种情况,就需要发送别的数据类型。
这个是笔者已经弄好的设备,可以作为对照
好了,到了这一步,我们在云端的工作就已经做好了。
5、打通SDK和华为云的通信,激活云端设备
既然我们已经在云端创建了一个设备,也就是一个云设备,并不是真实的设备,我们需要利用华为云给我们提供的SDK,并修改一下代码,然后放到Ubuntu运行,建立与云端的通信,才能激活我们刚刚在华为云创建的设备。首先,我们把文件放到vscode上:
文件总体是这样子的,这里我们主要对AgentLiteDemo.c还有ClientConf.json进行修改:
先改ClientConf.json:
按照自己的设备id,密码修改
把后面的端口号删除,还有那两个冒号,因为如果固定端口的话,可能出现连不上的情况。
网址跟这个一样:
设备ID在这:
然后再修改AgentLiteDemo.c:
#include "stdio.h"
#include "signal.h"#if defined(WIN32) || defined(WIN64)
#include "windows.h"
#endif#include "pthread.h"#include <math.h>
#include "hw_type.h"
#include "iota_init.h"
#include "iota_cfg.h"
#include <stdlib.h>
#include <stdbool.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <strings.h>
#include <errno.h>
#include <pthread.h>
#include <semaphore.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <net/if.h>
#include "LogUtil.h"
#include "JsonUtil.h"
#include "StringUtil.h"
#include "iota_login.h"
#include "iota_datatrans.h"
#include "string.h"
#include "cJSON.h"
#include "sys/types.h"
#include "unistd.h"
#include "iota_error_type.h"/* if you want to use syslog,you should do this:** #include "syslog.h"* #define _SYS_LOG** */char* workPath = ".";
char* gatewayId = NULL;int alarmValue = 0;char* serverIp_ = "";
int port_ = 1883;//原本是8883,需要把端口改成1883,不然与华为云的tcp会连接失败char* username_ = "64f82801a266cb7f6e6abfd1_gec6818";//deviceId,这个需要根据自己的情况做修改
char* password_ = "12345678";//这也也需要根据自己的情况做修改int disconnected_ = 0;char *subDeviceId = "f6cd4bbb1a8ab53acbb595efd0e90199_ABC123456789";//这个一般不用改int sleepTime = 5000;void timeSleep(int ms)
{
#if defined(WIN32) || defined(WIN64)Sleep(ms);
#elseusleep(ms * 1000);
#endif
}
上面的代码中我已经加入了许多我需要用到的头文件,如果编译出现缺少头文件的情况,需要你加上对应的头文件。
void Test_propertiesReport()
{int serviceNum = 1;//此处是上报的服务个数ST_IOTA_SERVICE_DATA_INFO services[serviceNum];cJSON *root;root = cJSON_CreateObject();//设置一个p操作,如果没有资源会在此处阻塞等待sem_wait(&s);//需要根据自己的设备进行修改,中间的是你的云端设备属性,第三个是值,这里笔者已经用变量代替,原本的只是一个随机数,后面你需要用变量替换cJSON_AddNumberToObject(root, "led", LED_value);cJSON_AddNumberToObject(root, "adc", ADC_value);cJSON_AddNumberToObject(root, "pwm", BEEP_value);char *payload;payload = cJSON_Print(root);cJSON_Delete(root);services[0].event_time = getEventTimeStamp(); //if event_time is set to NULL, the time will be the iot-platform's time.services[0].service_id = "开发板数据监控系统";//这里是一开始弄的那个产品名称,需要根据自己的情况修改services[0].properties = payload;int messageId = IOTA_PropertiesReport(services, serviceNum);if(messageId != 0){printfLog(EN_LOG_LEVEL_ERROR, "AgentLiteDemo: Test_batchPropertiesReport() failed, messageId %d\n", messageId);}free(payload);
}
void setConnectConfig(){FILE *file;long length;char *content;cJSON *json;file=fopen("./ClientConf.json","rb");fseek(file,0,SEEK_END);length = ftell(file);fseek(file,0,SEEK_SET);content = (char*)malloc(length+1);fread(content,1,length,file);fclose(file);json = cJSON_Parse(content);username_ = JSON_GetStringFromObject(json, "deviceId", NULL);password_ = JSON_GetStringFromObject(json, "secret", NULL);char *url = JSON_GetStringFromObject(json, "serverUri", NULL);deleteSubStr(url,"ssl://");deleteSubStr(url,":1883");//把这个地方的端口改成1883serverIp_ = url;
}
把上面的端口改一改
如果你需要弄云端下发命令,那么还需要修改一处:
这是原本的代码:
void handleCommandRequest(void* context, int messageId, int code, char *message, char *requestId)
{printfLog(EN_LOG_LEVEL_INFO, "AgentLiteDemo: handleCommandRequest(), messageId %d, code %d, messsage %s, requestId %s\n", messageId, code, message, requestId);JSON * root = JSON_Parse(message); //Convert string to JSONchar* object_device_id = JSON_GetStringFromObject(root, "object_device_id", "-1"); //get value of object_device_idprintfLog(EN_LOG_LEVEL_INFO, "AgentLiteDemo: handleCommandRequest(), object_device_id %s\n", object_device_id);char* service_id = JSON_GetStringFromObject(root, "service_id", "-1"); //get value of service_idprintfLog(EN_LOG_LEVEL_INFO, "AgentLiteDemo: handleCommandRequest(), content %s\n", service_id);char* command_name = JSON_GetStringFromObject(root, "command_name", "-1"); //get value of command_nameprintfLog(EN_LOG_LEVEL_INFO, "AgentLiteDemo: handleCommandRequest(), name %s\n", command_name);JSON* paras = JSON_GetObjectFromObject(root, "paras"); //get value of dataprintfLog(EN_LOG_LEVEL_INFO, "AgentLiteDemo: handleCommandRequest(), id %s\n", paras);if (paras){sleepTime = JSON_GetIntFromObject(paras, "value", 1) * 1000;printfLog(EN_LOG_LEVEL_INFO, "AgentLiteDemo: handleCommandRequest(), sleepTime %d\n", sleepTime);}Test_commandResponse(requestId); //command reponseJSON_Delete(root);}
这是笔者根据自己的项目需要进行的修改,仅供参考:
void handleCommandRequest(void* context, int messageId, int code, char *message, char *requestId)
{//printfLog(EN_LOG_LEVEL_INFO, "AgentLiteDemo: handleCommandRequest(), messageId %d, code %d, messsage %s, requestId %s\n", messageId, code, message, requestId);//这里的修改需要特别注意,如果需要对两种不同的命令做出判断,需要先提取command_name后,再细分判断,不然会报错JSON * root = JSON_Parse(message); //Convert string to JSONchar* command_name = JSON_GetStringFromObject(root, "command_name", "-1");if(strstr(command_name,"led")){JSON* paras = JSON_GetObjectFromObject(root, "paras");JSON* lled = JSON_GetObjectFromObject(paras, "led");// memcpy(sendled[0],cJSON_Print(lled),strlen(cJSON_Print(lled)));// printf("%s\n",sendled[0]);if (JSON_GetBoolFromObject(paras, "led状态", NULL) == true ){strcpy(ledstat, "true");//设置一个v操作,表示收到云端下发的命令sem_post(&xf);}else{strcpy(ledstat, "false");//设置一个v操作,表示收到云端下发的命令sem_post(&xf);}printf("%s\n", JSON_GetBoolFromObject(paras, "led状态", NULL)?"开灯":"关灯");}if(strstr(command_name,"beep")){JSON* paras = JSON_GetObjectFromObject(root, "paras");JSON* lled = JSON_GetObjectFromObject(paras, "beep");if (JSON_GetBoolFromObject(paras, "beep状态", NULL) == true ){strcpy(beepstat, "true");//设置一个v操作,表示收到云端下发的命令sem_post(&bp);}else{strcpy(beepstat, "false");//设置一个v操作,表示收到云端下发的命令sem_post(&bp);} printf("%s\n", JSON_GetBoolFromObject(paras, "beep状态", NULL)?"开警报":"关警报"); }JSON_Delete(root);}
如此修改完之后,保存后,在编译器直接输入make进行编译,然后给脚本文件start.sh赋予777权限,不然执行的时候会报权限不足的问题,编译好之后,在终端直接输入./start.sh执行这个SDK文件,运行结果会是如此:
可以看到,Ubuntu已经顺利执行华为云提供的SDK,在云端也可以看见设备已经激活,并且已经向云端发送ADC,LED,BEEP的数据,(此时的数据并不是真实设备的数据,只是rand函数随机模拟的数据,如果你想要发送真实设备的数据,还需要接通开发板上的设备),反正不管怎么说,现在已经打通了Ubuntu与云端的通信,下一步就应该想着怎么打通Ubuntu与开发板的联系,就是怎么把开发板的真实设备状态通过tcp或者udp发送到ubuntu,再由ubuntu转发给云端,以达到物联网数据监测的目的。
6、编写设备端代码
这里说的设备端是真实设备,也就是开发板上的蜂鸣器,LED和ADC旋钮,如果你需要将设备的状态简单的显示在开发板上,那还需要用到lcd屏幕,要控制这些硬件,当然需要相应的驱动模块,这里笔者就不给出驱动文件,在这提一嘴笔者踩过的雷,驱动一定要安装正确的版本与之相对应,不然会出现莫名其妙的错误。
这里给出一些常用的驱动命令:
lsmod:列出当前装载的驱动模块
rmmod: 删除指定的驱动模块
insmod: 安装指定驱动模块
还有对应的设备路径:
蜂鸣器:beep/buzzer/pwm
ADC: adc/gec6818_adc
LED: led/Led/gec_led
按键:button/gecBt/key
好了,铺垫了这么久,我们现在开始编写设备端的代码。在编写之前,我们需要考虑,每一个设备都有其对应的控制代码,还有其对应的逻辑控制。也就是每一个设备都有自己的.c文件,每个.c文件都有一个main函数。那么该如何实现,这些设备之间的交互呢,比如ADC到达一定的阈值后会触发警报。那就需要用到我们学过的知识了——进程间的通信。我们可以弄一个管理进程,通过exce函数启动其他设备的进程,例如:
#include "head.h"//蜂鸣器标记位,配合共享内存使用的,可惜没玩明白共享内存,暂时报废
const char *beepmanageron = "true";//设置一个互斥锁,所有的锁都是为了共享内存准备的,很可惜没玩明白共享内存
pthread_mutex_t m = PTHREAD_MUTEX_INITIALIZER;int main(int argc, char const *argv[])
{ //想用共享内存没有用出来,可惜,所以关于共享内存的代码都是无效的,不用管int shmid;// 创建或打开一个大小为30的SHM对象,用来存放开关的状态,获取其IDshmid = shmget(key, 40, IPC_CREAT|0666);if(shmid < 0){perror("创建SHM对象失败");}//映射共享内存char *shm_addr = shmat(shmid, NULL, 0);pthread_mutex_lock(&m);//把蜂鸣器的标记位写入共享内存(30-40),共享内存的代码用不着,不用在意memcpy(shm_addr + 30, beepmanageron, strlen(beepmanageron));pthread_mutex_unlock(&m);printf("共享内存内容:%s\n", shm_addr);// 1. 准备各个模块所需的IPCmkfifo("/root/fifo", O_CREAT|0666);mkfifo("/root/led", O_CREAT|0666);mkfifo("/root/beep", O_CREAT|0666);mkfifo("/root/ledstat", O_CREAT|0666);mkfifo("/root/beepstat", O_CREAT|0666);mkfifo("/root/setting", O_CREAT|0666);// 2. 依次启动各个模块if(fork() == 0){ execl("./adc_test", "./adc_test", NULL);}if(fork() == 0){execl("./buzzer", "./buzzer", NULL);}if(fork() == 0){execl("./bt_test", "./bt_test" , NULL);}if(fork() == 0){execl("./led_test", "led_test", NULL);}// 分离共享内存与当前进程,很可惜没弄出来if (shmdt(shm_addr) == -1) {perror("shmdt");return 1;}// 删除共享内存对象if (shmctl(shmid, IPC_RMID, NULL) == -1) {perror("shmctl");return 1;}pause();return 0;
}
这是笔者的管理程序,笔者通过execl函数启动其他的子线程,使用具名管道进行通信。如果大家觉得很麻烦,那大可以把所有的.c文件都放在一个文件里面,这样就不需要进程间通信,也不需要弄共享内存这些吃力不讨好的东西,但是问题在于一个.c文件集成了所有的设备代码,会显得很臃肿,而且也不能练习我们学过的各种进程间通信的知识。所以笔者不建议都弄到一个文件。
下面展示笔者弄的各设备模块的代码:
adc_test2.c
#include "head.h"#define GEC6818_ADC_IN0 _IOR('A', 1, unsigned long)
#define GEC6818_ADC_IN1 _IOR('A', 2, unsigned long)int sockfd;
//套接字
int recvfd, sendfd, recvfd1;
//这两个数组用来存放灯和凤鸣器的状态
char lednow[6];
char beepnow[6];//两个信号量,用来让两个更新程序一直等待执行
sem_t lo;
sem_t bo;
sem_t lf;
sem_t bf;
//这个是屏幕显示的方法
void showbitmap(bitmap *bm, int x, int y, char *p)
{// 直接写指针,不在这里写打开设备,避免重复调用打开char *q = p + (y*800 + x)*4;for(int j=0;j<bm->height && j < 480 ;j++){for(int i=0;i<bm->width && i< 800 ;i++) memcpy(q+(800*4*j)+i*4,bm->map+(bm->width*bm->byteperpixel*j)+bm->byteperpixel*i,bm->byteperpixel);} bzero(bm->map,bm->width*bm->height*bm->byteperpixel);
}//这个函数用来一直更新灯的状态
void *updateledon(void *arg)
{while (1){//p操作,如果没有资源就会一直等待sem_wait(&lo);strcpy(lednow, "on");}}void *updateledoff(void *arg)
{while (1){//p操作,如果没有资源就会一直等待sem_wait(&lf);strcpy(lednow, "off");}}//这个函数用来一直更新蜂鸣器的状态
void *updatebeepon(void *arg)
{while (1){//p操作,如果没有资源就会一直等待sem_wait(&bo);strcpy(beepnow, "on");}
}void *updatebeepoff(void *arg)
{while (1){//p操作,如果没有资源就会一直等待sem_wait(&bf);strcpy(beepnow, "off");}
}//用来接收云端发送过来的警报指令
void *recvudp1(void *arg)
{ //打开led具名管道,把读到的云端命令写到管道里面int beepfd = open("/root/fifo", O_RDWR);int setting = open("/root/setting", O_RDWR | O_NONBLOCK);if(beepfd == -1 || setting == -1){perror("打开BEEP和setting具名管道失败!");exit(0);}//准备好本机的ipstruct sockaddr_in addr = {0};addr.sin_family = AF_INET;//当一个服务器程序需要绑定到本机的某个ip地址上时,可以使用这个//表示服务器愿意接受来自任何可用网络接口的连接addr.sin_addr.s_addr = htonl(INADDR_ANY);addr.sin_port = htons(50003);//绑定上面的地址if(bind(recvfd1, (struct sockaddr *)&addr, sizeof(addr)) != 0){perror("102绑定地址失败!");}//等待对方发来的信息struct sockaddr_in clientAddr;//用来存放信息char buf[6];while (1){bzero(buf, 6);socklen_t len = sizeof(clientAddr);bzero(&clientAddr, len);//等待udp数据int n = recvfrom(recvfd1, buf, 6, 0,(struct sockaddr *)&clientAddr, &len);if (buf[0] == 't'){printf("收到来自云端的命令[%s:%hu]:%s\n", inet_ntoa(clientAddr.sin_addr),ntohs(clientAddr.sin_port),"开警报!");//把开警报信息写入管道write(setting, "on", 2);usleep(200*1000);write(beepfd, "on", 2);sem_post(&bo);}else if (buf[0] == 'f'){printf("收到来自云端的命令[%s:%hu]:%s\n", inet_ntoa(clientAddr.sin_addr),ntohs(clientAddr.sin_port),"关警报!");//把关灯信息写入管道write(beepfd, "off", 3);write(setting, "off", 3);sem_post(&bf);}else{printf("与云端[%s:%hu]:%s\n", inet_ntoa(clientAddr.sin_addr),ntohs(clientAddr.sin_port),"通信良好!");}}}//用来接收云端发送过来的灯指令
void *recvudp(void *arg)
{ //打开led具名管道,把读到的云端命令写到管道里面int ledfd = open("/root/led", O_RDWR);if(ledfd == -1){perror("打开LED具名管道失败!");exit(0);}//准备好本机的ipstruct sockaddr_in addr = {0};addr.sin_family = AF_INET;//当一个服务器程序需要绑定到本机的某个ip地址上时,可以使用这个//表示服务器愿意接受来自任何可用网络接口的连接addr.sin_addr.s_addr = htonl(INADDR_ANY);addr.sin_port = htons(50002);//绑定上面的地址if(bind(recvfd, (struct sockaddr *)&addr, sizeof(addr)) != 0){perror("154绑定地址失败!");}//打开显示设备,实现把状态信息显示到屏幕上//等待对方发来的信息struct sockaddr_in clientAddr;//用来存放信息char buf[6];while (1){bzero(buf, 6);socklen_t len = sizeof(clientAddr);bzero(&clientAddr, len);//等待udp数据int n = recvfrom(recvfd, buf, 6, 0,(struct sockaddr *)&clientAddr, &len);if (buf[0] == 't'){printf("收到来自云端的命令[%s:%hu]:%s\n", inet_ntoa(clientAddr.sin_addr),ntohs(clientAddr.sin_port),"开灯!");//把开灯信息写入管道write(ledfd, "on", 2);sem_post(&lo);}else if (buf[0] == 'f'){printf("收到来自云端的命令[%s:%hu]:%s\n", inet_ntoa(clientAddr.sin_addr),ntohs(clientAddr.sin_port),"关灯!");//把关灯信息写入管道write(ledfd, "off", 3);sem_post(&lf);}else{printf("与云端[%s:%hu]:%s\n", inet_ntoa(clientAddr.sin_addr),ntohs(clientAddr.sin_port),"通信良好!");}}}//设置一个互斥锁, 本来是用来控制读写共享内存的,结果没弄出来,所以不必在意
pthread_mutex_t m = PTHREAD_MUTEX_INITIALIZER;
pthread_mutex_t a = PTHREAD_MUTEX_INITIALIZER;int main(int argc, char **argv)
{//打开设备int lcd = open("/dev/fb0",O_RDWR);if (lcd == -1){perror("打开屏幕设备错误!");exit(0);}// 获取屏幕属性struct fb_var_screeninfo fixinfo; ioctl(lcd,FBIOGET_VSCREENINFO,&fixinfo); unsigned long VWIDTH = fixinfo.xres; //可见区宽度(单位:像素)unsigned long VHEIGHT = fixinfo.yres; //可见区高度(单位:像素)unsigned long BPP = fixinfo.bits_per_pixel; //色深 char *p = mmap(NULL, VWIDTH * VHEIGHT * BPP/8, PROT_WRITE,MAP_SHARED, lcd, 0); bzero(p,VWIDTH*VHEIGHT*BPP/8);//1.初始化字库 font *f1 = fontLoad("simfang.ttf"); // 指定字库文件,比如simfang.ttffont *f2 = fontLoad("simfang.ttf"); font *f3 = fontLoad("simfang.ttf"); font *f4 = fontLoad("simfang.ttf"); //2.设置字体的大小 fontSetSize(f1, 40);fontSetSize(f2, 70);fontSetSize(f3, 60);fontSetSize(f4, 60);//3.设置指针指向分配框区域bitmap *bm1;bitmap *bm2;bitmap *bm3;bitmap *bm4;//4.给分配框设置不同的大小,因为第三块用来显示通知文本,所以分了200行bm1 = createBitmapWithInit(800, 50, 4, 0x00000000);bm2 = createBitmapWithInit(800, 200, 4, 0x00000000);bm3 = createBitmapWithInit(800, 100, 4, 0x00000000);bm4 = createBitmapWithInit(800, 100, 4, 0x00000000);int fd=-1;int rt;int i=0;unsigned long adc_vol = 0;//初始化信号量,设置为0sem_init(&lo, 0, 0);sem_init(&bo,0, 0);sem_init(&lf, 0, 0);sem_init(&bf,0, 0);// 创建SHM对象或者打开int shmid = shmget(key, 40, IPC_CREAT|0666);if (shmid == -1) {perror("shmget");return 1;}//映射共享内存char *shm_addr = shmat(shmid, NULL, 0); //创建一个套接字sockfd = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);recvfd = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);recvfd1 = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);//sendfd = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);if (sockfd == -1 ||recvfd == -1 || recvfd1 == -1){perror("创建套接字失败!");}//创建两个线程,一个运行发送,一个运行接收pthread_t t1,t2,t3,t4,t5,t6;pthread_create(&t1, NULL, recvudp, NULL);pthread_create(&t2, NULL, recvudp1, NULL);pthread_create(&t3, NULL, updateledon, NULL);pthread_create(&t4, NULL, updateledoff, NULL);pthread_create(&t5, NULL, updatebeepon, NULL);pthread_create(&t6, NULL, updatebeepoff, NULL);//准备好对端的ipstruct sockaddr_in addr;socklen_t len = sizeof(addr);bzero(&addr, len);addr.sin_family = AF_INET;addr.sin_addr.s_addr = inet_addr("192.168.2.10");addr.sin_port = htons(50001);//打开adc设备fd = open("/dev/adc_drv", O_RDWR);if(fd < 0){perror("open /dev/adc_drv:");return fd;}//弄一个具名管道,把读到的数据写到管道里面int fifofd = open("/root/fifo", O_RDWR);//这两个管道用来更新灯和蜂鸣器的状态int ledstat = open("/root/ledstat", O_RDONLY | O_NONBLOCK);int beepstat = open("/root/beepstat", O_RDONLY | O_NONBLOCK);int setting = open("/root/setting", O_RDONLY | O_NONBLOCK);if(fifofd == -1 || ledstat == -1 || beepstat == -1){perror("打开具名管道失败!");exit(0);}unsigned long n = 0;//设置一个字符数组用于存放adc数据char msg[10];char led[6];char beep[6];char ledmsg[6];char beepmsg[6];//这个字符串用来存储ADC数据char BUF1[100]; bzero(BUF1,100);//这个字符串用来存储灯状态char BUF2[100]; bzero(BUF2,100);//这个字符串用来存储蜂鸣器的状态char BUF3[50]; bzero(BUF3,50);//读取共享内存内的灯和蜂鸣器状态pthread_mutex_lock(&m);memcpy(led, shm_addr + 20, 6);memcpy(beep, shm_addr + 20, 6);pthread_mutex_unlock(&m);while(1){ //显示系统时间time_t t; //获取系统时间struct tm *Time;time(&t);char buf[50]; //定义buf缓冲区,用来存放时间数据bzero(buf,50); Time=localtime(&t); char *wd[7] = {"星期日","星期一","星期二","星期三","星期四","星期五","星期六"}; //把要输出的数据保存到缓冲区bufsnprintf(buf,50,"%d年%d月%d日 %s %d时%d分%d秒",(1900+Time->tm_year),(1+Time->tm_mon),(Time->tm_mday),wd[Time->tm_wday],(Time->tm_hour),Time->tm_min,Time->tm_sec);//这些数组是暂时用来存放管道里面的数据的bzero(msg, 10);bzero(ledmsg, 6);bzero(beepmsg, 6);//读取管道内的数据read(ledstat, ledmsg, 6);read(beepstat, beepmsg, 6); //读取ADC通道0的电压值rt=ioctl(fd,GEC6818_ADC_IN0,&adc_vol);if(strcmp(ledmsg, "on") == 0){ //让灯更新为开sem_post(&lo);}if(strcmp(ledmsg, "off") == 0){ //让灯更新为关sem_post(&lf);}if(strcmp(beepmsg, "on") == 0){ //让蜂鸣器状态更新为开sem_post(&bo);}if(strcmp(beepmsg, "off") == 0){ //让蜂鸣器状态更新为关sem_post(&bf);}if(rt != 0){printf("adc in0 read filed\r\n"); usleep(50*1000); continue;}unsigned long m1 = adc_vol + n;if(m1 >= 3000 && m1 <= 3100){ //把数据写到具名管道write(fifofd, "on", 2);n = n + 100;}if (m1 > 3500){n = 0;}if (m1 >800 && m1 <3000){n = n + 100;}if (m1 > 3100){n = n + 100;}//把数据写入msgsnprintf(msg, sizeof(msg), "%lu", m1);//发送数据int n = sendto(sockfd, msg, strlen(msg), 0,(struct sockaddr *)&addr, sizeof(addr));//发送led灯的状态数据int n1 = sendto(sockfd, lednow, strlen(lednow), 0,(struct sockaddr *)&addr, sizeof(addr));//发送蜂鸣器的状态数据int n2 = sendto(sockfd, beepnow, strlen(beepnow), 0,(struct sockaddr *)&addr, sizeof(addr));if(n == -1){perror("发送adc数据失败!");}if(n1 == -1){perror("发送led数据失败!");}else{printf("发送成功!");}if(n2 == -1){perror("发送beep数据失败!");} printf("温度: %lu mv\r\n",m1);// 从共享内存中读取数据// printf("共享内存内容:%s\n", shm_addr);printf("灯状态:%s\n", (strcmp(lednow, "on") == 0) ? "开" : "关");printf("蜂鸣器状态:%s\n", (strcmp(beepnow, "on") == 0) ? "开" : "关");//把要输出的内容显示到显示屏上,50表示x的偏移量,5表示距离上一个分配框的距离 //把adc数据写入BUF0snprintf(BUF1, 100, "ADC值:%lu", m1);snprintf(BUF2, 100, "灯状态:%s\n", (strcmp(lednow, "on") == 0) ? "开" : "关");snprintf(BUF3, 50, "蜂鸣器状态:%s\n", (strcmp(beepnow, "on") == 0) ? "开" : "关");fontPrint(f1, bm1 ,50 ,5,buf, 0x00FFFF00, 0); fontPrint(f2, bm2, 5,40, BUF1, 0xFF000000, 0); fontPrint(f4, bm3,100,5, BUF2, 0xFF33CC66,750); fontPrint(f3, bm4,100,40, BUF3, 0xFFD70000, 0); //bm妥善地放置到LCD上显示出来 showbitmap(bm1, 10, 0, p); showbitmap(bm2, 200, 50, p); showbitmap(bm3, 100, 200, p);//为了让文本有一种居中对齐的效果,所以右边偏移量也-200showbitmap(bm4, 100, 270, p); sleep(1);}// 分离共享内存与当前进程if (shmdt(shm_addr) == -1) {perror("shmdt");return 1;}// 删除共享内存对象if (shmctl(shmid, IPC_RMID, NULL) == -1) {perror("shmctl");return 1;}close(fd); return 0;
}
这个adc_test2.c里面集成了控制蜂鸣器开关的代码,集成了接收云端命令控制led灯开关和蜂鸣器开关的代码,还整合了所有的设备信息向ubuntu发送的功能,还有在lcd上显示设备信息的功能,所以代码比较长,大家仅供参考。
buzzer.c代码:
#include "head.h"#define BUZZER_IOCTL_SET_FREQ 1
#define BUZZER_IOCTL_STOP 0void Usage(char *args)
{printf("Usage: %s <on/off> <freq>\n",args);return ;
}//定义一个全局变量
int buzzer_fd;//设置一个匿名POSIX信号量用来控制线程运行
sem_t s;
sem_t bt;//设置一个互斥锁
pthread_mutex_t m = PTHREAD_MUTEX_INITIALIZER;//设置蜂鸣器的标记位
const char *beepmanageron = "true";
const char *beepmanageroff = "false";
//蜂鸣器状态
const char *beepstaton = "on";
const char *beepstatoff = "off";//设置一个函数,用来关闭蜂鸣器
void *shutdownbeep(void *arg)
{ //这个管道用来更新蜂鸣器的状态int beepstat1 = open("/root/beepstat", O_RDWR);while (1){sem_wait(&bt);ioctl(buzzer_fd, BUZZER_IOCTL_STOP, 2000);write(beepstat1, "off", 3);}}//设置一个函数,用来设置蜂鸣器的声音频率
void *beepAlarm(void *arg)
{ //这个管道用来更新蜂鸣器的状态int beepstat1 = open("/root/beepstat", O_RDWR);while (1){ //p操作,如果没有资源就会一直等待sem_wait(&s);ioctl(buzzer_fd, BUZZER_IOCTL_SET_FREQ, 2000);usleep(200*1000);//ioctl(buzzer_fd, BUZZER_IOCTL_STOP, 2000);usleep(200*1000); write(beepstat1, "on", 2);}}int main(int argc , char **argv)
{ // 创建SHM对象或者打开int shmid = shmget(key, 40, 0644|IPC_CREAT);if (shmid == -1) {perror("shmget");return 1;}//映射共享内存char *shm_addr = shmat(shmid, NULL, 0);//初始化信号量,设置为0sem_init(&s, 0, 0);sem_init(&bt,0, 0);unsigned long freq = 0;char *endstr, *str;buzzer_fd = open("/dev/pwm", O_RDWR);if(buzzer_fd < 0){perror("打开设备失败:");exit(1);}//创建线程,以便于运行蜂鸣器pthread_t t, k;pthread_create(&t, NULL, beepAlarm, NULL);pthread_create(&k, NULL, shutdownbeep, NULL);//打开具名管道int fifofd = open("/root/fifo", O_RDWR);int beepsetting = open("/root/setting", O_RDWR | O_NONBLOCK);//用一个字符数组来存放读到的数据char buf[5];char set[5];//这个是蜂鸣器的开关标记位int setting = 1;while (1){ bzero(buf, 5);bzero(set, 5);read(fifofd, buf, 5);read(beepsetting, set, 5);// 从共享内存的第30个字节到第40个字节之间读取蜂鸣器的标记char beepsingal[6];pthread_mutex_lock(&m);memcpy(beepsingal, shm_addr + 30, 6);pthread_mutex_unlock(&m);if(strcmp(set, "off")==0){setting = 0;}if(strcmp(set, "on")==0){setting = 1;}//如果读到on,就执行v操作 if(strcmp(buf, "on")==0 /*&& strcmp(beepsingal, "true") == 0*/){ if(setting == 1){sem_post(&s);}// printf("worning!\n");pthread_mutex_lock(&m);//蜂鸣器状态设置为开memcpy(shm_addr + 20, beepstaton, strlen(beepstaton));pthread_mutex_unlock(&m);}if(strcmp(buf, "off") == 0){ //让关蜂鸣器程序开启sem_post(&bt);}if(strcmp(buf, "bpon") == 0){ //蜂鸣器状态设置为开,也把蜂鸣器的标记位设置为truesem_post(&s);} }// 分离共享内存与当前进程,下面这两个代码都是用不到的,为了避免删除后会发生未知错误,我就不动他们了if (shmdt(shm_addr) == -1) {perror("shmdt");return 1;}// 删除共享内存对象if (shmctl(shmid, IPC_RMID, NULL) == -1) {perror("shmctl");return 1;}close(buzzer_fd);return 0;
}
这个是蜂鸣器的控制代码,里面的逻辑仅供参考,共享内存相关的代码不用理会,因为作者一开始想用共享内存记录各种设备的状态,结果没弄出来,遇到了一些不可控的错误,后面笔者改用管道了,大家也可以用消息队列,不一定要用管道。
led_test.c代码:
#include "head.h"#define TEST_MAGIC 'x'
#define TEST_MAX_NR 2 //每个led灯的控制
#define LED1 _IO(TEST_MAGIC, 0)
#define LED2 _IO(TEST_MAGIC, 1)
#define LED3 _IO(TEST_MAGIC, 2)
#define LED4 _IO(TEST_MAGIC, 3)//设置信号量
sem_t s;
sem_t s1;int fd;
const char *ledison = "on";
const char *ledisoff = "off";//共享内存的东西,不用管
pthread_mutex_t m = PTHREAD_MUTEX_INITIALIZER;void *ledon(void *arg)
{ char *shm_addr = (char *)arg;while(1){ sem_wait(&s);pthread_mutex_lock(&m);ioctl(fd, LED1, 0);ioctl(fd, LED2, 0);ioctl(fd, LED3, 0);ioctl(fd, LED4, 0);// 共享内存的东西,不用管memcpy(shm_addr + 10, ledison, strlen(ledison));pthread_mutex_unlock(&m);}
}//控制灯开
void *ledoff(void *arg)
{ char *shm_addr = (char *)arg;while(1){ sem_wait(&s1);pthread_mutex_lock(&m);ioctl(fd, LED1, 1);ioctl(fd, LED2, 1);ioctl(fd, LED3, 1);ioctl(fd, LED4, 1);memcpy(shm_addr + 10, ledisoff, strlen(ledisoff));pthread_mutex_unlock(&m);}
}int main(int argc, char **argv)
{// 共享内存,不用管int shmid = shmget(key, 40, IPC_CREAT|0666);if (shmid == -1) {perror("shmget");return 1;}//不必理会char *shm_addr = shmat(shmid, NULL, 0);// 控制灯开sem_init(&s, 0, 0);sem_init(&s1, 0, 0);//共享内存不必理会pthread_t t1, t2;pthread_create(&t1, NULL, ledon, (void*)shm_addr);pthread_create(&t2, NULL, ledoff, (void*)shm_addr);fd = open("/dev/Led",O_RDWR); //���豸�µ�LED���ɹ�����0if(fd<0){perror("Can not open /dev/LED\n");return 0;}//打开具名管道,把灯的状态写进去int fifofd = open("/root/led", O_RDWR);//��һ���ַ���������Ŷ�ȡ����led����char buf[5];while (1){bzero(buf, 5);read(fifofd, buf, 5);if (strcmp(buf, "on") == 0){ //v�������������on�ͻ��һ���ź���sem_post(&s);}else{//����off�ͻ���صƳ����һ���ź���sem_post(&s1);}}// ���빲���ڴ��뵱ǰ����if (shmdt(shm_addr) == -1) {perror("shmdt");return 1;}// ɾ�������ڴ����if (shmctl(shmid, IPC_RMID, NULL) == -1) {perror("shmctl");return 1;}close(fd);return 0;}
这乱码不用管,看得懂代码逻辑即可,笔者也不知道怎么回事突然乱码了(TvT)。
好了,以上就是用得到的模块的代码了,至于显示状态在LCD屏幕上,那肯定需要用得到字库跟lcd,已经是用的很熟悉了,笔者就不再赘述,上面所有的模块代码都只是仅供参考,因为每一个人的思路不一样,我这也只是其中的一种。头文件我没给出,如果大家需要设备端的源码,可以从我的百度云盘提取:链接:百度网盘 请输入提取码
提取码:zlw6
二、总结
总的来说整个项目并不难,难的是一开始你没有经验,然后不知道该做什么,怎么做。所以一开始确定方向很重要,你需要把整个项目的思路理清楚,才能按照你的思路做下去,打通SDK与Ubuntu的联系并不难,难的是如果你需要从云端发送命令控制设备端,就需要对SDK的源码进行修改,其中有很多你需要摸索的函数,该如何使用他们。SDK提供了很多函数,在你使用不同的数据类型发送命令的时候,也会用到不同的函数。笔者自己修改SDK的源码实现命令下发判断的时候,就踩了很多坑,后面才和伙伴一起摸索出来,“哦,原来用这个函数才可以”。所以,没有一个项目是一帆风顺的,趁现在还在学习,多踩一点坑,以后工作的时候就会少踩一点坑。
在整个项目中,笔者使用的是UDP进行ubuntu对云端命令的转发,以及开发板向ubuntu发送设备数据,因为数据量不大,所以UDP就够了,而且ubuntu也是5秒才上传一次数据到云端,就算UDP一两次的数据丢了也不要紧。如果大家要实现tcp通信,也可以在SDK的main函数里创建套接字进行实现。
最后提供笔者的AgentLiteDemo.c的main函数作参考
int main(int argc, char **argv)
{
#if defined(_DEBUG)setvbuf(stdout, NULL, _IONBF, 0); //in order to make the console log printed immediately at debug mode
#endif//创建两个套接字//recvfd = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);sendfd = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);if (sendfd == -1){perror("创建套接字失败!");exit(0);}// 信号量初始化sem_init(&s, 0, 0);sem_init(&xf,0, 1);sem_init(&bp,0, 1);IOTA_SetPrintLogCallback(myPrintLog);setConnectConfig();printfLog(EN_LOG_LEVEL_INFO, "AgentLiteDemo: start test ===================>\n");if (IOTA_Init(workPath) > 0){printfLog(EN_LOG_LEVEL_ERROR, "AgentLiteDemo: IOTA_Init() error, init failed\n");return 1;}setAuthConfig();setMyCallbacks();//see handleLoginSuccess and handleLoginFailure for login resultint ret = IOTA_Connect();if (ret != 0){printfLog(EN_LOG_LEVEL_ERROR, "AgentLiteDemo: IOTA_Auth() error, Auth failed, result %d\n", ret);}//创建一个线程用来执行udp链接pthread_t t, t1,t2;pthread_create(&t, NULL, recvDate, NULL);pthread_create(&t1, NULL, sendUDP, NULL);pthread_create(&t2, NULL, sendUDP1, NULL);//pthread_create(&t2, NULL, recvudp, NULL);timeSleep(1500);int count = 0;while(count < 10000){// //message up
// Test_messageReport();//properties reportTest_propertiesReport();// //batchProperties report
// Test_batchPropertiesReport();
//
// //command response
// Test_commandResponse("1005");
//
// timeSleep(1500);
//
// //propSetResponse
// Test_propSetResponse("1006");
//
// timeSleep(1500);
//
// //propSetResponse
// Test_propGetResponse("1007");timeSleep(sleepTime);count++;}while (1){timeSleep(50);}return 0;
}
这个项目笔者耗时5天才全部弄完并把全部的功能实现,费时间的主要在前期弄共享内存,后面弄了好久居然发现用不了,又被迫用回管道这种简单粗暴的方式,在此先祝大家项目成功,希望我的文档对大家有帮助,谢谢大家。
笔者版本:
Ubuntu20.04 windows11 VMware17
后记:
如果还有小伙伴是完全不会弄这个项目的,我在百度网盘存放了整个项目需要用到的各种驱动,还有源码,还有修改过的SDK文件以及我的演示视频,大家可以自行提取,但是不可用直接运行,因为每个人华为云上的设备号都不一样的,还有ip地址也需要修改。
链接:https://pan.baidu.com/s/1C_5E-uOJlV59EGKkQxRijg
提取码:zlw6
这篇关于基于GEC6818开发板的华为云物联网数据监控项目整理的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!