socket通信——多角度控制LED燈亮滅

今天以物聯網網關(網關鏈接)以服務器,在多個客戶端就做一個非常簡單的功能:點亮或熄滅網關上的LED燈。目前想到了三種方式,分別是:TCP&UDP測試工具、自編Java客戶端和Mono Android客戶端。相信這會很有意思的。得意  


1、服務器端

在使用或編寫客戶端之前,首先來看看服務器端代碼,其專門通過串口燒進網關內部

OutputPort led = new OutputPort((Cpu.Pin)GPIO_NAMES.PF8, false);

Socket sc;
Socket ss = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);//創建tcp套接字
IPEndPoint iep = new IPEndPoint(IPAddress.Parse("192.168.13.200"), 8888);//網關IP已靜態配置,端口自選
try
{
	ss.Bind(iep);
	ss.Listen(3);
	Debug.Print("create server ok");
}
catch
{
	Debug.Print("connection failed");
}           

然後等待客戶端的數據並在終端打印,根據所收信息來判斷燈亮滅,最後向客戶端發反饋

while (true)
{
	if ((sc = ss.Accept()) != null)
	{
		Debug.Print("someone connected:" + sc.RemoteEndPoint.ToString());
		while (true)
		{
			Array.Clear(data, 0, data.Length);//一定要清
			sc.Receive(data, 16, 0);//從java客戶端收到的包括\r\n
			string str = new string(System.Text.Encoding.UTF8.GetChars(data));
			Debug.Print(str);
			Debug.Print(str.Length.ToString());
			if (str.IndexOf("ON") >= 0) // = 號不要忽略了
			{
				led.Write(true);
			}
			else if (str.IndexOf("OFF") >= 0)
			{
				led.Write(false);
			}

			Debug.Print("recv ok,about to send ");
			sc.Send(System.Text.Encoding.UTF8.GetBytes(str.IndexOf("ON") >= 0 ? "ON\r\n" : "OFF\r\n"));//爲方便java中的readline,添加了行結束符
		}
	}
 }

註釋的地方基本上是我犯錯的地方


2、客戶端

2.1、TCP&UDP測試工具

測試效果如下:



2.2、Java客戶端

代碼如下:

public class LightLED {

	private Socket client;
	private String host = "192.168.13.200";
	private int port = 8888;
	
	private String on = "ON";
	private String off = "OFF";
	
	public static void main(String[] args) throws InterruptedException,IOException {
		// TODO Auto-generated method stub
		new LightLED().doLED();
	}
	
	public LightLED() throws IOException{
		client = new Socket(host,port);
		System.out.println("connected...");
	}
	
	public void doLED() throws IOException,InterruptedException {
		try {
			BufferedReader br = getReader(client);
			PrintWriter pw = getWriter(client);
			while (true){
				pw.println(on); //不能用print,爲什麼?而且發過去的包括\r\n
				System.out.println("LED: " + br.readLine());				
				Thread.sleep(1000);
				
				pw.println(off);
				System.out.println("LED: " + br.readLine());				
				Thread.sleep(1000);
			}
		} catch (IOException e) {
			// TODO: handle exception
		}
	}
	
	private PrintWriter getWriter(Socket s) throws IOException{
		OutputStream ous = s.getOutputStream();
		return new PrintWriter(ous,true);
	}
	
	private BufferedReader getReader(Socket s) throws IOException {
		InputStream ins = s.getInputStream();
		return new BufferedReader(new InputStreamReader(ins));
	}
}


VS調試窗口:



Java調試窗口:



2.3、Mono Android客戶端

其實用C#也可以寫android程序的,並且還可跨平臺,也輕鬆移植到IOS上。它使用的框架是Mono,開發環境是Xamarin,以前叫Mono Develop。網上有很多教程,我在這裏就不細說了。我本人的相關軟件放在了這裏

之前我也沒用過mono寫過android程序,所以今天是個很好的嘗試。事實證明算這一客戶端最有趣了,待我細細道來。。

