Android 与AP模式下的硬件连接(嵌入式基础需求)

Android客户端开发可以大致分为两种,一是纯客户端APP开发,像某宝,某东,二是与硬件进行交互的APP,比如家庭WiFi、扫码乘地铁等。我之前是做纯客户端开发,并未与硬件进行交互开发过,也是怀着诸多猜测进入目前的公司,也是仅仅用了十天左右的时间由0到1的快速开发了一款他们需要的产品…之前自己也是查阅了相关博客什么的,希望能够让自己做APP时更有底气和省时间点,但是发现找的貌似都比较深奥,不懂在说啥,所以也通过这段时间的学习整理一份简单易懂的博客,一起学习一起参考。
tip:1.本文用到了retrofit+okhttp+rxjava,不管你会不会,除了动态变化url的地方其他都无关紧要。2.本公司产品代码肯定没这么粗糙,我这边只是给需要学习的小伙伴做下实例介绍。
一、AP模式:
官方解释:AP模式其实是Access Point的简称,意思是:访问接入点。而无线网桥的AP模式,也就是利用无线网桥做无线信号的接入点了。那么,究竟是什么东西接入无线信号呢?其实就是我们平日常用到的手机、笔记本电脑、平板电脑,又或者是无线CPE(WIFI信号无线接入端),因此,我们可以理解为,AP模式,就是当做无线WIFI共享点。
简单的理解:AP模式就是一个不能上网的WiFi(用手机在WLAN中可以看到),你可以直接连接进行简单交互,比如http请求,发送协议内容。
二、业务需求
要求:手机控制机器人比如灯亮,枕头高度(机器人控制枕高),机器人给手机发送用户数据,手机端展示数据给用户看。
在这里插入图片描述

功能分析(该分析基于机器人程序,不同程序操作不同):

  • WiFi 搜索
  • wifi连接(切换)
  • udp广播
  • udp点对点通信
  • baseUrl切换

(以下操作,根据自己的产品会把硬件叫做机器人,以下相关代码主要展示关键API,文末有GitHub连接)

1.WiFi 搜索
与硬件交互首先发现AP模式下的机器人(你可以通过手机WLAN,自主切换,但是为了减少用户的操作,我们可以这样),提供一位大牛写的工程所打包的apk,直接装上可以使用下载地址------不知道怎么免费,如果有需要留言给我,我发你邮箱。

 mainWifi = (WifiManager) getApplicationContext().getSystemService(Context.WIFI_SERVICE);
        receiverWifi = new WifiReceiver();
        //注册广播
        registerReceiver(receiverWifi, new IntentFilter(
                WifiManager.SCAN_RESULTS_AVAILABLE_ACTION));

以注册广播的形式去接收附近的路由通道并展示到屏幕上(手机在局域网中):
在这里插入图片描述
点击要链接的wifi,进行网络切换

2.wifi连接(网络切换)

 //链接AP路由器  AP模式下密码为空
        connectWithWpa(result.SSID, "");
 //切换网络总方法
private void connectWithWpa(String ssid, String pw) {
        WifiUtils.withContext(getApplicationContext())
                .connectWith(ssid, pw)
                .setTimeout(40000)
                .onConnectionResult(this::checkResult)
                .start();
    }
//该方法为切换wifi回调,(里面的代码下面见分晓)
    private void checkResult(boolean isSuccess) {
        if (isSuccess) {
            if (isFirst) {

                //第一次如果链接成功,向AP路由发送WiFissid以及password
                showDialog();

            } else {
                //切换成功后,启动广播,以及接收
                socket = new UDPSocket(MainActivity.this);
                socket.startUDPSocket();
                socket.startHeartbeatTimer("发送的广播内容,随便定义");
            }

        } else {
            Toast.makeText(this, "fail", Toast.LENGTH_SHORT).show();
        }
    }

