今天以物联网网关(网关链接)以服务器,在多个客户端就做一个非常简单的功能:点亮或熄灭网关上的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,从而控制网关,这样也不错。今天暂搁笔于此,以后有想法再补充。