设备通信

说明:在设备尚未开发完成时,在管理后台可以启动虚拟设备用于APP的调试。虚拟设备和真实设备使用方法相同,需要先绑定再使用。虚拟设备能够显示APP发到设备的指令,上报数据到云端、填入数据供APP查询。

发送消息到设备

使用二进制通信格式

例如:以开关设备为例,协议如下:

//请求数据包
{ 68 :
    //开关灯(二进制流,由厂商自己解析),其中0代表关灯,1代表开灯
    [ 0/1 , 0 , 0 , 0 ]
}
//响应数据包  
{ 102 :
    //结果(二进制流,由厂商自己解析),其中0代表失败,1代表成功
    [ 0/1 , 0 , 0 , 0 ]
}

截取开灯代码,如下:

public class LightMsg {
    public static final int REQ_CODE = 68;
    public static final int RESP_CODE = 102;

    //0代表关,1代表开
    public static final byte ON = 1;
    public static final byte OFF = 0;

    private byte ledOnOff;

    public LightMsg(byte ledOnOff) {
        this.ledOnOff = ledOnOff;
    }

    public byte[] getLedOnOff() {
        return new byte[]{ledOnOff, 0, 0, 0};
    }

    public String getDescription(){
        if(ledOnOff == OFF)
            return "close light";
        else 
            return "open light";
    }
}

LightMsg lightMsg = new LightMsg(LightMsg.ON);
//初始化ACDeviceMsg对象消息体,设置发送的msgCode及消息内容
ACDeviceMsg deviceMsg = new ACDeviceMsg(LightMsg.REQ_CODE, lightMsg.getLedOnOff(), lightMsg.getDescription());

//设置局域网通讯加密方式,不设置则默认为动态加密
//deviceMsg.setSecurityMode(ACDeviceSecurityMode.DYNAMIC_ENCRYPTED);

//AC.LOCAL_FIRST代表优先走局域网,局域网不通的情况下再走云端
bindMgr.sendToDeviceWithOption(subDomain, physicalDeviceId, deviceMsg, AC.LOCAL_FIRST, new PayloadCallback<ACDeviceMsg>() {
    @Override
    public void success(ACDeviceMsg deviceMsg) {
        byte[] resp = deviceMsg.getContent();
        if(resp[0] == 1){
            //开灯成功
        } else {
            //开灯失败
        }
    }

    @Override
    public void error(ACException e) {
        //网络错误或其他,根据e.getErrorCode()做不同的提示或处理
    }
});

发送到设备sendToDeviceWithOption的参数枚举如下:

  • AC.LOCAL_FIRST代表优先走局域网,局域网不通的情况下走云端通讯
  • AC.CLOUD_FIRST代表优先走云端,云端不通的情况下走局域网通讯
  • AC.ONLY_LOCAL代表仅通过局域网通讯
  • AC.ONLY_CLOUD代表仅通过云端通讯

使用json通信格式

例如:以开关设备为例,协议如下:

//请求数据包
{ 70 :
    {
        //开关灯,其中0代表关灯,1代表开灯
        "switch" : 0/1
    }
}
//响应数据包  
{
     //结果,其中false代表失败,1代表成功
     "result" : false/true
}
JSONObject req = new JSONObject();
try {
    req.put("switch", 1);
} catch (JSONException e) {
}
//初始化ACDeviceMsg对象消息体,设置发送的msgCode及消息内容
ACDeviceMsg deviceMsg = new ACDeviceMsg(70, req.toString().getBytes(), "open light");

//设置局域网通讯加密方式,不设置则默认为动态加密
//deviceMsg.setSecurityMode(ACDeviceSecurityMode.DYNAMIC_ENCRYPTED);

//AC.LOCAL_FIRST代表优先走局域网,局域网不通的情况下再走云端
bindMgr.sendToDeviceWithOption(subDomain, physicalDeviceId, deviceMsg, AC.LOCAL_FIRST, new PayloadCallback<ACDeviceMsg>() {
    @Override
    public void success(ACDeviceMsg deviceMsg) {
        try {
            JSONObject resp = new JSONObject(new String(deviceMsg.getContent()));
        } catch (JSONException e) {
        }
        boolean result = resp.optBoolean("result");
        if (result) {
            //开灯成功
        } else {
            //开灯失败
        }
    }

    @Override
    public void error(ACException e) {
        //网络错误或其他,根据e.getErrorCode()做不同的提示或处理
    }
});