这里面网络切换会进行两次,1是连接AP模式的机器人,连接成功后,向机器人发送机器人需要的参数:

 //  发送WiFi ssid以及pw以及硬件需要的数据
                WifiApi.getService().lineLogin(wifiNmae, pw, "ACESPillow_neck20180906000000001").compose(MainActivity.this.<WifiBean>getDefaultTransformer())
                        .subscribe(new Observer<WifiBean>() {
                            @Override
                            public void onSubscribe(Disposable d) {
                            }
                            @Override
                            public void onNext(WifiBean r) {
                                //成功返回后,进行网络切换,切换到家庭WiFi
                                isFirst = false;
                                //在这里可以用sp把之前的家庭WiFi ssid以及pw保存起来
                                connectWithWpa("ssid", "pw");//第二次切换网络
                            }
                            @Override
                            public void onError(Throwable e) {
                            }
                            @Override
                            public void onComplete() {
                            }
                        });

2是成功连接机器人后,切换到家庭WiFi,

 //成功返回后,进行网络切换,切换到家庭WiFi
      isFirst = false;
     //在这里可以用sp把之前的家庭WiFi ssid以及pw保存起来
      connectWithWpa("ssid", "pw");//第二次切换网络

并开始发送广播。

3.udp广播
udp通讯不需要建立通道,做简单的数据传输还是很快捷很方便的;广播分为单播、广播、多播,其中多播还是相当牛逼的,可以跨局域网,搞事情的可以研究下。
udp广播我这边封装在了UDPSocket中,主要运用了DatagramSocket ,不管是广播还是udp点对点通信用的都是它:

try {
                byte[] data = new byte[BUFFER_LENGTH];
                InetAddress bcIP = Inet4Address.getByAddress(Utils.getWifiBroadcastIP(context));
                DatagramSocket udp = new DatagramSocket();
                udp.setBroadcast(true);
                DatagramPacket dp = new DatagramPacket(data, data.length, bcIP, CLIENT_PORT);
                // String s = new String("0x00"  + "ACESPillow_neck20180906000000001");
                String s = new String("0x00"  + pId);//这里的广播内容随便定义
                Log.i(LOG_TAG, "Packet send from "+ dp.getAddress()+" with address: " + dp.getSocketAddress());
                // 设置发送广播内容
                byte[] buff =s.getBytes();
                dp.setData(buff,0,buff.length);

                //一次性广播,用完即关闭
                udp.send(dp);
                udp.disconnect();
                udp.close();
            } catch (IOException e) {
                e.printStackTrace();
            }

**dp.getAddress()**可以获取发送端的IP,这样就可以进行点对点通信,发送广播的同时,紧接着开启接收广播。
4.udp点对点通信
在这里其实就是广播接收,开启接收:

try {
                    Log.i(LOG_TAG, "Listener started!");
                  //  DatagramSocket socket = new DatagramSocket(3333);
                    client.setSoTimeout(1500);
                    byte[] buffer = new byte[BUFFER_LENGTH];
                    DatagramPacket packet = new DatagramPacket(buffer, BUFFER_LENGTH);
                    while(LISTEN) {
                        try {
                            Log.i(LOG_TAG, "Listening for packets");
                            client.receive(packet);

                            //返回值
                            String data = new String(buffer, 0, packet.getLength());

                            Log.i(LOG_TAG, "Packet received from "+ packet.getAddress() +" with contents: " + data);
                            String socketAddress = packet.getSocketAddress() + "";
                           // String s = socketAddress.split(":")[0];
                            String address = packet.getAddress() +"";
                            int port = packet.getPort();
                            Log.i(LOG_TAG, "Packet received from "+ socketAddress+" with port: " + port);
                            if (port == 3333) {//判断是不是硬件方发过来的udp信息,接口筛选

                              //  do something
                                //保存硬件发送过来的IP,就是address,拼接成"http:/"+ address

                                stopUDPSocket();//主动关闭广播
                            }
                        }
                        catch(IOException e) {
                            Log.e(LOG_TAG, "IOException in Listener " + e);
                        }
                    }

机器人在接收到广播后,会发送udp通讯,客户端只需要注意两点,1是new DatagramSocket(3333)接口3333是之前拟定协商好的不能变,2是if (port == 3333) //判断是不是硬件方发过来的udp信息,接口筛选排除自己发送的广播,并在其中的逻辑里关闭接收,获取packet.getAddress()udp通讯携带的IP。因为此IP是所在局域网重新分配给机器人的新IP,手机和机器人的通讯就全靠它了。

到此,机器人由AP模式进入了局域网中

5.URl动态切换
完成1-4,当你直接与机器人进行连接时会发现404或者请求断开,查看日志你会发现,请求url还是AP模式时的Url…
网络请求框架在app初始化时就被初始化了,一个retrofit对应一个baseURl…于是乎,动态改变Url,陷入了沉思。
首先感谢:https://www.jianshu.com/p/2919bdb8d09a大家想更清楚地了解的话先去看看他的博客吧,我只是帮忙宣扬一下我怎么用的,解释权归他
首先在Application进行对动态变化的retrofit进行初始化:

//@parm 1 需要url动态变化所对应的ke'y
//@parm2 AP_WIFI是需要动态变化的url本身,在本文中是AP模式下的URL
@Override
    public void onCreate() {
        super.onCreate();
        //初始化要动态变化的wifi
        RetrofitUrlManager.getInstance().putDomain("wifi_name", AP_WIFI);
    }

构建retrofit

    public WifiApi() {

        OkHttpClient builder = RetrofitUrlManager.getInstance().with(new OkHttpClient.Builder()) //RetrofitUrlManager 初始化
                .readTimeout(5, TimeUnit.SECONDS)
                .connectTimeout(5, TimeUnit.SECONDS)
                .retryOnConnectionFailure(true) // 失败重发
              //  .addInterceptor(new HeaderInterceptor2())
              //  .addInterceptor(new LoggingInterceptor.Builder()
              //          .setLevel(Level.BASIC)
             //           .log(Platform.INFO)
              //          .build())
                .build();

        Retrofit retrofit = new Retrofit.Builder()
                .baseUrl("192.168.0.100")//这里就是上面的AP_WIFI
                .addCallAdapterFactory(RxJava2CallAdapterFactory.create()) // 添加Rx适配器
                .addConverterFactory(GsonConverterFactory.create()) // 添加Gson转换器
                .client(builder)
                .build();

         wifiService = retrofit.create(WifiService.class);

    }

在需要转换url的地方:

//连接AP模式的IP可以写入到工程里,但是硬件连接wifi成功后,会重新被当地路由重新分配一个IP,这时候就需要动态换个新生成的IP
                HttpUrl httpUrl = RetrofitUrlManager.getInstance().fetchDomain("wifi_name");//application 处设置的同名
                if (httpUrl == null || !httpUrl.toString().equals("192.168.0.100")) { //可以在 App 运行时随意切换某个接口的 BaseUrl
                    //  RetrofitUrlManager.getInstance().putDomain(Constant.WIFI, Constant.WIFI_BASE_URL);
                    RetrofitUrlManager.getInstance().setGlobalDomain("udp收到的address");
                    //如果您已经确定了最终的 BaseUrl ,不需要再动态切换 BaseUrl, 请
                    //RetrofitUrlManager.getInstance().setRun(false);
                    // FIXME: 2018/12/12 再进行下面的请求
                    WifiApi.getService().test().compose(MainActivity.this.<WifiBean>getDefaultTransformer())
                            .subscribe(new Observer<WifiBean>() {
                                @Override
                                public void onSubscribe(Disposable d) {
                                }
                                @Override
                                public void onNext(WifiBean r) {
                                    //成功连接WiFi的硬件,就像个小服务器,可以根据需求进行POST 和 GET请求
                                }
                                @Override
                                public void onError(Throwable e) {
                                }
                                @Override
                                public void onComplete() {
                                }
                            });
                }
            }
        });

不需要转换,就像给AP模式下的机器人发送信息时就不需要变化url,这个时候不要

  HttpUrl httpUrl = RetrofitUrlManager.getInstance().fetchDomain("wifi_name");//application 处设置的同名
                if (httpUrl == null || !httpUrl.toString().equals("192.168.0.100")) { /
                    RetrofitUrlManager.getInstance().setGlobalDomain("udp收到的address");
                 

即可。
三、其他

 <uses-permission-sdk-23 android:name="android.permission.INTERNET"/>
    <uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
    <uses-permission android:name="android.permission.CHANGE_WIFI_STATE" />
    <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/>
    <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>

所具备的权限
代码通道:
示例代码——不要吝啬你的小星星

评论 9
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值