智能家居

课程简介

智能家居是在互联网的影响之下物联化体现。智能家居通过物联网技术将家中的各种设备连接到一起,提供各种控制或者定时控制的功能和手段。与普通家居相比,智能家居不仅具有传统的家庭居住功能,同时还兼备建筑、网络通信、信息家电、设备自动化功能,提供全方位的信息交互功能。

在本课程中一共有两个案例,第一个是智能摄像头,我将演示如何通过自己编写Android应用控制网络摄像头,实现远程监控功能。第二个是蓝牙继电器,通过手机的蓝牙跟智能设备进行通信进而控制开关,实现电器设备的“智能化”。

智能家居发展情况

智能家居起源

智能家居的概念起源很早,但是直到1984年美国联合科技公司(United Technologies Building System)将建筑设备信息化、整合化概念应用于美国康涅狄格州(Connecticut)哈特佛市(Hartford)的CityPlaceBuilding时,才出现了首栋的“智能型建筑”,从此揭开了全世界争相建造智能家居派的序幕。智能家居从开始到现在主要经历了四个阶段。

1、家庭自动化

通过一个中央微处理机接受相关电子产品(检测环境变化)的信息,再发送给其他产品。

2、家庭网络

家庭网络是在家庭范围内将家电(安全系统、照明系统)和广域网相连接的一种新技术。

3、网络家电

利用数字技术、网络技术及智能控制技术设计改进的新型家电产品,比如网络空调。

4、信息家电

能够通过网络系统交互信息的家电产品。

国内发展现状

智能家居在我国还是一个新生产业,处于一个导入期与成长期的临界点,我国政府在2013年8月14日发表了关于促进信息消费扩大内需的若干意见,大力发展宽带业务,也为智能家居打下了坚实的基础,加之智能家居市场消费观念还未形成,市场的消费潜力必然是巨大的,产业前景光明。

1、萌芽期/智能小区期(1994年-1999年)

概念熟悉、产品认知的阶段,还没有出现专业的智能家居生产厂商。

2、开创期(2000年-2005年)

成立了五十多家智能家居研发生产企业,没有进入国内市场。

3、徘徊期(2006-2010年)

过分夸大智能家居的功能,行业用户、媒体开始质疑智能家居的实际效果 ,国内企业转型,国外企业进入(罗格朗、霍尼韦尔)。

4、融合演变期(2011-2020年)

进入2014年以来,各大厂商已开始密集布局智能家居,经过一年多产业磨合,2015年合作企业已普遍进入到出成果时刻,智能家居新品已经层出不穷的出现了。

智能家电

智能灯泡
智能摄像头
智能空调

蓝牙灯泡

什么是蓝牙灯泡

智能家居的一部分,将蓝牙模块和灯泡相结合,通过手机和蓝牙模块进行通讯,控制电灯中的电压板,从而控制灯泡的打开、关闭、点动等操作。

应用场景

住家、卧室、客厅、厨房、浴厕,办公室、会议室、地下 室、汗蒸房、美容院、医院、疗养院等等地方。

优缺点

优点:方便、快捷、能耗低、寿命长、扩展性好
缺点:受距离限制(5-10米),不稳定

基本组成

灯泡

智能家居_第1张图片

蓝牙模块

智能家居_第2张图片

手机,目标:通过手机控制灯泡的打开、关闭、点动操作

基本流程

智能家居_第3张图片

//获取本地蓝牙适配器
BluetoothAdapter mBluetoothAdapter =  BluetoothAdapter.getDefaultAdapter();

//打开手机蓝牙
mBluetoothAdapter .enable();     
//关闭手机蓝牙
mBluetoothAdapter.disable();

//扫描蓝牙设备
mBluetoothAdapter.startDiscovery();
//取消扫描蓝牙设备,减少资源的消耗
mBluetoothAdapter.cancelDiscovery();

startDiscovery()和cancelDiscovery()必须在工程中注册一个蓝牙广播接受者

//添加蓝牙广播接受者

IntentFilter filter = new IntentFilter();
// 开始扫描的广播
filter.addAction(BluetoothAdapter.ACTION_DISCOVERY_STARTED);    
// 扫描完成的广播
filter.addAction(BluetoothAdapter.ACTION_DISCOVERY_FINISHED);   
// 发现一个可用的设备的广播
filter.addAction(BluetoothDevice.ACTION_FOUND);
mBluetoothReceiver = new BluetoothReceiver();
//注册监听
registerReceiver(mBluetoothReceiver, filter);

蓝牙广播接受者

class BluetoothReceiver extends BroadcastReceiver {

        @Override
        public void onReceive(Context context, Intent intent) {
                //获取蓝牙设备
        }
}

连接设备

public void connectServer(final BluetoothDevice device) {
    new Thread(new Runnable(){
        @Override
        public void run() {
        try {
                    System.out.println(randomUUID.toString());
                    BluetoothSocket clientSocket = device.
            createRfcommSocketToServiceRecord(
        UUID.fromString("00001101-0000-1000-8000-00805f9b34fb"));
                    clientSocket.connect();
                    out = clientSocket.getOutputStream();
                    System.out.println("连接成功");
                    Looper.prepare();
                   Toast.makeText(BluetoothDemoActivity.this, "连接成功", 0).show();
                   Looper.loop();
        } catch (IOException e) {
                   e.printStackTrace();
        }
        }}).start();
    }

注意:蓝牙2.1版本不用使用匹配的UUID也可以,但不保证准确性,蓝牙4.0版本需要使用匹配的UUID,为了兼容性都使用匹配的UUID
UUID: 00001101-0000-1000-8000-00805f9b34fb
匹配密码:1518(高版本为了低功耗不用密码验证)

