package com.hdl.sdk.link.zigbee; import com.google.gson.JsonArray; import com.google.gson.JsonObject; import com.google.gson.reflect.TypeToken; import com.hdl.sdk.link.common.event.EventListener; import com.hdl.sdk.link.common.exception.HDLLinkCode; import com.hdl.sdk.link.common.exception.HDLLinkException; import com.hdl.sdk.link.common.utils.LogUtils; import com.hdl.sdk.link.common.utils.ThreadToolUtils; import com.hdl.sdk.link.common.utils.gson.GsonConvert; import com.hdl.sdk.link.core.bean.ZigbeeResponse; import com.hdl.sdk.link.core.callback.ZigbeeCallBack; import com.hdl.sdk.link.core.connect.HDLZigbeeConnect; import com.hdl.sdk.link.zigbee.bean.PanelVibrationBean; import com.hdl.sdk.link.zigbee.bean.TimeDataBean; import com.hdl.sdk.link.zigbee.bean.ZBBaseSendBean; import com.hdl.sdk.link.zigbee.bean.ZBDeviceBean; import com.hdl.sdk.link.zigbee.callback.ZBDeviceListCallBack; import com.hdl.sdk.link.zigbee.config.ZigBee; import java.util.ArrayList; import java.util.List; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicInteger; /** * Created by jlchen on 12/16/21. * ZigBee原生协议相关 */ public class HDLLinkLocalZigBee { private static final String TAG = "HDLLinkLocalZigBee"; private static final String Cluster_ID = "Cluster_ID"; private static final String Command = "Command"; //instance private volatile static HDLLinkLocalZigBee instance; //getInstance public static synchronized HDLLinkLocalZigBee getInstance() { if (instance == null) { synchronized (HDLLinkLocalZigBee.class) { if (instance == null) { instance = new HDLLinkLocalZigBee(); } } } return instance; } /** * 发送消息给网关 * * @param gatewayOid * @param replyTopic 回复主题 * @param payload * @param callback */ public void sendData(String gatewayOid, String replyTopic, String payload, ZigbeeCallBack callback) { //注意 gatewayId是填要连接的网关,可以是主网关的Id,也可以是从网关的Id HDLZigbeeConnect.getInstance().Send(gatewayOid, replyTopic, payload, callback); } /********************************网关相关********************************/ /** * 发送指令到网关进行定位(网关LED闪烁识别) * * @param gatewayOid 网关id * @param callback */ public void locationGateway(String gatewayOid, ZigbeeCallBack callback) { JsonObject jObject = new JsonObject(); jObject.addProperty(Cluster_ID, 0); jObject.addProperty(Command, ZigBee.CommandType.GwLinuxLocate); sendData(gatewayOid, ZigBee.ReplyTopicType.GwLinuxLocate, jObject.toString(), callback); } /********************************设备相关********************************/ /** * 设置设备入网方式 * 设置1次之后保持设置的方式 * 重启恢复默认方式 * * @param gatewayOid 网关oid * @param deviceJoiningMode 0:默认;1:新方式(快速入网) */ public void setDeviceJoiningMode(String gatewayOid, int deviceJoiningMode, ZigbeeCallBack callback) { JsonObject jObject = new JsonObject(); jObject.addProperty("Cluster_ID", 0); jObject.addProperty("Command", 180); JsonObject data = new JsonObject(); data.addProperty("mode", deviceJoiningMode); jObject.add("Data", data); sendData(gatewayOid, ZigBee.ReplyTopicType.SetJoiningModeRespon, jObject.toString(), callback); } /** * 设置搜索新设备开启或者关闭 * * @param gatewayOid 网关oid * @param time 开启搜索设备的时间(单位:秒) 0:关闭搜索 255:一直开启 * @param callback */ public void sendSearchDevice(String gatewayOid, int time, ZigbeeCallBack callback) { ZBBaseSendBean baseSendBean = new ZBBaseSendBean(0, ZigBee.CommandType.SearchNewDevice, new TimeDataBean(time)); sendData(gatewayOid, ZigBee.ReplyTopicType.SearchNewDevice, GsonConvert.getGson().toJson(baseSendBean), callback); } /** * 搜索设备 * * @param gatewayOid * @param callback */ public void startSearchDevice(String gatewayOid, ZigbeeCallBack callback) { sendSearchDevice(gatewayOid, 255, callback); } /** * 暂停搜索设备 * * @param gatewayOid * @param callback */ public void stopSearchDevice(String gatewayOid, ZigbeeCallBack callback) { sendSearchDevice(gatewayOid, 0, callback); } /** * 定位设备 闪烁5秒 * 该条指令设备不回复,所有没callBack * 发送该命令,设备指示灯将闪烁。cluster 列表中支持 Identify 功能,即包含 cluster=3 的设 备可以使用该接口。 * * @param gatewayOid * @param deviceAddr 单播时为设备节点的 mac 地 址 类型是字符(16 个字符) 组播时为组 id 类型是数值 广播时为 null * @param epoint 设备端口号 和 mac 地址共同标识唯一的 zigbee 设备 数值范围 0-255 */ public void locationDevice(String gatewayOid, String deviceAddr, int epoint) { JsonObject jObject = new JsonObject(); jObject.addProperty("DeviceAddr", deviceAddr); jObject.addProperty("Epoint", epoint); jObject.addProperty(Cluster_ID, 3); jObject.addProperty(Command, 0); jObject.addProperty("SendMode", 2);//发送模式 组播: 1 单播: 2 广播: 15 JsonObject data = new JsonObject(); data.addProperty("Time", 5);//闪烁时间(秒) jObject.add("Data", data); sendData(gatewayOid, ZigBee.ReplyTopicType.Identify, jObject.toString(), null); } /** * 设备列表-设备删除(使设备离网) * 通过该指令可以使设备脱离当前 zigbee 网络,该设备信息也将会在网关上删除。 * 注意:将会删除同一 MAC 地址下所有不同端口的设备,如一个 2 路按键设备,它具有两个 按键,在设备列表上这两个按键具有相同的 mac,但端口号是不同的。使用该指令让任意 一个按键离网,另一个也会离网。 * * @param gatewayOid * @param compelClear 0:不强制清除。需要节点设备 反馈离网确认信息后才能删除设 备信息。 * 1:强制清除。不需要节点设备 反馈离网确认信息,直接删除设 备信息。 */ public void removeDevice(String gatewayOid, int compelClear, List deviceAddrList, ZigbeeCallBack callback) { if (deviceAddrList == null || deviceAddrList.size() == 0) { LogUtils.e("没有要删除到数据"); return; } JsonArray addrList = new JsonArray(); for (String addr : deviceAddrList) { JsonObject aObject = new JsonObject(); aObject.addProperty("DeviceAddr", addr); addrList.add(aObject); } JsonObject jObject = new JsonObject(); jObject.addProperty(Cluster_ID, 0); jObject.addProperty(Command, ZigBee.CommandType.RemoveDevice); JsonObject data = new JsonObject(); data.addProperty("CompelClear", compelClear); data.add("DeviceAddrList", addrList); jObject.add("Data", data); sendData(gatewayOid, ZigBee.ReplyTopicType.RemoveDevice, jObject.toString(), callback); } /** * 设备列表-获取本地网关设备 EPDeviceId 列表 * (网关一个一个返回端点设备详细信息) * * @param gatewayOid * @param callback */ public void getZigBeeDeviceList(String gatewayOid, ZBDeviceListCallBack callback) { JsonObject jObject = new JsonObject(); jObject.addProperty(Cluster_ID, 0); jObject.addProperty(Command, ZigBee.CommandType.GetDeviceInfo); //是否获取列表成功标记 AtomicBoolean isSuccess = new AtomicBoolean(false); //搜索5s等待标记 AtomicInteger timeOut = new AtomicInteger(0); //设备列表 List list = new ArrayList<>(); //注册监听事件 EventListener zigBeeEventListener = new EventListener() { @Override public synchronized void onMessage(Object msg) { if (msg instanceof ZigbeeResponse) { ZigbeeResponse zigbeeResponse = (ZigbeeResponse) msg; try { if (zigbeeResponse.getTopic().equals(ZigBee.ReplyTopicType.GetDeviceInfo)) { final ZBDeviceBean bean = GsonConvert.getGson().fromJson(zigbeeResponse.getData(), new TypeToken() { }.getType()); if (bean == null) { LogUtils.i("获取到的Zigbee设备列表反系列化失败:\r\n" + zigbeeResponse.getData()); } else { bean.setGatewayMac(zigbeeResponse.getOid()); LogUtils.i("zigbeeResponse ZBDeviceBean:" + bean.getDeviceAddr() + ":" + bean.getEpoint()); if (bean.getData().getTotalNum() == 0) { //回复空的设备列表 list.clear(); if (callback != null) { callback.onSuccess(list); } HDLZigbeeConnect.getInstance().removeListener(this); isSuccess.set(true); } else { list.add(bean); // LogUtils.i("getZigBeeDeviceList 重置时间为0"); timeOut.set(0);//收到一个设备则重置时间 //如果当前序号等于总数,代表是最后一个设备了,回调成功 if (bean.getData().getDeviceNum() == bean.getData().getTotalNum()) { if (callback != null) { callback.onSuccess(list); } HDLZigbeeConnect.getInstance().removeListener(this); isSuccess.set(true); LogUtils.i("getZigBeeDeviceList读取完成"); timeOut.set(6);//计时结束标记 } } } } } catch (Exception e) { LogUtils.e(TAG, "接收的数据内容:" + zigbeeResponse.getData() + " 异常信息:" + e.getMessage()); } } } }; HDLZigbeeConnect.getInstance().registerListener(zigBeeEventListener); //发送数据 sendData(gatewayOid, "", jObject.toString(), null); //5秒后还没有接收到数据,就回调失败 ScheduledExecutorService scheduledExecutorService = ThreadToolUtils.getInstance().newScheduledThreadPool(1); scheduledExecutorService.schedule(new Runnable() { @Override public void run() { while (timeOut.get() < 5) { try { timeOut.set(timeOut.get() + 1); LogUtils.i("getZigBeeDeviceList 计时:" + timeOut.get() + "s"); Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } } //移除监听事件 HDLZigbeeConnect.getInstance().removeListener(zigBeeEventListener); if (!isSuccess.get()) { if (null != callback) { callback.onError(HDLLinkException.getErrorWithCode(HDLLinkCode.HDL_GET_Zigbee_DEVICE_LIST_FAILURE_ERROR)); } } LogUtils.i("getZigBeeDeviceList 5s倒计时结束"); scheduledExecutorService.shutdownNow(); } }, 1, TimeUnit.SECONDS); } /** * 修改设备端口(按键)名称 * * @param gatewayOid * @param deviceAddr * @param epoint * @param deviceName * @param callback */ public void renameDeviceName(String gatewayOid, String deviceAddr, int epoint, String deviceName, ZigbeeCallBack callback) { JsonObject jObject = new JsonObject(); jObject.addProperty("DeviceAddr", deviceAddr); jObject.addProperty("Epoint", epoint); jObject.addProperty(Cluster_ID, 0); jObject.addProperty(Command, ZigBee.CommandType.DeviceRename); JsonObject data = new JsonObject(); data.addProperty("DeviceName", deviceName); jObject.add("Data", data); sendData(gatewayOid, ZigBee.ReplyTopicType.DeviceRename, jObject.toString(), callback); } /** * 对端点回路设备功能类型进行设置如 onoffoutput 回路,进行设置为普通开关或插座或灯光 * * @param gatewayOid * @param deviceAddr 设备节点的 mac 地址 (最大 16 个字符) * @param epoint 设备端口号 和 mac 地址共同标识唯一的 zigbee 设备 数值范围 0-255 * @param functionType 用来表示实际回路用于什么 功能,可扩展 0-继电器接普通开关 1-继电器接灯光 2-插座 * @param callback */ public void setEPDeviceFunctionType(String gatewayOid, String deviceAddr, int epoint, int functionType, ZigbeeCallBack callback) { JsonObject jObject = new JsonObject(); jObject.addProperty("DeviceAddr", deviceAddr); jObject.addProperty("Epoint", epoint); jObject.addProperty(Cluster_ID, 0); jObject.addProperty(Command, ZigBee.CommandType.SetEPDeviceFunctionType); JsonObject data = new JsonObject(); data.addProperty("FunctionType", functionType); jObject.add("Data", data); sendData(gatewayOid, ZigBee.ReplyTopicType.SetEPDeviceFunctionType, jObject.toString(), callback); } /********面板震动功能********/ /** * 获取简约面板震动功能的信息 */ public void getPanelVibrationInfo(String gatewayOid, String deviceAddr, int epoint, String passData, ZigbeeCallBack callback) { // //组装发送数据 // JsonObject jObject = new JsonObject(); // jObject.addProperty("DeviceAddr", deviceAddr); // jObject.addProperty("Epoint", 200); // jObject.addProperty(Cluster_ID, 64513); // jObject.addProperty(Command, 0); // JsonObject data = new JsonObject(); // data.addProperty("PassData", "050108110101"); // jObject.add("Data", data); // sendClientDataPassthrough(device, jObject.toString(), "0802", 18, 6, null, callback); // sendClientDataPassthrough(gatewayOid,deviceAddr,epoint,passData,callback); } /** * 设置简约面板震动功能的信息 * * @param device * @param deviceAddr * @param panelVibrationBean * @param callback */ public void setPanelVibrationInfo(String device, String deviceAddr, PanelVibrationBean panelVibrationBean, ZigbeeCallBack callback) { // String passData = "0800081104"; // passData += panelVibrationBean.getIsEnable() ? "01" : "00"; //// passData += Convert.ToString(panelVibrationBean.getStrength(), 16).PadLeft(2, '0'); //// String time = Convert.ToString(panelVibrationBean.getTime(), 16).PadLeft(4, '0'); //// //低位在前,高位在后 //// passData += time.Substring(2, 2); //// passData += time.Substring(0, 2); // //// var jObject = new Newtonsoft.Json.Linq.JObject { { "DeviceAddr", DeviceAddr }, { "Epoint", 200 }, { "Cluster_ID", 64513 }, { "Command", 0 } }; //// var data = new Newtonsoft.Json.Linq.JObject { { "PassData", passData } }; //// jObject.Add("Data", data); // // JsonObject jObject = new JsonObject(); // jObject.addProperty("DeviceAddr", deviceAddr); // jObject.addProperty("Epoint", 200); // jObject.addProperty(Cluster_ID, 64513); // jObject.addProperty(Command, 0); // JsonObject data = new JsonObject(); // data.addProperty("PassData", passData); // jObject.add("Data", data); // // sendClientDataPassthrough(device, jObject.toString(), "0002", 16, 6, null, callback); } /** * 开关控制(仅用于cluster=6的设备) 设备支持cluster=6的设备才能调用该接口 * * @param command 0:关闭 1:打开 2:取反 */ public void switchControl(String gatewayOid, int command, ZBDeviceBean deviceBean) { JsonObject jObject = new JsonObject(); jObject.addProperty("DeviceAddr", deviceBean.getDeviceAddr()); jObject.addProperty("Epoint", deviceBean.getEpoint()); jObject.addProperty(Cluster_ID, 6); jObject.addProperty(Command, command); jObject.addProperty("SendMode", 2); sendData(gatewayOid, "", jObject.toString(), null); } }