1712|2

155

帖子

1

TA的资源

一粒金砂(高级)

楼主
 

【建筑施工监测与安防系统】十四、项目核心功能实现 [复制链接]

 

       本人参赛项目的核心功能是施工现场压力等监测,并提供阈值报警。其中利用Kaluga板实现上位机的功能,Kaluga板主控ESP32 S2通过TCP(作为TCP Client)连接网关(作为TCP Server),向网关发送“网关ID+终端ID+命令”数据包,控制网关唤醒终端并作监测数据采集。之后,终端的采集数据(封装好格式)会回传网关,再由网关发给Kaluga板。Kaluga板则利用MQTT协议将收到的数据上传至OneNET云平台,用户端就可以通过APP等进行监测数据的查看并回传命令,以控制Kaluga板的工作。

       也就是说,Kaluga板每次只能获取一个终端的监测值,那么间隔period分钟(period是提前配置到TF卡中的参数)开启一次轮询,通过网关访问获取所有的终端。

       这里一次尝试获取终端的过程,不能无限期等待下去,设置是超过timeout秒钟,就表示请求失败了——可以再次发出请求或者超过重复请求次数直接返回终端断联信息。

       ESP32 IDF的socket应用案例中提供了recv()函数,不过这个函数是阻塞式调用,也就是不收到数据,函数就不返回。本人通过查找资料,了解到可以将其配置为非阻塞式即超时退出的模式。不过很奇怪的是,配置了超时时间,recv()函数却会瞬间返回,也就是压根不等待,直接返回接收错误——errno是11。网上查找都说是Wi-Fi信号弱会引起这个错误,反正本人是没有找到解决方法,于是找了一个“花招”。

       这个花招就是:send()函数在发送请求终端命令后,延时timeout秒,再做recv()——当然要设置为非阻塞式。这样如果在timeout时间段内有接收到信息,recv会将其存入buff,如果没有接收到recv也会立即返回。

       另外,设置非阻塞式需要开启几个宏,LwIP是在“..\esp-idf-v4.4\components\lwip\lwip\src\include\lwip\opt.h”中,不过IDF中移植的LwIP,“..\esp-idf-v4.4\components\lwip\port\esp32\include\lwipopts.h”中也有这个宏定义,本人是两个文件都改了。

 

 

图14-1 接收超时相关宏修改

 

       当然,上述的宏修改,纯粹是接收超时功能没有成功时的胡乱改动,最终也没有出现想要的效果,只能是插入延时了。

 

u_long non_blocking = 1; //设置非阻塞式接收的模式变量,0则是阻塞式
void aita_PollingFunc(void) {
    ESP_LOGI(TAG, "Polling...");

    char rx_buffer[64];
    char host_ip[] = HOST_IP_ADDR;
    int addr_family = 0;
    int ip_protocol = 0;

    struct sockaddr_in dest_addr;
    dest_addr.sin_addr.s_addr = inet_addr(host_ip);
    dest_addr.sin_family = AF_INET;
    dest_addr.sin_port = htons(PORT);
    addr_family = AF_INET;
    ip_protocol = IPPROTO_IP;

    int sock =  socket(addr_family, SOCK_STREAM, ip_protocol);
    if(sock < 0) {
        ESP_LOGE(TAG, "Unable to create socket: errno %d", errno);
        return;
    }
    ESP_LOGI(TAG, "Socket created, connecting to %s:%d", host_ip, PORT);

    int err = connect(sock, (struct sockaddr *)&dest_addr, sizeof(struct sockaddr_in6));
    if(err != 0) {
        ESP_LOGE(TAG, "Socket unable to connect: errno %d", errno);
        return;
    }
    ESP_LOGI(TAG, "Successfully connected");

    // set recv timeout
    int opt = timeout * 1000;//整型的延时值
    ioctlsocket(sock, FIONBIO, &non_blocking);//修改套接字为非阻塞模式
    setsockopt(sock, SOL_SOCKET, SO_RCVTIMEO, &opt, sizeof(opt));//配置超时值

/*
这里开始是轮询终端的核心代码,eid_num是从TF卡中读出的终端数,aita_BuildCommandPack()是自定义的构建“网关ID+终端ID+命令”数据包的函数,
repeat也是TF卡读出的如果请求一个终端失败后的重复次数,
*/ 
    int i = 0, cyc = 1;
    for(i=0; i<eid_num; i++) {
        aita_BuildCommandPack(i);
        cyc = repeat;
        while(cyc--) {
            int err = send(sock, comm_pack, COMMPACK_LEN-1, 0);
            if (err < 0) {
                ESP_LOGE(TAG, "Error occurred during sending: errno %d", errno);
                break;
            }

            vTaskDelay(timeout*1000 / portTICK_PERIOD_MS);

            int len = recv(sock, rx_buffer, sizeof(rx_buffer) - 1, 0);
            // Error occurred during receiving
            if(len < 0) {
                ESP_LOGE(TAG, "recv failed: errno %d", errno);
                // break;
            } else {
                rx_buffer[len] = 0; // recv buff
                ESP_LOGI(TAG, "Received %d bytes from %s:", len, host_ip);
                ESP_LOGI(TAG, "%s", rx_buffer);
// 上传到OneNET的数据,以“网关ID-终端ID-终端坐标”作为数据流名,
// 而数据点值则是终端通过网关回传到ESP32的监测值,由多组值通过中横线连接的字串,
// 这样保存是为了应用端可以解析到终端ID、坐标,并一次获取多个监测值。
                char pack[] = "{\"%s-%s-%s\":\"%s\"}";
                char msgJson[100];
// aita_PubPackType3()是自定义的以格式3上传OneNET的函数
                sprintf(msgJson, pack, gid, endians[i].id, endians[i].cor, rx_buffer);
                printf("%s\n", msgJson);
                aita_PubPackType3(msgJson);
                memset(rx_buffer, 0, 64);
                break;
            }
        }

        if(cyc <= 0) {
            printf("%s disconnected!\n", endians[i].id);
        }
    }
// 全部终端的一次轮询结束后,断开与网关的TCP连接。
    if(sock != -1) {
        ESP_LOGE(TAG, "Shutting down socket and restarting...");
        shutdown(sock, 0);
        close(sock);
    }
}

 

        上述轮询主业务的函数,被一个系统任务调用。系统开启后先做SNTP授时,然后开启秒级定时器,每秒发送一个信号量,用于唤醒任务执行。任务每正分钟向OneNET上传一次心跳包,然后每到period分钟(系统启动后是10s中进行一次终端轮询,之后才是period周期)进行一次轮询。

 