Looper :是用来封装消息循环和消息队列的一个类,用于在android线程中进行消息处理
Looper.prepare() :在一个线程中运行一个消息循环,通过perpare开启消息循环
Looper.loop() :循环处理消息,直到循环结束为止

打开灯泡

private void openLight() {
        if(out == null) return;
        try {
            // 向服务端写数据
            byte[] b = new byte[5];
            b[0] = (byte) 0x01;
            b[1] = (byte) 0x99;
            b[2] = (byte) 0x10;
            b[3] = (byte) 0x10;
            b[4] = (byte) 0x99;
            out.write(b);
            out.flush();
        } catch (IOException e) {
            Toast.makeText(this, "打开失败", 0).show();
            e.printStackTrace();
        }
    }

注销广播接受者

@Override
protected void onDestroy() {
        super.onDestroy();
        unregisterReceiver(mBluetoothReceiver);
}

智能摄像头

准备工作

网络摄像头,我这里采用的是 品牌: EasyN/易视眼 型号: TM007 ,产品图片如下。
智能家居_第4张图片

对应SDK

智能家居

我们开发的软件是基于第三方智能网平台的,因此需要使用到第三方的SDK。

工作原理

首先当我们将网络摄像头买回来的时候,需要给其设置wifi账号和密码,以让我们的硬件能够连接到物联网云平台。在我们这个案例中使用到的物联网云平台是台湾TUTK公司http://www.tutk.com/推出的IOTC(Internet of Things Cloud 物联网云)平台。摄像头使用的是深圳市普顺达科技有限公司 http://www.easyn.cn/的硬件设备。

这两家公司的网页首页截图如下:

智能家居_第5张图片

当我们的摄像头通上电并设置好网络后会自动连接到IOTC服务器。然后处于等待状态,等待接收来自App端发送的指令。App通过硬件设备的UID、用户名、密码登陆到IOTC服务器,然后发送指令。IOTC获取到App的指令后在转发给设备,然后设备将返回结果(可能是状态数据也可能是摄像机画面)在返回给IOTC,IOTC再将这些结果数据传给App。这样整个就实现了手机和摄像头的互联互动。在这整个过程中IOTC平台是核心部分,也可以看出智能家居其实拼的就是服务平台。

智能家居_第6张图片

代码实现

我们要做的案例效果图如下,总共有两个界面,第一个界面是登录界面,在文本框中输入名称、UID、密码,其中名称是我们自己给自己的摄像头设备起的名称,可以随意写。UID在设备上有,我们直接抄过来,是设备的唯一标识,不可以修改。密码是设备出厂时给了默认的,我们后期可以修改的。为了防止被黑客给黑掉,其实我们只要UID不外漏即可。

智能家居_第7张图片

编写布局文件 布局文件共两个,第一个登录界面的布局太简单了,就不给出来了。值给出第二个布局文件。

activity_camera.xml


<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
              android:layout_width="match_parent"
              android:layout_height="match_parent"
              android:orientation="vertical" >
    <com.tutk.IOTC.Monitor
        android:id="@+id/monitor"
        android:layout_width="400dp"
        android:layout_height="320dp" />
    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:gravity="center_horizontal"
        android:orientation="horizontal" >

        <TextView
            android:id="@+id/tv_state"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="连接状态:" />
    LinearLayout>
    <RelativeLayout
        android:layout_width="match_parent"
        android:layout_height="200dp"
        android:layout_gravity="center"
        android:gravity="bottom" >
        <View
            android:id="@+id/center"
            android:layout_width="10dp"
            android:layout_height="10dp"
            android:layout_centerInParent="true"
            android:layout_margin="20dp"
            android:background="#ff0000" />
        <ImageButton
            android:id="@+id/ib_left"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_centerVertical="true"
            android:layout_toLeftOf="@id/center"
            android:background="@drawable/left"
            android:contentDescription="@null" />
        <ImageButton
            android:id="@+id/ib_right"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_centerVertical="true"
            android:layout_toRightOf="@id/center"
            android:background="@drawable/right"
            android:contentDescription="@null" />
        <ImageButton
            android:id="@+id/ib_bottom"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_below="@id/center"
            android:layout_centerHorizontal="true"
            android:background="@drawable/bottom"
            android:contentDescription="@null" />
        <ImageButton
            android:id="@+id/ib_top"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_above="@id/center"
            android:layout_centerHorizontal="true"
            android:background="@drawable/top"
            android:contentDescription="@null" />
    RelativeLayout>
LinearLayout>

添加类库 如下图所示,考虑到类库的保密性在该文档中就不再给出下载地址。

智能家居_第8张图片

核心代码 总共有两个Activity,分别是MainActivity和CameraPlayActivity

MainActivity代码如下:

package com.example.smartcamera;

import android.content.Intent;
import android.os.Bundle;
import android.support.v7.app.ActionBarActivity;
import android.text.TextUtils;
import android.view.View;
import android.widget.EditText;
import android.widget.Toast;

import com.google.testapp.R;

