移遠4G模組撥號+socket獲取天氣數據


上一篇分享了4G開發板與我們PC進行通信(需要進行內網穿透),這一篇筆記我們直接使用4G開發板訪問天氣服務器獲取天氣數據。

我們要使用移遠4G模塊進行網絡通信,要經歷 3 個主要過程:網絡注冊網絡激活socket 創建

網絡注冊是自動完成的,無需用戶代碼干預(網絡注冊前,請確認 SIM 卡是否正常識別且 SIM 卡無欠費等業務異常)。只有在網絡注冊成功以后,才可進行網絡激活,即本文所述的撥號(下文皆稱為撥號)。只有撥號成功后,才可進行 socket 網絡通信。

撥號+socket 通信基本流程

確保撥號成功的必要條件:SIM 卡沒欠費; 硬件良好,天線匹配;撥號前,調用 ql_network_register_wait 等待網絡注冊成功;撥號時,向 ql_start_data_call 傳遞正確的參數。

Socket 通信流程

EC100 模塊 Socket 通信基本流程如上圖:
1、在進行 socket 通信之前,必須確保撥號已經成功,可以通過 ql_get_data_call_info()查詢需要進行 socket 通信的網絡通道是否撥號成功。
2、撥號成功后進行 socket 通信時,必須對需要通信的網絡通道進行 bind 操作。不管是 UDP 還是 TCP,都必須執行 Bind 操作之后才可以正常進行 socket 通信。

sdk中給我們提供了一個tcp_client的demo,這個demo的設計思路完全按照上面兩張圖流程圖來設計。我們在這個demo進行修改,獲取天氣數據。

我們修改后的example_tcp_weather_client.c代碼如下:

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include "ql_type.h"
#include "ql_rtos.h"
#include "ql_application.h"
#include "ql_data_call.h"
#include "sockets.h"
#include "netdb.h"
#include "cJSON.h"

#define  DEBUG   0		

/* 心知天氣(www.seniverse.com)IP及端口 */
#define  WEATHER_IP_ADDR   "116.62.81.138"
#define  WEATHER_PORT	   80

#define TCP_CONNECT_TIMEOUT_S 10
#define TCP_RECV_TIMEOUT_S 10
#define TCP_CLOSE_LINGER_TIME_S 10
#define TCP_CLIENT_SEND_STR "tcp client send string"
#define PROFILE_IDX 1
#define PROFILE_IDX 1

/* 秘鑰,注意!!如果要用這一份代碼,這個一定要改為自己的,因為這個我已經故意改錯了,防止有人與我公用一個KEY */
#define  KEY    "2owqvhhd2dd9o9f9"		// 這是在心知天氣注冊后,每個用戶自己的一個key

/* GET請求包 */
#define  GET_REQUEST_PACKAGE     \
         "GET https://api.seniverse.com/v3/weather/%s.json?key=%s&location=%s&language=zh-Hans&unit=c\r\n\r\n"
	
/* JSON數據包 */	
#define  NOW_JSON     "now"
//....還用更多其他的天氣數據包可查閱心知天氣

/* 天氣數據結構體 */
typedef struct
{
	/* 實況天氣數據 */
	char id[32];				//id
	char name[32];				//地名
	char country[32];			//國家
	char path[32];				//完整地名路徑
	char timezone[32];			//時區
	char timezone_offset[32];   //時差
	char text[32];				//天氣預報文字
	char code[32];				//天氣預報代碼
	char temperature[32];   	//氣溫
	char last_update[32];		//最后一次更新的時間
}Weather;

// 全局變量
static struct in_addr ip4_addr = {0};

// 函數聲明
static void GetWeather(char *weather_json, char *location, Weather *result);
static int cJSON_NowWeatherParse(char *JSON, Weather *result);
static int cJSON_DailyWeatherParse(char *JSON, Weather *result);
static void DisplayWeather(Weather *weather_data);
static void ql_nw_status_callback(int profile_idx, int nw_status);
static void datacall_satrt(void);