发送到设备sendToDeviceWithOption的参数枚举如下:

  • AC.LOCAL_FIRST代表优先走局域网,局域网不通的情况下走云端通讯
  • AC.CLOUD_FIRST代表优先走云端,云端不通的情况下走局域网通讯
  • AC.ONLY_LOCAL代表仅通过局域网通讯
  • AC.ONLY_CLOUD代表仅通过云端通讯

局域网通信

以下按照两种不同的适用场景进行讲解。

纯局域网下控制设备

适用场景:在已知完全没有外网的情况下控制设备,建议使用不加密/静态加密的方式控制设备。如选择不加密/静态加密方式,设备端亦需要配合修改初始化参数。

获取局域网内的设备列表

//AC.FIND_DEVICE_DEFAULT_TIMEOUT为获取局域网内设备的超时时间,默认3s,
AC.findLocalDevice(AC.FIND_DEVICE_DEFAULT_TIMEOUT, new PayloadCallback<List<ACDeviceFind>>() {
    @Override
    public void success(List<ACDeviceFind> deviceFinds) {
        //没有发现局域网内设备的情况下则返回deviceFinds为空的ArrayList
        //ACDeviceFind对象中包含设备的物理ID及IP,可用于更新界面或者判断当前设备是否在线。
    }

    @Override
    public void error(ACException e) {
        //此处一般是在手机主动关闭了WiFi连接时才会返回
        if(e.getErrorCode == ACException.NO_WIFI_CONNECTED)
                Toast.makeText(this, "请您打开WiFi便于控制设备", Toast.LENGTH_LONG).show();
    }
});

只有实时发现的设备列表可以实现局域网的控制。

发送消息到设备

以msgCode为64,payload为[1,0,0,0]为设备开灯协议举例

ACDeviceMsg deviceMsg = new ACDeviceMsg(64, new byte[]{1,0,0,0}, "open light");

//设置局域网通讯加密方式,此处设置静态加密,不设置则默认为动态加密
deviceMsg.setSecurityMode(ACDeviceSecurityMode.STATIC_ENCRYPTED);

//AC.ONLY_LOCAL代表仅通过局域网直连控制
bindMgr.sendToDeviceWithOption(subDomain, physicalDeviceId, deviceMsg, AC.ONLY_LOCAL, new PayloadCallback<ACDeviceMsg>() {
    @Override
    public void success(ACDeviceMsg deviceMsg) {
        //成功发送设备指令并收到设备响应
    }

    @Override
    public void error(ACException e) {
        //网络错误或其他,根据e.getErrorCode()做不同的提示或处理
    }
});

局域网/云端优先控制设备

适用场景:若通过局域网控制设备失败后,仍需要通过云端发送消息到设备;反之亦相同。

从云端获取已绑定设备的列表

获取设备列表(在网络环境差或者没有外网的情况下如果获取不到设备列表会从本地缓存里取设备列表)。

//获取设备列表
public void getDeviceList() {
    bindMgr.listDevicesWithStatus(new PayloadCallback<List<ACUserDevice>>() {
        @Override
        public void success(List<ACUserDevice> deviceList) {
            for(ACUserDevice device:deviceList){
                /**
                 * 设备在线状态(listDeviceWithStatus时返回,listDevice不返回该值)
                 * 0不在线 1云端在线 2局域网在线 3云端和局域网同时在线
                 * 若只选择直连的通讯方式,则只有在2和3的状态下才能往设备发送成功
                 */
                device.getStatus();
            }
        }

        @Override
        public void error(ACException e) {
            //建议不做处理
        }
    });
}

注意:app启动初始化AbleCloud时会自动获取局域网设备,由于获取局域网设备是一个异步过程(默认时间为2s,可以根据实际情况设置AC.INIT_APP_DEFAULT_TIMEOUT的值,建议为闪屏页的时间),所以建议在启动app到打开设备列表页面之间增加一个闪屏页面。

定时刷新界面上的局域网状态

注意:若不需要在APP界面上实时显示局域网的状态,则不需要此步骤,直接进入下一步骤发送消息到设备

因为局域网通讯要求设备与APP处于同一个WiFi下,若网络环境变化,如切换手机WiFi,或者设备掉线时,直连的状态需要发生改变,所以建议在设备页通过定时器定时更新局域网状态,具体可参照ac-service-android-demo的实现