public class MainActivity extends ActionBarActivity {
    public static final String NAME = "name";
    public static final String PSW = "psw";
    public static final String UID = "uid";
    private EditText et_name;
    private EditText et_pwd;
    private EditText et_uid;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        initView();
    }
    /**
     * 初始化子控件
     */
    private void initView() {

        et_name = (EditText) findViewById(R.id.et_devname);
        et_pwd = (EditText) findViewById(R.id.et_psw);
        et_uid = (EditText) findViewById(R.id.et_uid);
    }
    /**
     * 点击Button 开始链接
     *
     * @param view
     */
    public void connect(View view) {
        String name = et_name.getText().toString();
        String pwd = et_pwd.getText().toString();
        String uid = et_uid.getText().toString();
        if (TextUtils.isEmpty(name) || TextUtils.isEmpty(pwd) ||
                TextUtils.isEmpty(uid)) {
            Toast.makeText(this, "数据不能为空。", Toast.LENGTH_SHORT).show();
            return;
        }
        Intent intent = new Intent(this, CameraPlayActivity.class);
        intent.putExtra(NAME, name);
        intent.putExtra(PSW, pwd);
        intent.putExtra(UID, uid);
        //跳转到播放页面
        startActivity(intent);
    }
}

CameraPlayActivity代码如下:

package com.example.smartcamera;

import android.app.Activity;
import android.content.Intent;
import android.graphics.Bitmap;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.ImageButton;
import android.widget.TextView;

import com.google.testapp.R;
import com.tutk.IOTC.AVIOCTRLDEFs;
import com.tutk.IOTC.AVIOCTRLDEFs.SMsgAVIoctrlPtzCmd;
import com.tutk.IOTC.Camera;
import com.tutk.IOTC.IRegisterIOTCListener;
import com.tutk.IOTC.Monitor;

public class CameraPlayActivity extends Activity implements OnClickListener,
        IRegisterIOTCListener {
    private Monitor     monitor;
    private ImageButton ib_left;
    private ImageButton ib_right;
    private ImageButton ib_top;
    private ImageButton ib_bottom;
    private TextView    tv_state;
    private String      mName;
    private String      mPsw;
    private String      mUID;
    private Camera      mCamera;
    Handler handler = new Handler() {
        @Override
        public void handleMessage(android.os.Message msg) {
            switch (msg.what) {
                // 针对不同的连接状态做不同的处理
                case Camera.CONNECTION_STATE_CONNECT_FAILED:
                    tv_state.setText("连接失败");
                    break;
                // 如果是已经连接成功
                case Camera.CONNECTION_STATE_CONNECTED:
                    tv_state.setText("已连接");
                    // 调用play()方法,完成Camera 和Monitor 的绑定,最终将画面显示出来
                    play();
                    break;
                case Camera.CONNECTION_STATE_CONNECTING:
                    tv_state.setText("连接中...");
                    break;
                case Camera.CONNECTION_STATE_DISCONNECTED:
                    tv_state.setText("未连接");
                    break;
                case Camera.CONNECTION_STATE_TIMEOUT:
                    tv_state.setText("连接超时");
                    break;
                case Camera.CONNECTION_STATE_UNKNOWN_DEVICE:
                    tv_state.setText("未知设备");
                    break;
                case Camera.CONNECTION_STATE_UNSUPPORTED:
                    tv_state.setText("不支持的设备");
                    break;
                case Camera.CONNECTION_STATE_WRONG_PASSWORD:


                    tv_state.setText("密码不正确");
                    break;
                case Camera.CONNECTION_STATE_NONE:
                    tv_state.setText("未知错误");
                    break;
                default:
                    tv_state.setText("未知" + msg.what);
                    break;
            }
        }

        ;
    };

    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_camera);
        // 初始化控件
        initView();
        // 初始化数据
        initData();
        // 开始连接网络
        connectNet();
    }

    /**
     * 显示画面的核心方法
     */
    protected void play() {
        /**
         * 如果条件满足则
* 1 将界面monitor 和Camera 进行绑定
* 2 开始播放Camera 画面 * */
if (mCamera != null && mCamera.isChannelConnected(Camera.DEFAULT_AV_CHANNEL)) { monitor.attachCamera(mCamera, Camera.DEFAULT_AV_CHANNEL); mCamera.startShow(Camera.DEFAULT_AV_CHANNEL, true); } } private void connectNet() { // 初始化摄像头内部加载c 语言库 Camera.init(); // 新创建一个Camera 实例不是Android 中的Camera 而是com.tutk.IOTC.Camera.Camera mCamera = new Camera(); /** * 给Camera 注册IOTC 监听
* IOTC Internet of Things Cloud 物联网云平台
* 该平台是台湾TUTK 公司推出,有偿使用,因此SDK 不开源 */
mCamera.registerIOTCListener(this); /** * 连接到IOTC 云平台
* 连接传入的是UID
* 每个硬件设备出厂时都有唯一的UID编号,并且出厂时已经将该编号注册到IOTC平台服务器
* 硬件在连接服务器的时候需要带着自己的身边标识(就是UID) */
mCamera.connect(mUID); /** * 仅仅让硬件连接网络还是不够了,作为终端用户我们想看到云平台上的摄像头画面那么还需要用户 * 通过用户名和密码登陆进行身份验证
* * @parameter Camera.DEFAULT_AV_CHANNEL * 我们可以把一个摄像头硬件理解为一个电视机,那么电视机可以有多个频道,这里我 * 们使用默认频道即可 * @parameter mName 用户名
* @parameter mPsw 密码
* * 当开始连接的时候,由于之前注册了Camera 监听,因此接收到的数据会以回调的形 * 式传到形参中 * ,因此这个时候我们就去IRegisterIOTCListener 的回调方法中等数据就行了 */
mCamera.start(Camera.DEFAULT_AV_CHANNEL, mName, mPsw); } /** * 从上一个Activity 中获取用户数据 */ private void initData() { Intent intent = getIntent(); mName = intent.getStringExtra(MainActivity.NAME); mPsw = intent.getStringExtra(MainActivity.PSW); mUID = intent.getStringExtra(MainActivity.UID); } /** * 当前类已经实现了OnClickListener 接口因此绑定点击事件只需要传递this 即可 */ private void initView() { monitor = (Monitor) findViewById(R.id.monitor); ib_left = (ImageButton) findViewById(R.id.ib_left); ib_right = (ImageButton) findViewById(R.id.ib_right); ib_top = (ImageButton) findViewById(R.id.ib_top); ib_bottom = (ImageButton) findViewById(R.id.ib_bottom); tv_state = (TextView) findViewById(R.id.tv_state); // 初始化点击事件 ib_bottom.setOnClickListener(this); ib_left.setOnClickListener(this); ib_right.setOnClickListener(this); ib_top.setOnClickListener(this); } /** * 上下左右点击事件的绑定 */ @Override public void onClick(View v) { switch (v.getId()) { case R.id.ib_left: /** * 发送左移动画面指令
* PTZ :在安防监控应用中是Pan/Tilt/Zoom 的简写,代表云台全方位(左右/上下)移 * 动及镜头变倍、变焦控制。
* 因为上下左右都需要发送指令,因此我抽取出一个方法 */
sendPTZ(AVIOCTRLDEFs.AVIOCTRL_PTZ_LEFT); break; case R.id.ib_right: sendPTZ(AVIOCTRLDEFs.AVIOCTRL_PTZ_RIGHT); break; case R.id.ib_bottom: sendPTZ(AVIOCTRLDEFs.AVIOCTRL_PTZ_DOWN); break; case R.id.ib_top: sendPTZ(AVIOCTRLDEFs.AVIOCTRL_PTZ_UP); break; } } ; /** * 发送移动指令 */ private void sendPTZ(int type) { /** * 给摄像头发送指令 * * @parameter Camera.DEFAULT_AV_CHANNEL 默认频道 * AVIOCTRLDEFs.IOTYPE_USER_IPCAM_PTZ_COMMAND 指令类型 * SMsgAVIoctrlPtzCmd.parseContent((byte) type, (byte) 0, * (byte) 0, (byte) 0, (byte) 0, (byte) * Camera.DEFAULT_AV_CHANNEL) * 指令数据,第三个参数是字节数组我们直接使用SMsgAVIoctrlPtzCmd 类的 * parseContent 方法生成即可 * */ mCamera.sendIOCtrl(Camera.DEFAULT_AV_CHANNEL, AVIOCTRLDEFs.IOTYPE_USER_IPCAM_PTZ_COMMAND, SMsgAVIoctrlPtzCmd.parseContent((byte ) type, (byte) 0, (byte) 0, (byte) 0, (byte) 0, (byte) Camera .DEFAULT_AV_CHANNEL)); } // 销毁时退出摄像头 @Override protected void onDestroy() { super.onDestroy(); quit(); } /** * 当接收到服务器的数据时回调该函数 * * @param resultCode 返回状态码 */ @Override public void receiveChannelInfo(Camera arg0, int channel, int resultCode) { Message msg = Message.obtain(); msg.what = resultCode; handler.sendMessage(msg); } @Override public void receiveFrameData(Camera arg0, int arg1, Bitmap arg2) { } @Override public void receiveFrameInfo(Camera arg0, int arg1, long arg2, int arg3, int arg4, int arg5, int arg6) { } @Override public void receiveIOCtrlData(Camera arg0, int arg1, int arg2, byte[] arg3) { } @Override public void receiveSessionInfo(Camera arg0, int arg1) { } @Override public void onBackPressed() { super.onBackPressed(); quit(); } /** * 断开连接 */ private void quit() { if (monitor != null) { // 1.解除绑定 monitor.deattachCamera(); // 2.停止显示 mCamera.stopShow(Camera.DEFAULT_AV_CHANNEL); // 3.断开连接 // 3.1 取消渠道号 mCamera.stop(Camera.DEFAULT_AV_CHANNEL); // 3.2 断开连接 mCamera.disconnect(); // 4.注销监听 mCamera.unregisterIOTCListener(this); } } }