/*******************************************************************************************************
** 函數: sockets_weather_test
**------------------------------------------------------------------------------------------------------
** 參數: void
** 返回: void
********************************************************************************************************/
static void sockets_weather_test(void * argv)
{
	struct ql_data_call_info info = {0};
	char ip4_addr_str[16] = {0};
	Weather weather_data = {0};
	char *location = "shenzhen";

	printf("========== sockets tcp test will start ...\r\n");

	/* 撥號開始 */
	datacall_satrt();

	/* 獲取撥號信息 */
	ql_get_data_call_info(1, 0, &info);

	printf("info.profile_idx: %d\r\n", info.profile_idx);
	printf("info.ip_version: %d\r\n", info.ip_version);
	printf("info.v4.state: %d\r\n", info.v4.state);
	printf("info.v4.reconnect: %d\r\n", info.v4.reconnect);

	inet_ntop(AF_INET, &info.v4.addr.ip, ip4_addr_str, sizeof(ip4_addr_str));
	printf("info.v4.addr.ip: %s\r\n", ip4_addr_str);

	inet_ntop(AF_INET, &info.v4.addr.pri_dns, ip4_addr_str, sizeof(ip4_addr_str));
	printf("info.v4.addr.pri_dns: %s\r\n", ip4_addr_str);

	inet_ntop(AF_INET, &info.v4.addr.sec_dns, ip4_addr_str, sizeof(ip4_addr_str));
	printf("info.v4.addr.sec_dns: %s\r\n", ip4_addr_str);

	ip4_addr = info.v4.addr.ip;

	if(info.v4.state)
	{
		memset(&weather_data, 0, sizeof(weather_data));  // weather_data清零 
		GetWeather(NOW_JSON, location, &weather_data);   // GET 並解析實況天氣數據
		DisplayWeather(&weather_data);					 // 顯示天氣結果
	}

	printf("========== sockets tcp test finished\r\n");
	
	return 0;
}
	
static void ql_nw_status_callback(int profile_idx, int nw_status)
{
	printf("profile(%d) status: %d\r\n", profile_idx, nw_status);
}

static void datacall_satrt(void)
{
	printf("wait for network register done\r\n");

	/* 等待網絡注冊結果 */
	if(ql_network_register_wait(120) != 0)
	{
		printf("*** network register fail ***\r\n");
	}
	else
	{
		printf("doing network activing ...\r\n");
		
		ql_wan_start(ql_nw_status_callback);			/* 撥號初始化:注冊撥號回調函數 */
		ql_set_auto_connect(1, TRUE);					/* 設置撥號掉線是否自動重連 */
		ql_start_data_call(1, 0, NULL, NULL, NULL, 0);  /* 開始撥號 */
	}
}