我突然想改變一下前面的想法,由於網關上有3個LED燈,於是這次想讓客戶端同時操作這3個燈。當我發一個數值時,相應的燈亮或滅。具體細則是這樣規定的:我所發送的數值範圍是0-7,共8個數,化成二進制恰好可以代表3個燈的狀態,1爲亮,0爲滅,就這樣簡單定義。而且,爲了使得多個android客戶端可同時登陸服務器,利用多線程方式來實現。主線程只負責接收客戶端連接,每個客戶端對應一個單獨線程來與服務器通信。

既然這樣,服務器端代碼就得變了,見下:

首先還是服務器的初使化:

Socket sc = null;
Socket ss = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);//創建tcp套接字
IPEndPoint iep = new IPEndPoint(IPAddress.Parse("192.168.13.200"), 8888);//網關IP已靜態配置,端口自選
try
{
	ss.Bind(iep);
	ss.Listen(5);
	Debug.Print("create server ok");
}
catch
{
	Debug.Print("connection failed");
}           

然後無限循環接收客戶連接:

while (true)
{
	byte[] recedata = new byte[1];
	if ((sc = ss.Accept()) != null)
	{
		Debug.Print("someone connected:" + sc.RemoteEndPoint.ToString());
		ClientThread ch = new ClientThread(sc);//創建客戶線程類
		Thread t = new Thread(new ThreadStart(ch.service));
		t.Start();                                                       
	}
}

相信已發現上面用到了一個類ClientThread,這是我自定義的:

internal class ClientThread
{
	enum GPIO_NAMES
	{
		PA0, PA1, PA2, PA3, PA4, PA5, PA6, PA7, PA8, PA9, PA10, PA11, PA12, PA13, PA14, PA15,
		PB0, PB1, PB2, PB3, PB4, PB5, PB6, PB7, PB8, PB9, PB10, PB11, PB12, PB13, PB14, PB15,
		PC0, PC1, PC2, PC3, PC4, PC5, PC6, PC7, PC8, PC9, PC10, PC11, PC12, PC13, PC14, PC15,
		PD0, PD1, PD2, PD3, PD4, PD5, PD6, PD7, PD8, PD9, PD10, PD11, PD12, PD13, PD14, PD15,
		PE0, PE1, PE2, PE3, PE4, PE5, PE6, PE7, PE8, PE9, PE10, PE11, PE12, PE13, PE14, PE15,
		PF0, PF1, PF2, PF3, PF4, PF5, PF6, PF7, PF8, PF9, PF10, PF11, PF12, PF13, PF14, PF15,
		PG0, PG1, PG2, PG3, PG4, PG5, PG6, PG7, PG8, PG9, PG10, PG11, PG12, PG13, PG14, PG15
	};

	private Socket sc;
	OutputPort led1 = new OutputPort((Cpu.Pin)GPIO_NAMES.PF8, false);//第一個LED
	OutputPort led2 = new OutputPort((Cpu.Pin)GPIO_NAMES.PF7, false);//第二個LED
	OutputPort led3 = new OutputPort((Cpu.Pin)GPIO_NAMES.PF6, false);//第三個LED

	public ClientThread(Socket s)
	{
		sc = s;
		//sc.ReceiveTimeout = 60000; // 1分鐘內若未收到數據,則關閉連接。由於在虛擬設備中反應很慢,所以就註釋掉了          
	}

	public void service()
	{           
		byte[] recedata = new byte[1];//目前只接收0-7的某個數
		try
		{
			while (sc.Receive(recedata) != 0)
			{
				Debug.Print(recedata[0].ToString());
				doLED(recedata);//根據數值點亮或熄滅燈
				Array.Clear(recedata,0,1);
			}
		}
		catch (System.Exception ex)
		{
			sc.Close();
			Debug.Print("rece timeout" + ex.Message);
		}
		
	}
	private void doLED(byte[] recedata)
	{           
		//感覺下面寫得不太簡潔
		byte L1 = (byte)(recedata[0] >> 2);
		byte L2 = (byte)((recedata[0] & 3) >> 1);
		byte L3 = (byte)(recedata[0] & 1);
		if (L1 == 1)
		{
			led1.Write(true);
		}
		else
		{
			led1.Write(false);
		}
		if (L2 == 1)
		{
			led2.Write(true);
		}
		else
		{
			led2.Write(false);
		}
		if (L3 == 1)
		{
			led3.Write(true);
		}
		else
		{
			led3.Write(false);
		}
	}
}