添加访问网络权限 因为我们的App是访问网络的,当然得记得添加权限

<uses-permission android:name="android.permission.INTERNET"/>

智能继电器

智能继电器目前多用于控制家庭电器的断电与通电。智能继电器跟普通继电器的不同之处就是上面内置了蓝牙芯片,该蓝牙芯片允许手机蓝牙进行连接,然后接收手机蓝牙发送的指令,根据不同的指令打开或者关闭开关,从而间接实现控制家用电器的功能。

准备工作

1、智能继电器

从网上购买了蓝牙继电器。产品详情链接(可能有做广告嫌疑):https://detail.tmall.com/item.htm?id=44157073788&spm=a1z09.2.9.133.cmtDUE&_u=2am3rd62f16

2、灯泡组 电池 电线 螺丝刀工具等

该继电器是针对220v居民用电设计的,但是我们是实验的用途因此用3v的电池即可,灯泡就是用发光二极管即可,这样保证了绝对的安全。

3、发光二极管详情:https://detail.tmall.com/item.htm?id=17637399755&spm=a1z09.2.9.100.cmtDUE&_u=2am3rd662b0

4、不需要开发SDK,只需要知道继电器蓝牙的指令集即可,在购买的界面商家提供了所有的指令集

工作原理

手机搜索到继电器的蓝牙,然后通过蓝牙的配对密码连接上继电器,继电器就是开关,只不过这里用的是继电器上有多个开关组,手机通过蓝牙给继电器发送指令,继电器把指令解析成相应的开或者关的动作,这样就实现了手机控制电器的操作。我自己做成以后的真实图如下。中间方形的是蓝牙继电器,右侧是发光二极管,二极管使用干电池供电。手机通过软件控制灯泡的开和关。