static void GetWeather(char *weather_json, char *location, Weather *result)
{
	int				sock_nbio	= 1;
	int				ret			= 0;
	int				sock_fd     = -1;
	int				sock_error  = 0;
	socklen_t		optlen = 0;
	fd_set 			read_fds, write_fds;
	struct timeval	t;
	struct addrinfo		* res, hints;
	struct sockaddr_in	* ip4_svr_addr;
	struct sockaddr_in	ip4_local_addr = {0};
	u8 dns_success = 0;
	u8 recv_buf[128] = {0};
	u8 GetRequestBuf[256] = {0};
	u8 WeatherRecvBuf[2*1024] = {0};

	/*  */
	memset(&hints, 0, sizeof(struct addrinfo));
	hints.ai_family = AF_INET;
	hints.ai_socktype = SOCK_STREAM;
	if(getaddrinfo_with_pcid(WEATHER_IP_ADDR, NULL, &hints, &res, PROFILE_IDX) != 0)
	{
		printf("*** DNS fail ***\r\n");
		goto exit;
	}

	dns_success = 1;
	
	ret = socket(AF_INET, SOCK_STREAM, 0);
	if(ret < 0)
	{
		printf("*** socket create fail ***\r\n");
		goto exit;
	}

	sock_fd = ret;

	ioctl(sock_fd, FIONBIO, &sock_nbio);

	ip4_local_addr.sin_family = AF_INET;
	ip4_local_addr.sin_port = htons(ql_soc_generate_port());
	ip4_local_addr.sin_addr = ip4_addr;
	
	ret = bind(sock_fd, (struct sockaddr *)&ip4_local_addr, sizeof(ip4_local_addr));
	if(ret < 0)
	{
		printf("*** bind fail ***\r\n");
		goto exit;
	}
	
	ip4_svr_addr = (struct sockaddr_in *)res->ai_addr;
	ip4_svr_addr->sin_port = htons(WEATHER_PORT);

	ret = connect(sock_fd, (struct sockaddr *)ip4_svr_addr, sizeof(struct sockaddr));

	printf("connect ret: %d, errno: %u\r\n", ret, errno);

	if(ret == -1 && errno != EINPROGRESS)
	{
		printf("*** connect fail ***\r\n");
		goto exit;
	}

	t.tv_sec = TCP_CONNECT_TIMEOUT_S;
	t.tv_usec = 0;

	FD_ZERO(&read_fds);
	FD_ZERO(&write_fds);

	FD_SET(sock_fd, &read_fds);
	FD_SET(sock_fd, &write_fds);

	ret = select(sock_fd + 1, &read_fds, &write_fds, NULL, &t);

	printf("select ret: %d\r\n", ret);

	if(ret <= 0)
	{
		printf("*** select timeout or error ***\r\n");
		goto exit;
	}

	if(!FD_ISSET(sock_fd, &read_fds) && !FD_ISSET(sock_fd, &write_fds))
	{
		printf("*** connect fail ***\r\n");
		goto exit;
	}
	else if(FD_ISSET(sock_fd, &read_fds) && FD_ISSET(sock_fd, &write_fds))
	{
		optlen = sizeof(sock_error);
		ret = getsockopt(sock_fd, SOL_SOCKET, SO_ERROR, &sock_error, &optlen);
		if(ret == 0 && sock_error == 0)
		{
			printf("connect success\r\n");
		}
		else
		{
			printf("*** connect fail, sock_err = %d, errno = %u ***\r\n", sock_error, errno);
			goto exit;
		}
	}
	else if(!FD_ISSET(sock_fd, &read_fds) && FD_ISSET(sock_fd, &write_fds))
	{
		printf("connect success\r\n");
	}
	else if(FD_ISSET(sock_fd, &read_fds) && !FD_ISSET(sock_fd, &write_fds))
	{
		printf("*** connect fail ***\r\n");
		goto exit;
	}
	else
	{
		printf("*** connect fail ***\r\n");
		goto exit;
	}

	/* 組合GET請求包 */
	sprintf(GetRequestBuf, GET_REQUEST_PACKAGE, weather_json, KEY, location);

	/* 發送數據到服務端 */
	ret = send(sock_fd, (const void*)GetRequestBuf, strlen(GetRequestBuf), 0);
	if(ret < 0)
	{
		printf("*** send fail ***\r\n");
		goto exit;
	}

_recv_:
	t.tv_sec = TCP_RECV_TIMEOUT_S;
	t.tv_usec = 0;

	FD_ZERO(&read_fds);
	FD_SET(sock_fd, &read_fds);

	ret = select(sock_fd + 1, &read_fds, NULL, NULL, &t);

	printf("select ret: %d\r\n", ret);

	if(ret <= 0)
	{
		printf("*** select timeout or error ***\r\n");
		goto exit;
	}

	if(FD_ISSET(sock_fd, &read_fds))
	{
		ret = recv(sock_fd, WeatherRecvBuf, sizeof(WeatherRecvBuf), 0);
		if(ret > 0)
		{
			printf("recv data: [%d]%s\r\n", ret, WeatherRecvBuf);
			/* 解析天氣數據並保存到結構體變量weather_data中 */
			if (0 == strcmp(weather_json, NOW_JSON))		// 天氣實況
			{
				cJSON_NowWeatherParse(WeatherRecvBuf, result);	
			}
		}
		else if(ret == 0)
		{
			printf("*** peer closed ***\r\n");
			goto exit;
		}
		else
		{
			if(!(errno == EINTR || errno == EWOULDBLOCK || errno == EAGAIN))
			{
				printf("*** error occurs ***\r\n");
				goto exit;
			}
			else
			{
				printf("wait for a while\r\n");
				ql_rtos_task_sleep_ms(20);
				goto _recv_;
			}
		}

	}

exit:
	if(dns_success) freeaddrinfo(res);

	if(sock_fd >= 0)
	{
		struct linger linger = {0};

		linger.l_onoff = 1;
		linger.l_linger = TCP_CLOSE_LINGER_TIME_S;

		setsockopt(sock_fd, SOL_SOCKET, SO_LINGER, &linger, sizeof(linger));
        setsockopt(sock_fd, IPPROTO_TCP, TCP_CLOSE_TIMEROUT, &linger.l_linger, sizeof(linger.l_linger));

		/* 清空緩沖區 */
		memset(GetRequestBuf, 0, 256);   
		memset(WeatherRecvBuf, 0, 2*1024);    
		close(sock_fd);
	}
}

