基于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);
#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天才全部弄完并把全部的功能实现,费时间的主要在前期弄共享内存,后面弄了好久居然发现用不了,又被迫用回管道这种简单粗暴的方式,在此先祝大家项目成功,希望我的文档对大家有帮助,谢谢大家。

笔者版本:

Ubuntu20.04 windows11 VMware17

后记:

        如果还有小伙伴是完全不会弄这个项目的,我在百度网盘存放了整个项目需要用到的各种驱动,还有源码,还有修改过的SDK文件以及我的演示视频,大家可以自行提取,但是不可用直接运行,因为每个人华为云上的设备号都不一样的,还有ip地址也需要修改。

链接:https://pan.baidu.com/s/1C_5E-uOJlV59EGKkQxRijg 
提取码:zlw6

评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值