智能家居_第9张图片

代码实现

软件总共有2个界面,运行效果图如下所示。
第一个界面用于查找周围的蓝牙设备。将找到的蓝牙用ListView显示出来,然后点击智能继电器对应的蓝牙设备,进入第二个界面,在第二个界面完成对智能继电器的多种操作。

智能家居_第10张图片

activity_main.xml对应第一个界面:

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
              android:layout_width="match_parent"
              android:layout_height="match_parent"
              android:orientation="vertical" >
    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="horizontal"
        >
        <Button
            android:layout_width="0dp"
            android:layout_weight="1"
            android:layout_height="wrap_content"
            android:layout_margin="5dp"
            android:onClick="checkBluetooth"
            android:text="监测蓝牙设备" />
        <Button
            android:id="@+id/btn_scan"
            android:layout_width="0dp"
            android:layout_weight="1"
            android:layout_height="wrap_content"
            android:layout_margin="5dp"
            android:onClick="scanBluetooth"
            android:text="扫描蓝牙" />
    LinearLayout>
    <ListView
        android:id="@+id/lv"
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:layout_weight="1"
        >
    ListView>
LinearLayout>

activity_control.xml对应第二个界面


<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
            android:layout_width="match_parent"
            android:layout_height="match_parent">

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="vertical">

        <Button
            android:id="@+id/btn_kaiguan1"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_margin="5dp"
            android:text="开关1"/>

        <Button
            android:id="@+id/btn_kaiguan2"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_margin="5dp"
            android:text="开关2"/>

        <Button
            android:id="@+id/btn_kaiguan3"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_margin="5dp"
            android:text="开关3"/>

        <Button
            android:id="@+id/btn_kaiguan4"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_margin="5dp"
            android:text="开关4"/>

        <Button
            android:id="@+id/btn_kaiguan5"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_margin="5dp"
            android:text="开关5"/>

        <Button
            android:id="@+id/btn_kaiguanAll"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_margin="5dp"
            android:text="全部"/>

        <Button
            android:id="@+id/btn_kaiguan5_diandong"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_margin="5dp"
            android:text="开关5 点动1s"/>

        <Button
            android:id="@+id/btn_kaiguan5_husuo"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_margin="5dp"
            android:text="开关5 互锁"/>

        <Button
            android:id="@+id/btn_kaiguan5_zisuo"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_margin="5dp"
            android:text="开关5 自锁"/>
    LinearLayout>
ScrollView>

MainActivity.java是入口Activity

package com.example.bluetoothtest;

import android.support.v7.app.ActionBarActivity;

package com.example.bluetoothtest;

import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.os.Bundle;
import android.support.v7.app.ActionBarActivity;
import android.view.View;
import android.widget.AdapterView;
import android.widget.AdapterView.OnItemClickListener;
import android.widget.ArrayAdapter;
import android.widget.Button;
import android.widget.ListView;
import android.widget.Toast;

import com.google.testapp.R;

import java.util.ArrayList;

