今天以物聯網網關(網關鏈接)以服務器,在多個客戶端就做一個非常簡單的功能:點亮或熄滅網關上的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);
}
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,從而控制網關,這樣也不錯。今天暫擱筆於此,以後有想法再補充。