好,以上就是修改過的服務器端。下面是用c#寫的android客戶端。安裝好相關軟件,打開Xamarin,創建android工程:



相關細節就不詳述了,具體可參考官方文檔,寫得很詳細,其實還是用到了android開發的相關概念比如Activity

IDE已經爲你生成了相關框架代碼,生成了類MainActivity,繼承自Activity,重點修改其OnCreate方法,它在界面出現時被調用。

首先創建一些私有量:

private Socket sc = null;
private string host = "192.168.13.200";//服務器端IP和端口
private int port = 8888;

修改OnCreate方法如下:

protected override void OnCreate (Bundle bundle)
{
	base.OnCreate (bundle);
	var layout = new LinearLayout (this);
	layout.Orientation = Orientation.Vertical;

	var lbl = new TextView (this);
	lbl.Text = "hello,xmarin.android";

	sc= new Socket (AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
	try
	{
		sc.Connect(IPAddress.Parse(host),port);//連接服務器
		lbl.Text = "connected with gateway server";
	}
	catch {
		lbl.Text = "conn failed";
	}

	EditText et = new EditText (this);
	var btn = new Button (this);
	btn.Text = "click and send";
	btn.Click += (sender, e) => {	//添加事件處理
		doLED(et.Text);
	};

	layout.AddView (lbl);
	layout.AddView (et);
	layout.AddView (btn);
	SetContentView (layout);
}


一旦button被點擊,執行發送:
public void doLED(string leddata)
{
	byte num = Convert.ToByte (leddata);//獲取EditText的數值
	byte[] senddata = new byte[1] {num};
	sc.Send (senddata);
}

最後來一張界面截圖:



Yoxi.. 客戶端代碼就是這樣,接下來配置好android模擬器,先在模擬器中跑一跑。在服務器端設一個斷點,首先開啓服務器,然後再開啓Xamarin中的程序。一段時間的等待後(android模擬器啓動忒慢),部署程序到模擬器,自動啓動了客戶端。於是首先在文本框中輸入7,使3個燈全亮,見下圖:



果然,三燈全亮。再輸入4,使第1燈亮,其餘二個燈全滅:



最後輸入0,不用說,全滅。如果沒有反應,可嘗試給網關重新上電。

好,最後一招,將客戶端部署到android手機。注意,要提前破解Mono for Android,而且不要簡簡單單地把bin目錄中的apk安裝到手機中,我試過,不能啓動。正確做法是將手機連接PC,安裝好驅動後,在IDE中可看到設備:



我在第一次部署到手機時,碰到了這個問題:FastDev directory creation failed。經查詢這裏有了答案:

https://bugzilla.xamarin.com/show_bug.cgi?id=14474

所以我首先採用release模式部署,再改成debug模式,最後部署成功。

啓動android客戶端,連上路由器使手機與網關在同一個局域網內。先後輸入7,4,0,三燈反應正常:



VS調試窗口如下:


OK,到目前爲止,我的目標總算是實現了,能在android跑還是挺歡喜的。邏輯上沒問題,至於界面的美化嘛,慢慢修改唄。

其實,又有了個新想法,可創建一個html5 移動web應用,放在手機上運行,應該也可以。那個前端東西我不是很熟悉就不做了,感覺應該也不難。或者這樣也可以,以網關爲服務器,以Netduino爲客戶端,通過紅外操作netduino,從而控制網關,這樣也不錯。今天暫擱筆於此,以後有想法再補充。再見

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章