void aita_PollingTask(void *pv) {
    (void) pv;
    while(1) {
        if(xSemaphoreTake(timersem, portMAX_DELAY) == true) {
            --periodsec;
        }
        if(periodsec <= 0) {
            periodsec = 60 * period;
            aita_PollingFunc();
        }
    }
}

// 定时器回调函数
static void periodic_timer_callback(void* arg) {
    xSemaphoreGiveFromISR(timersem, NULL);
    time_t now;
    struct tm localdate;
    time(&now);
    localtime_r(&now, &localdate);
    if(localdate.tm_sec == 0) {
        aita_SendHeartBeatPack(&localdate);
    }   
}

 

       本项目实际是本人之前参与的一个研发项目的ESP32 IDF版本——初始版本使用树莓派Java开发上位机,纯粹是为了赶进度选择的便于快速实现的Java。实际一个ESP32就可以解决需求了,距离比赛结束还有一段时间,后续在下将尝试构建UI以显示系统工作状态(至少把Kaluga的LCD用起来)。如果可能,还想看看能不能利用摄像头做一下识别功能——因为系统会装到工地监控室,也可以顺便构建一个人脸识别的安防或考勤功能。

最新回复

期待楼主的系统会装到工地监控室下面能测试成功   详情 回复 发表于 2022-10-3 15:43
点赞 关注
 
 

回复
举报

6802

帖子

0

TA的资源

五彩晶圆(高级)

沙发
 

期待楼主的系统会装到工地监控室下面能测试成功

点评

实际上这个项目的初试原型已经安装到工地了,因为天津这一年稀稀拉拉的,我们现在都不能去工地把原型收回。所以测试都是用PC的网络助手模拟的。  详情 回复 发表于 2022-10-3 19:30
 
 
 

回复

155

帖子

1

TA的资源

一粒金砂(高级)

板凳
 
Jacktang 发表于 2022-10-3 15:43 期待楼主的系统会装到工地监控室下面能测试成功

实际上这个项目的初试原型已经安装到工地了,因为天津这一年稀稀拉拉的,我们现在都不能去工地把原型收回。所以测试都是用PC的网络助手模拟的。

 
 
 

回复
您需要登录后才可以回帖 登录 | 注册

随便看看
查找数据手册?

EEWorld Datasheet 技术支持

相关文章 更多>>
关闭
站长推荐上一条 1/9 下一条

 
EEWorld订阅号

 
EEWorld服务号

 
汽车开发圈

About Us 关于我们 客户服务 联系方式 器件索引 网站地图 最新更新 手机版

站点相关: 国产芯 安防电子 汽车电子 手机便携 工业控制 家用电子 医疗电子 测试测量 网络通信 物联网

北京市海淀区中关村大街18号B座15层1530室 电话:(010)82350740 邮编:100190

电子工程世界版权所有 京B2-20211791 京ICP备10001474号-1 电信业务审批[2006]字第258号函 京公网安备 11010802033920号 Copyright © 2005-2025 EEWORLD.com.cn, Inc. All rights reserved
快速回复 返回顶部 返回列表