public class MainActivity extends ActionBarActivity {
    private static final String BTN_SCANING       = "正在扫描蓝牙设备...";
    private static final String BTN_SCAN          = "扫描蓝牙设备";
    private static final int    REQUEST_ENABLE_BT = 1;
    private BluetoothAdapter mBluetoothAdapter;
    private ArrayList mArrayAdapter = new ArrayList<>();
    private Button btn_scan;
    private ArrayList devices = new ArrayList<>();
    private ListView             mListView;
    private ArrayAdapter mAdapter;
    // Create a BroadcastReceiver for ACTION_FOUND
    private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
        public void onReceive(Context context, Intent intent) {
            String action = intent.getAction();
            // When discovery finds a device
            if (BluetoothDevice.ACTION_FOUND.equals(action)) {
                // Get the BluetoothDevice object from the Intent
                BluetoothDevice device =
                        intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
                // Add the name and address to an array adapter to show in a ListView
                mArrayAdapter.add(device.getName() + "\n" + device.getAddress());
                devices.add(device);
                mAdapter.notifyDataSetChanged();
            }
        }
    };

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        initView();
    }

    private void initView() {
        mListView = (ListView) findViewById(R.id.lv);
        mAdapter = new ArrayAdapter<>(this, android.R.layout.simple_list_item_1,
                mArrayAdapter);
        mListView.setAdapter(mAdapter);
        mListView.setOnItemClickListener(new OnItemClickListener() {
            @Override
            public void onItemClick(AdapterView parent, View view, int position,
                                    long id) {
                Intent intent = new Intent(MainActivity.this,
                        ControlActivity.class);
                intent.putExtra("device", devices.get(position));
                mBluetoothAdapter.cancelDiscovery();
                btn_scan.setText(BTN_SCAN);
                startActivity(intent);
            }
        });
        btn_scan = (Button) findViewById(R.id.btn_scan);
        mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
    }

    /**
     * 监测蓝牙是否开启,如果没有开,请求打开
     *
     * @param view
     */
    public void checkBluetooth(View view) {

        if (mBluetoothAdapter == null) {
            Toast.makeText(this, "对不起,您的设备不支持蓝牙。", 0).show();
            return;
        }
        //监测蓝牙是否可用
        boolean enabled = mBluetoothAdapter.isEnabled();
        if (!enabled) {
            Intent enableBtIntent = new
                    Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
            startActivityForResult(enableBtIntent, REQUEST_ENABLE_BT);
            return;
        }
        //查询已经配对的蓝牙
        queryingPairedDevices();
    }

    private void queryingPairedDevices() {
        mArrayAdapter.clear();
        devices = new ArrayList(mBluetoothAdapter.getBondedDevices());
        // If there are paired devices
        if (devices.size() > 0) {
            // Loop through paired devices
            for (BluetoothDevice device : devices) {
                // Add the name and address to an array adapter to show in a ListView
                黑马程序员——只要学不死,就往死里学,冲击年薪20 万!

                mArrayAdapter.add(device.getName() + "\n" + device.getAddress());
            }
            mAdapter.notifyDataSetChanged();
        } else {
            Toast.makeText(this, "没有找到已经配对的蓝牙", 0).show();
        }
    }

    /**
     * 扫描蓝牙
     *
     * @param view
     */
    public void scanBluetooth(View view) {
        if (mBluetoothAdapter.isEnabled() && !mBluetoothAdapter.isDiscovering()) {
            boolean startDiscovery = mBluetoothAdapter.startDiscovery();
            if (!startDiscovery) {
                Toast.makeText(this, "开始扫描失败", 0).show();
                return;
            }
            Toast.makeText(this, "开始扫描", 0).show();
            mArrayAdapter.clear();
            devices.clear();
            btn_scan.setText(BTN_SCANING);
            // Register the BroadcastReceiver
            IntentFilter filter = new IntentFilter(BluetoothDevice.ACTION_FOUND);
            // Don't forget to unregister during onDestroy
            registerReceiver(mReceiver, filter);
        } else {
            String scan_state = btn_scan.getText().toString();
            if (BTN_SCANING.equals(scan_state)) {
                boolean cancelDiscovery = mBluetoothAdapter.cancelDiscovery();
                if (cancelDiscovery) {
                    btn_scan.setText(BTN_SCAN);
                }
                unregisterReceiver(mReceiver);
            }
        }
    }

    @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
        super.onActivityResult(requestCode, resultCode, data);
        if (resultCode == RESULT_OK) {
            Toast.makeText(this, "蓝牙已经可以使用", 0).show();
            checkBluetooth(null);
        } else if (resultCode == RESULT_CANCELED) {
            Toast.makeText(this, "对不起,您的设备没有打开蓝牙,无法使用该应用咯", 0).show();
            return;
        }
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        if (mBluetoothAdapter.isDiscovering()) {
            mBluetoothAdapter.cancelDiscovery();
            btn_scan.setText(BTN_SCAN);
        }
    }
}

ControlActivity.java是核心类,用于发送指令

package com.example.bluetoothtest;

import android.annotation.SuppressLint;
import android.app.Activity;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothSocket;
import android.content.Intent;
import android.os.Bundle;
import android.os.Looper;
import android.os.SystemClock;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.Toast;

import com.google.testapp.R;

import java.io.IOException;
import java.io.OutputStream;
import java.util.UUID;