/*******************************************************************************************************
** 函數: cJSON_NowWeatherParse,解析天氣實況數據
**------------------------------------------------------------------------------------------------------
** 參數: JSON:天氣數據包   result:數據解析的結果
** 返回: void
********************************************************************************************************/
static int cJSON_NowWeatherParse(char *JSON, Weather *result)
{
	cJSON *json,*arrayItem,*object,*subobject,*item;
	
	json = cJSON_Parse(JSON); //解析JSON數據包
	if(json == NULL)		  //檢測JSON數據包是否存在語法上的錯誤,返回NULL表示數據包無效
	{
		printf("Error before: [%s]\n",cJSON_GetErrorPtr()); //打印數據包語法錯誤的位置
		return 1;
	}
	else
	{
		if((arrayItem = cJSON_GetObjectItem(json,"results")) != NULL); //匹配字符串"results",獲取數組內容
		{
			int size = cJSON_GetArraySize(arrayItem);     //獲取數組中對象個數
#if DEBUG
			printf("cJSON_GetArraySize: size=%d\n",size); 
#endif
			if((object = cJSON_GetArrayItem(arrayItem,0)) != NULL)//獲取父對象內容
			{
				/* 匹配子對象1:城市地區相關 */
				if((subobject = cJSON_GetObjectItem(object,"location")) != NULL)
				{
					// 匹配id
					if((item = cJSON_GetObjectItem(subobject,"id")) != NULL)   
					{
						memcpy(result->id, item->valuestring,strlen(item->valuestring)); 		// 保存數據供外部調用
					}
					// 匹配城市名
					if((item = cJSON_GetObjectItem(subobject,"name")) != NULL) 
					{
						memcpy(result->name, item->valuestring,strlen(item->valuestring)); 		// 保存數據供外部調用
					}
					// 匹配城市所在的國家
					if((item = cJSON_GetObjectItem(subobject,"country")) != NULL)
					{
						memcpy(result->country, item->valuestring,strlen(item->valuestring)); 	// 保存數據供外部調用
					}
					// 匹配完整地名路徑
					if((item = cJSON_GetObjectItem(subobject,"path")) != NULL)  
					{
						memcpy(result->path, item->valuestring,strlen(item->valuestring)); 		// 保存數據供外部調用	
					}
					// 匹配時區
					if((item = cJSON_GetObjectItem(subobject,"timezone")) != NULL)
					{
						memcpy(result->timezone, item->valuestring,strlen(item->valuestring)); 	// 保存數據供外部調用	
					}
					// 匹配時差
					if((item = cJSON_GetObjectItem(subobject,"timezone_offset")) != NULL)
					{
						memcpy(result->timezone_offset, item->valuestring,strlen(item->valuestring)); 	// 保存數據供外部調用
					}
				}
				/* 匹配子對象2:今天的天氣情況 */
				if((subobject = cJSON_GetObjectItem(object,"now")) != NULL)
				{
					// 匹配天氣現象文字
					if((item = cJSON_GetObjectItem(subobject,"text")) != NULL)
					{
						memcpy(result->text, item->valuestring,strlen(item->valuestring));  // 保存數據供外部調用
					}
					// 匹配天氣現象代碼
					if((item = cJSON_GetObjectItem(subobject,"code")) != NULL)
					{
						memcpy(result->code, item->valuestring,strlen(item->valuestring));  // 保存數據供外部調用
					}
					// 匹配氣溫
					if((item = cJSON_GetObjectItem(subobject,"temperature")) != NULL) 
					{
						memcpy(result->temperature, item->valuestring,strlen(item->valuestring));   // 保存數據供外部調用
					}	
				}
				/* 匹配子對象3:數據更新時間(該城市的本地時間) */
				if((subobject = cJSON_GetObjectItem(object,"last_update")) != NULL)
				{
					memcpy(result->last_update, subobject->valuestring,strlen(subobject->valuestring));   // 保存數據供外部調用
				}
			} 
		}
	}
	
	cJSON_Delete(json); //釋放cJSON_Parse()分配出來的內存空間
	
	return 0;
}

/*******************************************************************************************************
** 函數: DisplayWeather,顯示天氣數據
**------------------------------------------------------------------------------------------------------
** 參數: weather_data:天氣數據
** 返回: void
********************************************************************************************************/
static void DisplayWeather(Weather *weather_data)
{
	printf("============%s today weather===========\n", weather_data->name);
	printf("weather_data->text: %s\n", weather_data->text);		
	printf("weather_data->temperature: %s\n", weather_data->temperature);	
	printf("weather_data->timezone: %s\n", weather_data->timezone);	
	printf("weather_data->timezone_offset: %s\n", weather_data->timezone_offset);
	printf("weather_data->last_update: %s\n", weather_data->last_update);
}

application_init(sockets_weather_test, "sockets_weather_test", 8, 4);

關於天氣獲取、解析代碼之前已經有詳細分享過,這里不再解釋。相關文章:

socket應用】基於C語言的天氣客戶端的實現

測試結果:


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM