# 實驗: 取得 RSSI 並回傳至伺服器

## 整體 RSSI 蒐集架構

本實驗中的程式以及說明由蔡東倫先生所準備，作者只做些微修改與補充。

![framework](https://2123799480-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-LNYz37Gb_OI9VBKgqqt%2F-LOgk8_VJOvN3SHzKqme%2F-LOgkBeCAWSG6Zs4hqwE%2Fframework.PNG?alt=media\&token=b2935e35-3b2d-41e3-942f-a1f2f1343c39)

本實驗的目標為取得監聽的 RSSI 資訊並回傳至後端的伺服器。一開始我們有一個 WiFi 裝置透過OpenWRT WiFi AP 進行資料傳輸，此 OpenWRT WiFi AP 以建立兩張網卡，分別處於 master 模式以及 monitor 模式，因此可以一邊幫該使用者轉傳封包，一邊記錄下 RSSI 資訊，並送到後端的伺服器。

伺服器和 WiFi AP 的資料傳輸使用 socket 模式，換言之，伺服器為 socket server，而 WiFi AP 為 socket client，兩者透過 polling 的方式將資料回傳。

{% hint style="info" %}
甚麼是 socket?

簡單來說，socket 是一種透過網路傳輸資料的方式，透過指定的 IP 和 port，就可以進行通訊。在此架構下，socket client 會傳送連線需求至 socket server 的 IP 與指定的 port，socket server 會持續的監聽該 port 的連線需求，若有 client 的連線需求，則建立連線，並開始資料雙向交換。事實上，socket 有分成許多不同的類別，若有興趣，可以閱讀:

* <http://wmnlab.ee.ntu.edu.tw/nmlab/exp1_socket.html>
* <http://beej-zhtw.netdpi.net/02-what-is-socket>
* <http://beej-zhtw.netdpi.net/02-what-is-socket/2-1-two-internet-sockets>
  {% endhint %}

## WiFi AP 端程式

在[之前的實驗](https://openwrt-nctu.gitbook.io/project/~/edit/drafts/-LOgNyAd2x15c_pAzkNy/er-ka-qu-de-rssi)中，我們在 OpenWRT 上建立虛擬無線網卡，模式分別為 AP 模式與 Monitor 模式。接著用 tcpdump 監聽 Monitor 模式的無線網卡，並用 grep 指令保留同時包含 RSSI 值與source MAC 的訊息。上述的動作皆可以由 C 語言建立 pipe 時觸發，接著在 C 語言實作 parser將所要資訊擷取出來。

{% hint style="info" %}
pipe 函式簡介

如果我們除了想在 C 程式執行指令之外，也想要透過標準輸入與輸出與指令互動的話，pipe 函式比起 system 函式更加適於這種情境。在 Linux 系統中，為了使用將多個程式組合在一起，會在程式之間建立單向串列管道 (pipe)，使得某一程式的輸出做為另一程式的輸入。在 Linux console 中，使用符號 “|” 即可建立 pipe，對於要處理大量序列的程式 (如:grep) 這是一種常見的作法。

在 C 語言中，利用`popen()`可以做到相同事情。在`popen()`中第一個輸入為要執行的程式或指令，第二個輸入 pipe 為讀取 (“r”) 或寫入 (“w”)，其回傳值為檔案指標。只要在方向正確的情形下，可以用舊有的檔案處理函式 (fprintf、fscanf) 去讀寫這個 pipe。若要關閉 pipe 則用 pclose() 函式。使用此函式請記得引入 "unistd.h"。在許多情況下，pipe 會比 system 方便。以下是程式範例:

`FILE *fp;` \
`char buffer[1000];`\
\
`fp = popen(“ifconfig”, "r");` \
`fgets(buffer, sizeof(buffer), fp);` \
`printf(“%s”, buffer);` \
`pclose(fp);`
{% endhint %}

以下附的是參考程式碼，由於使用了 thread 因此用 gcc 編譯時需加上 -lpthread，才能編譯成功。在此程式中使用了兩個 thread:&#x20;

* sensing\_func(): 負責讀取 pipe、取出對應的 RSSI、計算 RSSI 平均值。
* report\_func(): 當 socket server polling 時回報所得到的 RSSI 資訊。

為了避免兩者同時存取共用記憶體，在 critical section 使用了 mutex ，避免對 RSSI 數值同時存取，造成錯誤。以下是所撰寫的程式 (RSSI\_client.c):

```
#include "stdio.h"
#include "stdlib.h"
#include "string.h"
#include "unistd.h"
#include "sys/types.h"
#include "sys/socket.h"
#include "netinet/in.h"
#include "netdb.h"
#include "signal.h"

#define MAX_USER_NUM 200
#define ReconnectionDelayTime 2
#define WLAN "wlan0"

char* server_ip;
int server_port;

void *sensing_func(void *parm);
void *report_func(void *parm);
char* get_interface_MAC_string();
char* search_pattern_following(char* str1, char* str2);
static pthread_t sensing_thread;
static pthread_t report_thread;
static pthread_mutex_t sensing_report_mutex;

static int sensing_socket;
static int construct_link(char ip[], int port);
void disconnection(int sig);

struct sensing_result
{
	char src_mac[100];
	float rssi_mean;
	float rssi_variance;
	int sample_num;
};
static struct sensing_result sensing_result[100];
static int sensing_result_num=0;

int main(int argc, char *argv[])
{
	if(argc<3)
	{
		printf("argument: [Server IP] [Server Port]\n");
		return;
	}
	else
	{
		server_ip = argv[1];
		server_port = atoi(argv[2]);
	}
	(void)signal(SIGINT, disconnection);

	pthread_mutex_init(&sensing_report_mutex, NULL);
	pthread_mutex_lock(&sensing_report_mutex);

	pthread_create(&sensing_thread, NULL, sensing_func, NULL);
	pthread_create(&report_thread, NULL, report_func, NULL);

	pthread_mutex_unlock(&sensing_report_mutex);

	while(1);

	return 0;
}

#define TCPDUMP_CMD "tcpdump -ne -y ieee802_11_radio -s 65535 -i wlan1-2 |grep \"signal\" | grep \"SA\\|TA\" |grep \"dBm\""

void *sensing_func(void *parm)
{
	FILE *fp;
	char sensing_dump[1000];
	char src_mac[100];
	float rssi;
	float old_rssi_mean;
	float new_rssi_mean;
	float old_rssi_variance;
	float new_rssi_variance;
	int i;

	if( (fp = popen(TCPDUMP_CMD, "r"))== NULL)
	{
		printf("popen() error!\n");
		exit(1);
	}

	while(fgets(sensing_dump, sizeof(sensing_dump), fp))
	{
		sscanf(strstr(sensing_dump, "dBm ")-4, "%fdB", &rssi);

		memset(src_mac, 0, sizeof(src_mac));
		if(strstr(sensing_dump, "SA:")!=NULL)
		{
			sscanf(strstr(sensing_dump, "SA:")+3, "%s", src_mac);
		}
		else if(strstr(sensing_dump, "TA:")!=NULL)
		{
			sscanf(strstr(sensing_dump, "TA:")+3, "%s", src_mac);
		}
		else
		{
			printf("ERROR: SA and TA are not found\n");
		}

		for(i=0;i<strlen(src_mac);i++) if(islower(src_mac[i]))
		{
			src_mac[i]=toupper(src_mac[i]);
		}

		pthread_mutex_lock(&sensing_report_mutex);

		//search old item
		for(i=0;i<sensing_result_num;i++)
		{
			if(strcmp(sensing_result[i].src_mac, src_mac)==0)
			{
				//policy 1: keep the latest RSSI sample for all devices
				new_rssi_mean = rssi;
				new_rssi_variance = 0;
				sensing_result[i].rssi_mean=new_rssi_mean;
				sensing_result[i].rssi_variance=new_rssi_variance;
				sensing_result[i].sample_num=1;
				
				break;
			}
		}

		//create new item if necessary
		if(i==sensing_result_num)
		{
			memcpy(sensing_result[i].src_mac, src_mac, strlen(src_mac));
			sensing_result[i].rssi_mean=rssi;
			sensing_result[i].rssi_variance=0.0;
			sensing_result[i].sample_num=1;
			sensing_result_num++;
		}

		pthread_mutex_unlock(&sensing_report_mutex);

	}

	pclose(fp);

	return;
}

void *report_func(void *parm)
{
	int i;
	char sensing_report[100];
	char rcv_buf[100];
	
start:
	sensing_socket = construct_link(server_ip, server_port);

	while(1)
	{
		//format: [round] [observer MAC] [target MAC] [RSSI]

		memset(rcv_buf, 0, sizeof(rcv_buf));
		while(read(sensing_socket, rcv_buf, sizeof(rcv_buf))==0); //wait for polling

		printf("%s is received\n", rcv_buf);
		pthread_mutex_lock(&sensing_report_mutex);

		for(i=0;i<sensing_result_num;i++)
		{
			printf("sensing_result[%d].src_mac=%s\n", i, sensing_result[i].src_mac);
			if(1) 
			{
				memset(sensing_report, 0, sizeof(sensing_report));
				sprintf(sensing_report, "%s %s %s %f\n",
					rcv_buf,
					get_interface_MAC_string(),
					sensing_result[i].src_mac,
					sensing_result[i].rssi_mean);
					
				printf("%s is sent\n", sensing_report);

				if(write(sensing_socket, sensing_report, strlen(sensing_report))<=0)
				{
					printf("%s:socket write error\n", __FUNCTION__);
					goto start;
				}
			}
		}

		memset(sensing_result, 0, sizeof(sensing_result));
		sensing_result_num=0;

		pthread_mutex_unlock(&sensing_report_mutex);

		printf("\n");
	}
}

void disconnection(int sig)
{
	close(sensing_socket);
	(void)signal(SIGINT, SIG_DFL);
}

static int construct_link(char ip[], int port)
{
	int client_fd = -1;
	struct hostent *host;
	struct sockaddr_in client_sin;

	while(client_fd <= 0)
	{
		if((host = gethostbyname(ip)) == NULL)
		{
			fprintf(stderr, "Can't get ip !!!\n");
			return -1;
		}
		client_fd = socket(AF_INET, SOCK_STREAM, 0);
		bzero(&client_sin, sizeof(client_sin));
		client_sin.sin_family = AF_INET;
		client_sin.sin_addr = *((struct in_addr *)host->h_addr);
		client_sin.sin_port = htons(port);
		if(connect(client_fd, (struct sockaddr *)&client_sin, sizeof(client_sin)) == -1)
		{
			fprintf(stderr, "Can't connect to server \n");
			fprintf(stdout, "Reconstruct link after 2 seconds \n");
			client_fd = -1;
			sleep(ReconnectionDelayTime);
		}
	}
	return client_fd;
}

char* get_interface_MAC_string()
{
	FILE *fp;
	char cmd[100];
	static char ret[50];

	char line_buf[100];
	char *cursor;
	int mac[6];

	memset(ret, 0, sizeof(ret));
	sprintf(ret, "-1");

	memset(cmd, 0, sizeof(cmd));
	sprintf(cmd, "ifconfig %s", WLAN);

	if((fp=popen(cmd, "r"))!=NULL)
	{
		while(!feof(fp))
		{
			memset(line_buf, 0, sizeof(line_buf));
			fgets(line_buf, sizeof(line_buf), fp);

			if( (cursor=search_pattern_following(line_buf, "HWaddr ")) != NULL )
			{
				sscanf(cursor, "%02X:%02X:%02X:%02X:%02X:%02X",
					&mac[0], &mac[1], &mac[2],
					&mac[3], &mac[4], &mac[5] );

				memset(ret, 0, sizeof(ret));
				sprintf(ret, "%02X:%02X:%02X:%02X:%02X:%02X",
					mac[0], mac[1], mac[2],
					mac[3], mac[4], mac[5] );
					
				break;
			}

		}
		pclose(fp);
	}
	else
	{
		printf("%s:Fail to open the file\n", __FUNCTION__);
	}

	return ret;
}

char* search_pattern_following(char* str1, char* str2)
{
	char* ret;
	if( (ret=strstr(str1, str2)) != NULL ) ret+=strlen(str2);

	return ret;
}
```

編譯時指令為:

```
 mips-openwrt-linux-gcc RSSI_client.c --static -lpthread -o RSSI_client
```

會出現許多警告，如下所示，不過不影響使用:

```
ofwrt@ofwrt-18:~/ofwrt_code$ mips-openwrt-linux-gcc RSSI_client.c --static -lpthread -o RSSI_client
RSSI_client_smartAnt.c: In function 'main':
RSSI_client_smartAnt.c:48:3: warning: 'return' with no value, in function returning non-void
   return;
   ^~~~~~
RSSI_client_smartAnt.c:43:5: note: declared here
 int main(int argc, char *argv[])
     ^~~~
RSSI_client_smartAnt.c:57:2: warning: implicit declaration of function 'pthread_mutex_init'; did you mean 'pthread_kill'? [-Wimplicit-function-declaration]
  pthread_mutex_init(&sensing_report_mutex, NULL);
  ^~~~~~~~~~~~~~~~~~
  pthread_kill
RSSI_client_smartAnt.c:58:2: warning: implicit declaration of function 'pthread_mutex_lock'; did you mean 'pthread_kill'? [-Wimplicit-function-declaration]
  pthread_mutex_lock(&sensing_report_mutex);
  ^~~~~~~~~~~~~~~~~~
  pthread_kill
RSSI_client_smartAnt.c:60:2: warning: implicit declaration of function 'pthread_create'; did you mean 'pthread_kill'? [-Wimplicit-function-declaration]
  pthread_create(&sensing_thread, NULL, sensing_func, NULL);
  ^~~~~~~~~~~~~~
  pthread_kill
RSSI_client_smartAnt.c:63:2: warning: implicit declaration of function 'pthread_mutex_unlock' [-Wimplicit-function-declaration]
  pthread_mutex_unlock(&sensing_report_mutex);
  ^~~~~~~~~~~~~~~~~~~~
RSSI_client_smartAnt.c: In function 'sensing_func':
RSSI_client_smartAnt.c:108:37: warning: implicit declaration of function 'islower' [-Wimplicit-function-declaration]
   for(i=0;i<strlen(src_mac);i++) if(islower(src_mac[i]))
                                     ^~~~~~~
RSSI_client_smartAnt.c:110:15: warning: implicit declaration of function 'toupper' [-Wimplicit-function-declaration]
    src_mac[i]=toupper(src_mac[i]);
               ^~~~~~~
RSSI_client_smartAnt.c:167:2: warning: 'return' with no value, in function returning non-void
  return;
  ^~~~~~
RSSI_client_smartAnt.c:72:7: note: declared here
 void *sensing_func(void *parm)
       ^~~~~~~~~~~~
```

## 伺服器端程式

client 程式為執行在OpenWRT上的 C 程式，執行時需附上 server IP 與 port，即 `./{client程式名} {server IP} {server port}`。另外，在使用 CRTL+C 中止程式時，請用指令 ps 確認是否所有執行緒皆已中止，並用指令 kill 中止殘留多餘執行緒。若遇到無法使用CRTL+C 中止程式的情形時，重新建立一個新的 SSH 連線並用指令 kill 中止程式。

在 server 與 client 之間，我們依不同方向制定了二種溝通格式，參數之間以空白隔開、訊息之間以換行隔開。server 觸發 client 回報最新的 RSSI 時，會附上該次觸發的序號 (round)。為了讓client 能支援回報多個傳送端的封包 RSSI、並讓 server 能區分回報結果屬於哪回合，client 回報時會附上 server 傳來的序號 (round)、傳送端 (target MAC) 與RSSI (單位為dBm)。

| 方向              | 格式                                             |
| --------------- | ---------------------------------------------- |
| server → client | \[round]                                       |
| client → server | \[round] \[observer MAC] \[target MAC] \[RSSI] |

透過此格式設定，server 會定期要求 WiFi AP 回傳 RSSI 訊號，並顯示於螢幕上。

```
import java.io.*;
import java.net.*;
import java.util.*;

public class RSSI_Server
{
	static public void main(String[] args) throws Exception
	{
		Server Server = new Server();
		Server.start();
		
		IdleHandler IdleHandler=new IdleHandler(Server);
		IdleHandler.start();
		
		//guard interval for access
		Thread.sleep(1000);
		
		int interval=0;
		int round=0;
		String FileName;
		
		Scanner input=new Scanner(System.in);
		
		while(true)
		{
			System.out.println("Enter parameters for sensing");
			
			System.out.println("Enter sensing interval(msec):");
			try
			{
				interval = Integer.valueOf(input.nextLine());
			}
			catch(NumberFormatException e)
			{
				System.out.println("Format ERROR!!");
				continue;
			}
			
			System.out.println("Enter total sensing round:");
			try
			{
				round = Integer.valueOf(input.nextLine());
			}
			catch(NumberFormatException e)
			{
				System.out.println("Format ERROR!!");
				continue;
			}
			
			System.out.println("Enter FileName to save result:");
			if( (FileName = input.nextLine())==null)
			{
				continue;
			}
				
			for(int i=0;i<round;i++)
			{
				Server.sendSensingCommandToAll(String.valueOf(i));
				
				Thread.sleep(interval*3/4);
				Server.getSensingResultFromAll(FileName);
				
				Thread.sleep(interval/4);
				System.out.println(interval+"msec passed");
			}
			
			IdleHandler.clearWatchDogCount();
		}
	}
}

class Server extends Thread
{
	List<Client> users;
		
	public void run()
	{
		users = new ArrayList<Client>();
		System.out.println("server start.");
		
		try
		{
			ServerSocket ss = new ServerSocket(2223);
			
			while(true)
			{
				Socket s = ss.accept(); //listen 2223
																
				Client u = new Client(s, users);
				users.add(u);
				u.start();

				System.out.println(users.size() +" clients are connected");
			}
		}
		catch (IOException e)
		{
			e.printStackTrace();
			System.out.println("server exception while listening.");
		}
	}
	
	public void sendSensingCommandToAll(String cmd)
	{
		for(Client u:users)
		{
			u.sendSensingCommand(cmd);
		}
	}
	
	public void getSensingResultFromAll(String FileName)
	{
		try
		{
			BufferedWriter bufferedWriter=new BufferedWriter(new FileWriter(FileName, true));
			
			for(Client u:users)
			{
				String result = u.getSensingResult();
				if(result!=null)
				{
					bufferedWriter.write(result+'\n');
				}
			}
			
			bufferedWriter.write('\n');
			bufferedWriter.close();
		}
		catch (Exception e)
		{
			e.printStackTrace();
		}
	}
}

class Client extends Thread
{
	Socket UserSocket;
	List<Client> users;
	String message;
	
	Client(Socket s, List<Client> list)
	{
		UserSocket=s;
		users=list;
	}
	
	public void run()
	{
		//server-to-client format: [round]
		//client-to-server format: [round] [observer MAC] [target MAC] [RSSI]
		
		try
		{
			BufferedReader in = new BufferedReader(new InputStreamReader(UserSocket.getInputStream()));
			
			while( (message=in.readLine())!=null)
			{
				System.out.println(message);
			}
		}
		catch (Exception e)
		{
			e.printStackTrace();
			System.out.println("an user is leaving");
			return;
		}
	}
	
	public void sendSensingCommand(String cmd)
	{
		try
		{
			BufferedWriter out = new BufferedWriter(new OutputStreamWriter(UserSocket.getOutputStream()));
			out.write(cmd);
			out.flush();
		}
		catch (Exception e)
		{
			e.printStackTrace();
		}
		
	}
	
	public String getSensingResult()
	{
		String ret=null;
		
		if(message!=null)
		{
			ret = message;
			message = null;
		}
		
		return ret;
	}
}

class IdleHandler extends Thread
{
	int WatchDogCount=0;
	Server Server;
	
	IdleHandler(Server s)
	{
		Server = s;
	}
	
	public void run()
	{
		while(true)
		{
			try
			{
				Thread.sleep(1000);
			}
			catch (InterruptedException e)
			{
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
			
			WatchDogCount++;
			
			if(WatchDogCount>=60)
			{
				Server.sendSensingCommandToAll(String.valueOf(-1));
				WatchDogCount=0;
			}
		}
	}
	
	void clearWatchDogCount()
	{
		WatchDogCount=0;
	}
}

```