public class ControlActivity extends Activity implements OnClickListener {
    /**
     * 用于设置开关的状态,显示在Button 上的文字就是
     */
    private final static String KAIGUAN1_STATE_OPEN = "开关1 已经打开";
    private final static String KAIGUAN2_STATE_OPEN = "开关2 已经打开";
    private final static String KAIGUAN3_STATE_OPEN = "开关3 已经打开";
    private final static String KAIGUAN4_STATE_OPEN = "开关4 已经打开";
    private final static String KAIGUAN5_STATE_OPEN = "开关5 已经打开";
    private final static String KAIGUAN1_STATE_CLOSE = "开关1 已经关闭";
    private final static String KAIGUAN2_STATE_CLOSE = "开关2 已经关闭";
    private final static String KAIGUAN3_STATE_CLOSE = "开关3 已经关闭";
    private final static String KAIGUAN4_STATE_CLOSE = "开关4 已经关闭";
    private final static String KAIGUAN5_STATE_CLOSE = "开关5 已经关闭";
    private final static String KAIGUANALL_STATE_OPEN = "所有开关已经打开";
    private final static String KAIGUANALL_STATE_CLOSE = "所有开关已经关闭";
    /**
     * 产生一个唯一序列值,用于跟智能继电器的蓝牙连接的时候给自己的蓝牙设备做一个标记作用
     */
    public static final UUID MY_UUID = UUID.randomUUID();
    /**
     * 打开对应的开关的指令集
* 这些指令大家在卖家的产品说明中有,直接抄过来的
* 通过这些指令大家发现通信的时候每一个指令都是一个字节数组 * */
private static final byte[] COMMAND_OPEN_1 = new byte[] { (byte) 0x01, (byte) 0x99, (byte) 0x10, (byte) 0x10, (byte) 0x99 }; private static final byte[] COMMAND_OPEN_2 = new byte[] { (byte) 0x01, (byte) 0x99, (byte) 0x20, (byte) 0x20, (byte) 0x99 }; private static final byte[] COMMAND_OPEN_3 = new byte[] { (byte) 0x01, (byte) 0x99, (byte) 0x30, (byte) 0x30, (byte) 0x99 }; private static final byte[] COMMAND_OPEN_4 = new byte[] { (byte) 0x01, (byte) 0x99, (byte) 0x40, (byte) 0x40, (byte) 0x99 }; private static final byte[] COMMAND_OPEN_5 = new byte[] { (byte) 0x01, (byte) 0x99, (byte) 0x50, (byte) 0x50, (byte) 0x99 }; /** * 关闭对应的开关指令 */ private static final byte[] COMMAND_CLOSE_1 = new byte[] { (byte) 0x01, (byte) 0x99, (byte) 0x11, (byte) 0x11, (byte) 0x99 }; private static final byte[] COMMAND_CLOSE_2 = new byte[] { (byte) 0x01, (byte) 0x99, (byte) 0x21, (byte) 0x21, (byte) 0x99 }; private static final byte[] COMMAND_CLOSE_3 = new byte[] { (byte) 0x01, (byte) 0x99, (byte) 0x31, (byte) 0x31, (byte) 0x99 }; private static final byte[] COMMAND_CLOSE_4 = new byte[] { (byte) 0x01, (byte) 0x99, (byte) 0x41, (byte) 0x41, (byte) 0x99 }; private static final byte[] COMMAND_CLOSE_5 = new byte[] { (byte) 0x01, (byte) 0x99, (byte) 0x51, (byte) 0x51, (byte) 0x99 }; /** * 全部开关控制指令 */ private static final byte[] COMMAND_OPEN_ALL = new byte[] { (byte) 0x01, (byte) 0x99, (byte) 0x64, (byte) 0x64, (byte) 0x99 }; private static final byte[] COMMAND_CLOSE_ALL = new byte[] { (byte) 0x01, (byte) 0x99, (byte) 0x65, (byte) 0x65, (byte) 0x99 }; /** * 开关5 点动1s 所谓的点动1s 就是通电1s 后断点 黑马程序员——只要学不死,就往死里学,冲击年薪20 万! */ private static final byte[] COMMAND_DIANDONG_5 = new byte[] { (byte) 0x01, (byte) 0x99, (byte) 0x53, (byte) 0x53, (byte) 0x99 }; /** * 互锁自锁这两个概念不好理解互锁的效果是把开关5 打开了,把开关4 关闭了。自锁的效果跟开关 * 很类似,不过稍微有差异,对于我们来讲可以忽略 */ private static final byte[] COMMAND_HUSUO_5 = new byte[] { (byte) 0x01, (byte) 0x99, (byte) 0x54, (byte) 0x54, (byte) 0x99 }; private static final byte[] COMMAND_ZISUO_5 = new byte[] { (byte) 0x01, (byte) 0x99, (byte) 0x55, (byte) 0x55, (byte) 0x99 }; private Button btn_kaiguan1; private Button btn_kaiguan2; private Button btn_kaiguan3; private Button btn_kaiguan4; private Button btn_kaiguan5; private Button btn_kaiguanAll; private Button btn_kaiguan5_diandong; private Button btn_kaiguan5_husuo; private Button btn_kaiguan5_zisuo; //蓝牙设备 private BluetoothDevice mDevice; //通过蓝牙设备获取到的流 private BluetoothSocket socket; // 是否连接成功用于记录连接状态 private boolean isConnected = false; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_control); initView(); initData(); initConnection(); } /** * 初始化视图 */ private void initView() { btn_kaiguan1 = (Button) findViewById(R.id.btn_kaiguan1); btn_kaiguan2 = (Button) findViewById(R.id.btn_kaiguan2); btn_kaiguan3 = (Button) findViewById(R.id.btn_kaiguan3); btn_kaiguan4 = (Button) findViewById(R.id.btn_kaiguan4); btn_kaiguan5 = (Button) findViewById(R.id.btn_kaiguan5); btn_kaiguanAll = (Button) findViewById(R.id.btn_kaiguanAll); btn_kaiguan5_diandong = (Button) findViewById(R.id.btn_kaiguan5_diandong); btn_kaiguan5_husuo = (Button) findViewById(R.id.btn_kaiguan5_husuo); btn_kaiguan5_zisuo = (Button) findViewById(R.id.btn_kaiguan5_zisuo); // 设置点击事件 btn_kaiguan1.setOnClickListener(this); btn_kaiguan2.setOnClickListener(this); btn_kaiguan3.setOnClickListener(this); btn_kaiguan4.setOnClickListener(this); btn_kaiguan5.setOnClickListener(this); btn_kaiguanAll.setOnClickListener(this); btn_kaiguan5_diandong.setOnClickListener(this); btn_kaiguan5_husuo.setOnClickListener(this); btn_kaiguan5_zisuo.setOnClickListener(this); // 设置默认状态 btn_kaiguan1.setText(KAIGUAN1_STATE_CLOSE); btn_kaiguan2.setText(KAIGUAN2_STATE_CLOSE); btn_kaiguan3.setText(KAIGUAN3_STATE_CLOSE); btn_kaiguan4.setText(KAIGUAN4_STATE_CLOSE); btn_kaiguan5.setText(KAIGUAN5_STATE_CLOSE); btn_kaiguanAll.setText(KAIGUANALL_STATE_CLOSE); } /** * 初始化数据 */ private void initData() { Intent intent = getIntent(); BluetoothDevice device = intent.getParcelableExtra("device"); if (null == device) { Toast.makeText(this, "没有获取到数据", 0).show(); finish(); } this.mDevice = device; } /** * 初始化蓝牙连接
* 因为蓝牙连接是线程阻塞且耗时的操作,因此需要放到子线程中,当连接成功后修改连接状态为true */
private void initConnection() { Toast.makeText(this, "蓝牙开始连接", 0).show(); new Thread(new Runnable() { @SuppressLint("NewApi") @Override public void run() { if (socket == null) { try { /** * 通过蓝牙设备获取蓝牙Socket 流 */ socket = mDevice.createRfcommSocketToServiceRecord(MY_UUID); } catch (IOException e) { Looper.prepare(); Toast.makeText(ControlActivity.this, "连接失败。" + e, 0).show(); Looper.loop(); e.printStackTrace(); return; } } if (!socket.isConnected()) { try { socket.connect(); } catch (IOException e) { e.printStackTrace(); Looper.prepare(); Toast.makeText(ControlActivity.this, "连接失败。" + e, 0).show(); Looper.loop(); return; } } if (outputStream == null) { try { /** * 通过socket 获取输出流 */ outputStream = socket.getOutputStream(); Looper.prepare(); Toast.makeText(ControlActivity.this, "连接成功。", 0).show(); isConnected = true; Looper.loop(); isConnected = true; } catch (IOException e) { e.printStackTrace(); } } } }).start(); } //如果频繁点击则拒绝,因为对继电器的物理损坏比较大,一秒只能点击一次 long time = SystemClock.uptimeMillis(); @Override public void onClick(View v) { //如果还没连接成功则拒绝往下操作 if (!isConnected) { Toast.makeText(this, "蓝牙正在连接中。。。", 0).show(); return ; } if (SystemClock.uptimeMillis() - time < 1000) { Toast.makeText(this, "您的操作太频繁了,请稍后再试", 0).show(); time = SystemClock.uptimeMillis(); return; } Button button = (Button) v; String state = button.getText().toString(); int id = v.getId(); switch (id) { case R.id.btn_kaiguan1: if (KAIGUAN1_STATE_CLOSE.equals(state)) { //发送对应的指令 sendCommand(COMMAND_OPEN_1); btn_kaiguan1.setText(KAIGUAN1_STATE_OPEN); } else { sendCommand(COMMAND_CLOSE_1); btn_kaiguan1.setText(KAIGUAN1_STATE_CLOSE); } break; case R.id.btn_kaiguan2: if (KAIGUAN2_STATE_CLOSE.equals(state)) { sendCommand(COMMAND_OPEN_2); btn_kaiguan2.setText(KAIGUAN2_STATE_OPEN); } else { sendCommand(COMMAND_CLOSE_2); btn_kaiguan2.setText(KAIGUAN2_STATE_CLOSE); } break; case R.id.btn_kaiguan3: if (KAIGUAN3_STATE_CLOSE.equals(state)) { sendCommand(COMMAND_OPEN_3); btn_kaiguan3.setText(KAIGUAN3_STATE_OPEN); } else { sendCommand(COMMAND_CLOSE_3); btn_kaiguan3.setText(KAIGUAN3_STATE_CLOSE); } break; case R.id.btn_kaiguan4: if (KAIGUAN4_STATE_CLOSE.equals(state)) { sendCommand(COMMAND_OPEN_4); btn_kaiguan4.setText(KAIGUAN4_STATE_OPEN); } else { sendCommand(COMMAND_CLOSE_4); btn_kaiguan4.setText(KAIGUAN4_STATE_CLOSE); } break; case R.id.btn_kaiguan5: if (KAIGUAN5_STATE_CLOSE.equals(state)) { sendCommand(COMMAND_OPEN_5); btn_kaiguan5.setText(KAIGUAN5_STATE_OPEN); } else { sendCommand(COMMAND_CLOSE_5); btn_kaiguan5.setText(KAIGUAN5_STATE_CLOSE); } break; case R.id.btn_kaiguanAll: if (KAIGUANALL_STATE_CLOSE.equals(state)) { sendCommand(COMMAND_OPEN_ALL); btn_kaiguan1.setText(KAIGUAN1_STATE_OPEN); btn_kaiguan2.setText(KAIGUAN2_STATE_OPEN); btn_kaiguan3.setText(KAIGUAN3_STATE_OPEN); btn_kaiguan4.setText(KAIGUAN4_STATE_OPEN); btn_kaiguan5.setText(KAIGUAN5_STATE_OPEN); btn_kaiguanAll.setText(KAIGUANALL_STATE_OPEN); } else { sendCommand(COMMAND_CLOSE_ALL); btn_kaiguan1.setText(KAIGUAN1_STATE_CLOSE); btn_kaiguan2.setText(KAIGUAN2_STATE_CLOSE); btn_kaiguan3.setText(KAIGUAN3_STATE_CLOSE); btn_kaiguan4.setText(KAIGUAN4_STATE_CLOSE); btn_kaiguan5.setText(KAIGUAN5_STATE_CLOSE); btn_kaiguanAll.setText(KAIGUANALL_STATE_CLOSE); } break; case R.id.btn_kaiguan5_diandong: sendCommand(COMMAND_DIANDONG_5); break; case R.id.btn_kaiguan5_husuo: sendCommand(COMMAND_HUSUO_5); break; case R.id.btn_kaiguan5_zisuo: sendCommand(COMMAND_ZISUO_5); break; default: break; } } private OutputStream outputStream; /** * 发送指令考虑到发送数据也是耗时操作,因此也在子线程中执行 */ @SuppressLint("NewApi") private void sendCommand(final byte[] command) { new Thread(new Runnable() { @Override public void run() { try { outputStream.write(command); outputStream.flush(); } catch (IOException e) { Looper.prepare(); Toast.makeText(ControlActivity.this, e.toString(), 0).show(); Looper.loop(); e.printStackTrace(); } } }).start(); } //释放资源 @SuppressLint("NewApi") @Override protected void onDestroy() { super.onDestroy(); if (outputStream != null) { try { outputStream.close(); } catch (IOException e) { e.printStackTrace(); } } if (socket != null) { if (socket.isConnected()) { try { socket.close(); } catch (IOException e) { e.printStackTrace(); } } socket = null; isConnected = false; } } }

Android视频教程

你可能感兴趣的:(Android基础)