【建筑施工监测与安防系统】十四、项目核心功能实现
[复制链接]
本人参赛项目的核心功能是施工现场压力等监测,并提供阈值报警。其中利用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用起来)。如果可能,还想看看能不能利用摄像头做一下识别功能——因为系统会装到工地监控室,也可以顺便构建一个人脸识别的安防或考勤功能。
|