赞
踩
在做项目之前,我们首先需要知道项目需要用到什么模块,什么协议,还有用哪个云端存储上传的数据。
在此分享我做项目时的要求:
根据要求我们可以知道,在设备端我们需要利用开发板上的ADC旋钮,LED灯和蜂鸣器,并且想办法把这些设备的状态上传到云端,那如何将设备的状态上传到云端呢?这就利用到了MQTT协议。
MQTT(Message Queuing Telemetry Transport,消息队列遥测传输协议),是一种基于发布/订阅(publish/subscribe)模式的"轻量级"通讯协议,该协议构建于TCP/IP协议上。MQTT最大优点在于,可以以极少的代码和有限的带宽,为连接远程设备提供实时可靠的消息服务。作为一种低开销、低带宽占用的即时通讯协议,使其在物联网、小型设备、移动应用等方面有较广泛的应用。
上面的说法是一个概括,如果想要具体了解MQTT协议,可以根据下面链接了解:零基础入门学用物联网 – MQTT基础篇 – 目录 – 太极创客 (taichi-maker.com)
在了解完什么是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作为一个中间网关,接收开发板的真实设备数据,再转发上华为云。
首先,我们需要登录华为云,再搜索框搜索“设备接入”。
选择第一个,因为笔者已经创建好了设备,所以直接选择第一个。如果没有创建设备过的话,直接搜索“设备接入 IoTDA”,界面应该是这样的:
直接选免费试用
这个区域是有讲究的,你以后登录华为云的时候,要记得在哪个区域创建了设备,就像你玩游戏在哪个大区创建了角色一样。
实例名称可以随便填,但是最好填一个跟项目相关的名称。然后进来是这样子的:
因为当前的实例是基础版的,我们需要切换到刚刚创建的专业版实例,这里笔者的实例名称是“互联网实验”,点击详情,然后是这样子的:
点击切换实例,再点击总览:
进来后再点击向导式极速体验快速创建一个云端设备
进来后是这样的:
点击创建产品:
点击注册设备:
点击下一步:
点击下载设备演示包,就会下载一个华为云为你写好的SDK
到此,我们需要回头改一些我们匆匆创建好的设备信息,先修改一下设备的密码:
修改完后点确定
修改完密码后我们需要修改一下设备的属性,把它改成我们需要的属性:
命令可以像这样子设置,也可以发送不同的数据类型,笔者是发的bool型数据用于开关灯,如果命令有多种情况,就需要发送别的数据类型。
这个是笔者已经弄好的设备,可以作为对照
好了,到了这一步,我们在云端的工作就已经做好了。
既然我们已经在云端创建了一个设备,也就是一个云设备,并不是真实的设备,我们需要利用华为云给我们提供的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);
- #else
- usleep(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");//把这个地方的端口改成1883
-
- serverIp_ = 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 JSON
-
- char* object_device_id = JSON_GetStringFromObject(root, "object_device_id", "-1"); //get value of object_device_id
- printfLog(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_id
- printfLog(EN_LOG_LEVEL_INFO, "AgentLiteDemo: handleCommandRequest(), content %s\n", service_id);
-
- char* command_name = JSON_GetStringFromObject(root, "command_name", "-1"); //get value of command_name
- printfLog(EN_LOG_LEVEL_INFO, "AgentLiteDemo: handleCommandRequest(), name %s\n", command_name);
-
- JSON* paras = JSON_GetObjectFromObject(root, "paras"); //get value of data
- printfLog(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 reponse
-
- JSON_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 JSON
-
- char* 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对象,用来存放开关的状态,获取其ID
- shmid = 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. 准备各个模块所需的IPC
- mkfifo("/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);
- }
-
- //准备好本机的ip
- struct 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);
- }
-
- //准备好本机的ip
- struct 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.ttf
- font *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;
-
- //初始化信号量,设置为0
- sem_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);
- //准备好对端的ip
- struct 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] = {"星期日","星期一","星期二","星期三","星期四","星期五","星期六"};
- //把要输出的数据保存到缓冲区buf
- snprintf(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;
- }
-
-
- //把数据写入msg
- snprintf(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数据写入BUF0
- snprintf(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);//为了让文本有一种居中对齐的效果,所以右边偏移量也-200
- showbitmap(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 0
-
-
-
- void 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);
-
- //初始化信号量,设置为0
- sem_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)
- {
- //蜂鸣器状态设置为开,也把蜂鸣器的标记位设置为true
- sem_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���ɹ�����0
- if(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 result
- int 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 report
- Test_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天才全部弄完并把全部的功能实现,费时间的主要在前期弄共享内存,后面弄了好久居然发现用不了,又被迫用回管道这种简单粗暴的方式,在此先祝大家项目成功,希望我的文档对大家有帮助,谢谢大家。
如果还有小伙伴是完全不会弄这个项目的,我在百度网盘存放了整个项目需要用到的各种驱动,还有源码,还有修改过的SDK文件以及我的演示视频,大家可以自行提取,但是不可用直接运行,因为每个人华为云上的设备号都不一样的,还有ip地址也需要修改。
链接:https://pan.baidu.com/s/1C_5E-uOJlV59EGKkQxRijg
提取码:zlw6
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。