//定时更新设备当前的局域网状态
public void refreshDeviceStatus() {
    //当设备掉线或网络环境不稳定导致获取局域网显示状态不准确时,需要手动刷新设备列表与局域网状态
    AC.findLocalDevice(AC.FIND_DEVICE_DEFAULT_TIMEOUT, new PayloadCallback<List<ACDeviceFind>>() {
        @Override
        public void success(List<ACDeviceFind> deviceFinds) {
            //成功获取局域网内的设备列表,通过匹配已有的设备列表更新界面上的局域网状态,以下为示例参考代码:
            //局域网状态是否发生改变,是否需要更新界面
            boolean isRefresh = false;
            //遍历当前用户绑定的所有设备列表
            for (ACUserDevice device : adapter.deviceList) {
                //判断当前设备是否局域网本地在线
                boolean isLocalOnline = false;
                //遍历当前发现的局域网在线列表
                for (ACDeviceFind deviceFind : deviceFinds) {
                    //通过设备的物理Id进行匹配,若当前设备在发现的局域网列表中,则置为局域网在线
                    if (device.getPhysicalDeviceId().equals(deviceFind.getPhysicalDeviceId())) {
                        isLocalOnline = true;
                    }
                }
                if (isLocalOnline) {
                    //当前设备由不在线更新为局域网在线
                    if (device.getStatus() == ACUserDevice.OFFLINE) {
                        device.setStatus(ACUserDevice.LOCAL_ONLINE);
                        isRefresh = true;
                    //当前设备由云端在线更新为云端局域网同时在线
                    } else if (device.getStatus() == ACUserDevice.NETWORK_ONLINE) {
                        device.setStatus(ACUserDevice.BOTH_ONLINE);
                        isRefresh = true;
                    }
                } else {
                    //当前设备由局域网在线更新为不在线
                    if (device.getStatus() == ACUserDevice.LOCAL_ONLINE) {
                        device.setStatus(ACUserDevice.OFFLINE);
                        isRefresh = true;
                    //当前设备由云端局域网同时在线更新为云端在线
                    } else if (device.getStatus() == ACUserDevice.BOTH_ONLINE) {
                        device.setStatus(ACUserDevice.NETWORK_ONLINE);
                        isRefresh = true;
                    }
                }
            }
            //局域网状态需要发生改变,更新列表界面
            if (isRefresh)
                adapter.notifyDataSetChanged();
        }

        @Override
        public void error(ACException e) {
            //发生IO错误,将所有设备的局域网状态置为不在线,以下为示例参考代码:
            //局域网状态可能发生改变,判断是否需要更新界面上的列表显示
            boolean isRefresh = false;
            for (ACUserDevice device : adapter.deviceList) {
                //没有设备当前局域网在线,所以把所有当前显示局域网在线的设备状态重置
                if (device.getStatus() == ACUserDevice.LOCAL_ONLINE) {
                    device.setStatus(ACUserDevice.OFFLINE);
                    isRefresh = true;
                } else if (device.getStatus() == ACUserDevice.BOTH_ONLINE) {
                    device.setStatus(ACUserDevice.NETWORK_ONLINE);
                    isRefresh = true;
                }
            }
            //局域网状态需要发生改变,更新列表界面
            if (isRefresh)
                adapter.notifyDataSetChanged();
        }
    });
}

发送消息到设备

以msgCode为64,payload为[1,0,0,0]为设备开灯协议举例

ACDeviceMsg deviceMsg = new ACDeviceMsg(64, new byte[]{1,0,0,0}, "open light");

//设置局域网通讯加密方式,此处设置静态加密,不设置则默认为动态加密
deviceMsg.setSecurityMode(ACDeviceSecurityMode.DYNAMIC_ENCRYPTED);

//AC.LOCAL_FIRST代表优先走局域网,局域网不通的情况下再走云端
bindMgr.sendToDeviceWithOption(subDomain, physicalDeviceId, deviceMsg, AC.LOCAL_FIRST, new PayloadCallback<ACDeviceMsg>() {
    @Override
    public void success(ACDeviceMsg deviceMsg) {
        //成功发送设备指令并收到设备响应
    }

    @Override
    public void error(ACException e) {
        //网络错误或其他,根据e.getErrorCode()做不同的提示或处理
    }
});