一,简介
蓝牙4.0以下称为传统蓝牙,4.0以上是低功耗蓝牙,5.0开始主打物联网
5.0协议蓝牙最重要的技术就是Mesh组网,实现1对多,多对多的无线通信。即从点对点传输发展为网络拓扑结构,主要领域如灯光控制等,可以同时控制一组内的多个设备。
如下模型,把灯具分组,就可以同时控制一组或者多组内的多台设备
二 蓝牙组网步骤
2.1 扫描,还是用BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter();
/**
* Start scanning for Bluetooth devices.
*
* @param filterUuid UUID to filter scan results with
*/
public void startScan(final UUID filterUuid, boolean auto) {
mFilterUuid = filterUuid;
Log.e(TAG, "mScannerStateLiveData: 6" );
if (mScannerStateLiveData.isScanning()) {
return;
}
if (mFilterUuid.equals(BleMeshManager.MESH_PROXY_UUID)) {
final MeshNetwork network = mMeshManagerApi.getMeshNetwork();
if (network != null) {
if (!network.getNetKeys().isEmpty()) {
mNetworkId = mMeshManagerApi.generateNetworkId(network.getNetKeys().get(0).getKey());
}
}
}
Log.e(TAG, "mScannerStateLiveData: 7" );
mScannerStateLiveData.scanningStarted();
//Scanning settings
final ScanSettings settings = new ScanSettings.Builder()
.setScanMode(ScanSettings.SCAN_MODE_LOW_LATENCY)
// Refresh the devices list every second
.setReportDelay(0)
// Hardware filtering has some issues on selected devices
.setUseHardwareFilteringIfSupported(false)
// Samsung S6 and S6 Edge report equal value of RSSI for all devices. In this app we ignore the RSSI.
/*.setUseHardwareBatchingIfSupported(false)*/
.build();
//Let's use the filter to scan only for unprovisioned mesh nodes.
final List<ScanFilter> filters = new ArrayList<>();
filters.add(new ScanFilter.Builder().setServiceUuid(new ParcelUuid((filterUuid))).build());
final BluetoothLeScannerCompat scanner = BluetoothLeScannerCompat.getScanner();
Log.e(TAG, "startScan: 开始扫描" );
scanner.startScan(filters, settings, mScanCallbacks);
}
2.2 扫描到设备后,连接并获取地址
@SuppressLint("RestrictedApi")
private void connect(final ExtendedBluetoothDevice extendedBluetoothDevice) {
/**
* 蓝牙 --根据UUID中的型号ID
* DMX --根据品牌-系列-模式
* 2.4G --单色温,双色温,全彩
*/
String uuid = ((UnprovisionedBeacon) selectedBluetoothDevice.getBeacon()).getUuid().toString().replace("-", "");
String[] stringArray = OrderUtils.hexStringToStringArray(uuid);
//型号byte 10 ~ 12 表示型号
String modelFileName = stringArray[10] + stringArray[11] + stringArray[12];
Log.e(TAG, "modelFileName: " + modelFileName);
dataJson = dataJsonCommonDaoUtils.queryBykey(modelFileName);
if (dataJson == null) {
ConfirmDialog confirmDialog = new ConfirmDialog(mContext, R.style.dialog, "没有该型号的配置文件:\n" + modelFileName, getResources().getString(R.string.account_confirm));
confirmDialog.show();
hideCustomProgress();
cancleHandlerProvisioning();
return;
}
if (fromType == 1 || fromType == 2) {
String json = dataJson.getDataJson();
if (!json.contains("FUNCTION")) {
ConfirmDialog confirmDialog = new ConfirmDialog(mContext, R.style.dialog, "请添加控制盒类型的配置文件\n", getResources().getString(R.string.account_confirm));
confirmDialog.show();
hideCustomProgress();
cancleHandlerProvisioning();
return;
}
}
nextChBlue = getNextCHBlue();
if (nextChBlue == -1) {//如果nextChBlue==-1,说明蓝牙地址1-512已将占完,直接return;
cancleHandlerProvisioning();
return;
}
showCustomProgress(getResources().getString(R.string.ble_provisioning) + (addIndex + 1) + "/" + mSelectCommonList.size());
scannerViewModel.getScannerRepository().stopScan();
provisioningViewModel.connect(this, extendedBluetoothDevice, false);
//监听连接状态
provisioningViewModel.getConnectionState().observe(this, new Observer<String>() {
@Override
public void onChanged(@Nullable String s) {
Log.e(TAG, "getConnectionState: " + s);
}
});
//监听是否连接
provisioningViewModel.isConnected().observe(this, connected -> {
final boolean isComplete = provisioningViewModel.isProvisioningComplete();
if (isComplete) {
return;
}
if (connected != null) {
if (connected) {
Log.e(TAG, "isConnected: " + "连接成功");
handlerProvisioning.sendEmptyMessageDelayed(1, 4000);
} else {
Log.e(TAG, "isConnected: " + "连接失败");
}
} else {
Log.e(TAG, "isConnected: " + "未连接");
}
});
//监听设备信息
provisioningViewModel.isDeviceReady().observe(this, deviceReady -> {
if (provisioningViewModel.getBleMeshManager().isDeviceReady()) {
Log.e(TAG, "isDeviceReady:");
final boolean isComplete = provisioningViewModel.isProvisioningComplete();
if (isComplete) {
setupProvisionerStateObservers();
return;
}
}
});
//监听重连
provisioningViewModel.isReconnecting().observe(this, isReconnecting -> {
Log.e(TAG, "isReconnecting:");
if (isReconnecting != null && isReconnecting) {
provisioningViewModel.getUnprovisionedMeshNode().removeObservers(this);
} else {
setResultIntent();
}
});
//监听key
provisioningViewModel.getNetworkLiveData().observe(this, meshNetworkLiveData -> {
final ApplicationKey applicationKey = meshNetworkLiveData.getSelectedAppKey();
Log.e(TAG, "getNetworkLiveData:Key:" + MeshParserUtils.bytesToHex(applicationKey.getKey(), false));
Log.e(TAG, "getNetworkLiveData: Address:" + getString(R.string.ble_hex_format, String.format(Locale.US, "%04X", meshNetworkLiveData.getMeshNetwork().getUnicastAddress())));
// 获取已选择的app key
//appKeyView.setText(MeshParserUtils.bytesToHex(applicationKey.getKey(), false));
// Log.e("TAG", "onCreate: " + MeshParserUtils.bytesToHex(applicationKey.getKey(), false) );
// unicastAddressView.setText(getString(R.string.ble_hex_format,
// String.format(Locale.US, "%04X", meshNetworkLiveData.getMeshNetwork().getUnicastAddress())));
});
//监听设备识别
provisioningViewModel.getUnprovisionedMeshNode().observe(this, meshNode -> {
Log.e(TAG, "getUnprovisionedMeshNode:meshNode=" + (meshNode == null));
if (meshNode != null) {
final ProvisioningCapabilities capabilities = meshNode.getProvisioningCapabilities();
Log.e(TAG, "getUnprovisionedMeshNode:capabilities=" + (capabilities == null));
if (capabilities != null) {
final MeshNetwork network = provisioningViewModel.getNetworkLiveData().getMeshNetwork();
if (network != null) {
try {
final int elementCount = capabilities.getNumberOfElements();
final Provisioner provisioner = network.getSelectedProvisioner();
final int unicast = network.nextAvailableUnicastAddress(elementCount, provisioner);
network.assignUnicastAddress(unicast);
} catch (IllegalArgumentException ex) {
ToastUtil.showToast(mContext, ex.getMessage());
}
}
}
}
});
}
2.3 选择模型和节点
//选择节点
public ProvisionedMeshNode setSelectNode(String controlAddress) {
MeshNetwork network = Consts.sharedViewModel.getNetworkLiveData().getMeshNetwork();
ProvisionedMeshNode node = network.getNode(Integer.parseInt(controlAddress));
if (node != null) {
Consts.sharedViewModel.setSelectedMeshNode(node);
mElements.clear();
mElements.addAll(node.getElements().values());
tag2:
for (int i = 0; i < mElements.size(); i++) {
List<MeshModel> models = new ArrayList<>(mElements.get(i).getMeshModels().values());
tag1:
for (int j = 0; j < models.size(); j++) {
if (models.get(j) instanceof VendorModel) {
Consts.modelConfigurationViewModel.setSelectedElement(mElements.get(i));
Consts.modelConfigurationViewModel.setSelectedModel(models.get(j));
break tag2;
}
}
}
}
return node;
}
2.4 配置入网
//入网
@SuppressLint("RestrictedApi")
public void provisionClick() {
final UnprovisionedMeshNode node = provisioningViewModel.getUnprovisionedMeshNode().getValue();
Log.e(TAG, "isConnected: ((((((((((((( " + (node == null));
if (node == null) {
Log.e(TAG, "isConnected: " + provisioningViewModel.getNetworkLiveData().getNodeName());
provisioningViewModel.getNrfMeshRepository().identifyNode(selectedBluetoothDevice);
return;
}
//配置入网
if (node.getProvisioningCapabilities() != null) {
Log.e(TAG, "onCreate: " + (node.getProvisioningCapabilities().getAvailableOOBTypes().size() == 1 &&
node.getProvisioningCapabilities().getAvailableOOBTypes().get(0) == AuthenticationOOBMethods.NO_OOB_AUTHENTICATION));
if (node.getProvisioningCapabilities().getAvailableOOBTypes().size() == 1 &&
node.getProvisioningCapabilities().getAvailableOOBTypes().get(0) == AuthenticationOOBMethods.NO_OOB_AUTHENTICATION) {
onNoOOBSelected();
} else {
// final DialogFragmentSelectOOBType fragmentSelectOOBType = DialogFragmentSelectOOBType.newInstance(meshNode.getProvisioningCapabilities());
// fragmentSelectOOBType.show(getSupportFragmentManager(), null);
}
}
}
@SuppressLint("RestrictedApi")
public void setupProvisionerStateObservers() {
provisioningViewModel.getProvisioningStatus().observe(this, provisioningStateLiveData -> {
if (provisioningStateLiveData != null) {
final ProvisionerProgress provisionerProgress = provisioningStateLiveData.getProvisionerProgress();
provisioningStateLiveData.getStateList();
if (provisionerProgress != null) {
final ProvisionerStates state = provisionerProgress.getState();
Log.e(TAG, "setupProvisionerStateObservers: state:" + state);
switch (state) {
case PROVISIONING_CAPABILITIES:
Log.e("TAG", "PROVISIONING_CAPABILITIES: " + provisioningViewModel.getNetworkLiveData().getMeshNetwork().getUnicastAddress());
String address = String.format(Locale.US, "%04X", provisioningViewModel.getNetworkLiveData().getMeshNetwork().getUnicastAddress());
addressMap.put(selectedBluetoothDevice.getAddress(), address);
break;
case PROVISIONING_FAILED://失败
// if (getSupportFragmentManager().findFragmentByTag(DIALOG_FRAGMENT_PROVISIONING_FAILED) == null) {
// final String statusMessage = ProvisioningFailedState.parseProvisioningFailure(getApplicationContext(), provisionerProgress.getStatusReceived());
// DialogFragmentProvisioningFailedError message = DialogFragmentProvisioningFailedError.newInstance(getString(R.string.ble_title_error_provisioning_failed), statusMessage);
// message.show(getSupportFragmentManager(), DIALOG_FRAGMENT_PROVISIONING_FAILED);
// }
break;
case PROVISIONING_AUTHENTICATION_STATIC_OOB_WAITING:
case PROVISIONING_AUTHENTICATION_OUTPUT_OOB_WAITING:
case PROVISIONING_AUTHENTICATION_INPUT_OOB_WAITING:
// if (getSupportFragmentManager().findFragmentByTag(DIALOG_FRAGMENT_AUTH_INPUT_TAG) == null) {
// DialogFragmentAuthenticationInput dialogFragmentAuthenticationInput = DialogFragmentAuthenticationInput.
// newInstance(mViewModel.getUnprovisionedMeshNode().getValue());
// dialogFragmentAuthenticationInput.show(getSupportFragmentManager(), DIALOG_FRAGMENT_AUTH_INPUT_TAG);
// }
break;
case PROVISIONING_AUTHENTICATION_INPUT_ENTERED:
// final DialogFragmentAuthenticationInput fragment = (DialogFragmentAuthenticationInput) getSupportFragmentManager().
// findFragmentByTag(DIALOG_FRAGMENT_AUTH_INPUT_TAG);
// if (fragment != null) {
// fragment.dismiss();
// }
break;
case PROVISIONING_COMPLETE:
case NETWORK_TRANSMIT_STATUS_RECEIVED:
// if (getSupportFragmentManager().findFragmentByTag(DIALOG_FRAGMENT_CONFIGURATION_STATUS) == null) {
// DialogFragmentConfigurationComplete fragmentConfigComplete = DialogFragmentConfigurationComplete.
// newInstance(getString(R.string.title_configuration_compete), getString(R.string.configuration_complete_summary));
// fragmentConfigComplete.show(getSupportFragmentManager(), DIALOG_FRAGMENT_CONFIGURATION_STATUS);
// }
Log.e(TAG, "setupProvisionerStateObservers: " + "PROVISIONING_COMPLETE");
handlerProvisioning.sendEmptyMessageDelayed(3, 3000);
break;
case PROVISIONER_UNASSIGNED:
setResultIntent();
break;
default:
break;
}
}
}
});
}
public void onNoOOBSelected() {
final UnprovisionedMeshNode node = provisioningViewModel.getUnprovisionedMeshNode().getValue();
if (node != null) {
try {
node.setNodeName(provisioningViewModel.getNetworkLiveData().getNodeName());
setupProvisionerStateObservers();
provisioningViewModel.getMeshManagerApi().startProvisioning(node);
} catch (IllegalArgumentException ex) {
ToastUtil.showToast(mContext, ex.getMessage());
}
}
}
2.5 发送消息
/**
* Send vendor model acknowledged message
*
* @param opcode opcode of the message
* @param parameters parameters of the message
*/
public void sendVendorModelMessage(final int opcode, final byte[] parameters, final boolean acknowledged) {
final Element element = Consts.modelConfigurationViewModel.getSelectedElement().getValue();
if (element != null) {
final VendorModel model = (VendorModel) Consts.modelConfigurationViewModel.getSelectedModel().getValue();
if (model != null) {
final int appKeyIndex = Consts.modelConfigurationViewModel.getMeshManagerApi().getMeshNetwork().getAppKey(0).getKeyIndex();
// final int appKeyIndex = model.getBoundAppKeyIndexes().get(0);
final ApplicationKey appKey = Consts.modelConfigurationViewModel.getNetworkLiveData().getMeshNetwork().getAppKey(appKeyIndex);
final MeshMessage message;
if (acknowledged) {
message = new VendorModelMessageAcked(appKey, model.getModelId(), model.getCompanyIdentifier(), opcode, parameters);
int address = element.getElementAddress();
if (lightEquipmentGroup != null && lightEquipmentGroup.getConnectMethod() == 0) {
address = Consts.sharedViewModel.getSelectedGroup().getValue().getAddress();
}
sendMessage(address, message);
} else {
message = new VendorModelMessageUnacked(appKey, model.getModelId(), model.getCompanyIdentifier(), opcode, parameters);
int address = element.getElementAddress();
if (lightEquipmentGroup != null && lightEquipmentGroup.getConnectMethod() == 0) {
address = Consts.sharedViewModel.getSelectedGroup().getValue().getAddress();
}
sendMessage(address, message);
}
}
}
}
protected void sendMessage(final int address, @NonNull final MeshMessage meshMessage) {
try {
Log.e(TAG, "sendMessage: " + checkConnectivity());
if (!checkConnectivity())
return;
Consts.modelConfigurationViewModel.getMeshManagerApi().createMeshPdu(address, meshMessage);
} catch (IllegalArgumentException ex) {
ToastUtil.showToast(mContext, getString(R.string.ble_title_error));
}
}
2.6 订阅网络群组
//订阅网络群组
public void subscribe() {
final ProvisionedMeshNode meshNode = modelConfigurationViewModel.getSelectedMeshNode().getValue();
Log.e(TAG, "meshNodeIsnull: " + (meshNode == null));
if (meshNode != null) {
final Element element = modelConfigurationViewModel.getSelectedElement().getValue();
Log.e(TAG, "elementIsnull: " + (element == null));
if (element != null) {
final int elementAddress = element.getElementAddress();
final MeshModel model = modelConfigurationViewModel.getSelectedModel().getValue();
Log.e(TAG, "modelIsnull: " + (model == null));
if (model != null) {
final int modelIdentifier = model.getModelId();
final MeshMessage configModelSubscriptionAdd;
Log.e(TAG, "group.getAddressLabel(): " + (group.getAddressLabel() == null));
if (group.getAddressLabel() == null) {
configModelSubscriptionAdd = new ConfigModelSubscriptionAdd(elementAddress, group.getAddress(), modelIdentifier);
} else {
configModelSubscriptionAdd = new ConfigModelSubscriptionVirtualAddressAdd(elementAddress, group.getAddressLabel(), modelIdentifier);
}
sendMessage(meshNode.getUnicastAddress(), configModelSubscriptionAdd);
handlerCheckIsConnectIndex=addIndex;
handlerCheckIsConnect.removeCallbacksAndMessages(0);
handlerCheckIsConnect.sendEmptyMessageDelayed(1,3000);
}
}
}
}
2.7 接收消息
public void onMeshMessageReceived(final int src, @NonNull final MeshMessage meshMessage) {
final ProvisionedMeshNode node = mMeshNetwork.getNode(src);
if (node != null)
if (meshMessage instanceof ProxyConfigFilterStatus) {
mProvisionedMeshNode = node;
setSelectedMeshNode(node);
final ProxyConfigFilterStatus status = (ProxyConfigFilterStatus) meshMessage;
final int unicastAddress = status.getSrc();
Log.v(TAG, "Proxy configuration source: " + MeshAddress.formatAddress(status.getSrc(), false));
mConnectedProxyAddress.postValue(unicastAddress);
mMeshMessageLiveData.postValue(status);
} else if (meshMessage instanceof ConfigCompositionDataStatus) {
final ConfigCompositionDataStatus status = (ConfigCompositionDataStatus) meshMessage;
if (mSetupProvisionedNode) {
mIsCompositionDataReceived = true;
mProvisionedMeshNodeLiveData.postValue(node);
mConnectedProxyAddress.postValue(node.getUnicastAddress());
mProvisioningStateLiveData.onMeshNodeStateUpdated(ProvisionerStates.COMPOSITION_DATA_STATUS_RECEIVED);
mHandler.postDelayed(() -> {
Log.e(TAG, "onMeshMessageReceived: 500" );
final ConfigDefaultTtlGet configDefaultTtlGet = new ConfigDefaultTtlGet();
Log.e(TAG, "onMeshMessageReceived: 3" );
mMeshManagerApi.createMeshPdu(node.getUnicastAddress(), configDefaultTtlGet);
//}, 500);
}, 0);
} else {
updateNode(node);
}
} else if (meshMessage instanceof ConfigDefaultTtlStatus) {
final ConfigDefaultTtlStatus status = (ConfigDefaultTtlStatus) meshMessage;
if (mSetupProvisionedNode) {
mIsDefaultTtlReceived = true;
mProvisionedMeshNodeLiveData.postValue(node);
mProvisioningStateLiveData.onMeshNodeStateUpdated(ProvisionerStates.DEFAULT_TTL_STATUS_RECEIVED);
mHandler.postDelayed(() -> {
Log.e(TAG, "onMeshMessageReceived: 1500" );
final ApplicationKey appKey = mMeshNetworkLiveData.getSelectedAppKey();
@SuppressLint("RestrictedApi") final int index = node.getAddedNetKeys().get(0).getIndex();
final NetworkKey networkKey = mMeshNetwork.getNetKeys().get(index);
final ConfigAppKeyAdd configAppKeyAdd = new ConfigAppKeyAdd(networkKey, appKey);
Log.e(TAG, "onMeshMessageReceived: 2" );
mMeshManagerApi.createMeshPdu(node.getUnicastAddress(), configAppKeyAdd);
//}, 1500);
}, 0);
} else {
updateNode(node);
mMeshMessageLiveData.postValue(status);
}
} else if (meshMessage instanceof ConfigAppKeyStatus) {
final ConfigAppKeyStatus status = (ConfigAppKeyStatus) meshMessage;
if (mSetupProvisionedNode) {
if (status.isSuccessful()) {
mIsAppKeyAddCompleted = true;
mProvisionedMeshNodeLiveData.postValue(node);
mProvisioningStateLiveData.onMeshNodeStateUpdated(ProvisionerStates.APP_KEY_STATUS_RECEIVED);
mHandler.postDelayed(() -> {
final ConfigNetworkTransmitSet networkTransmitSet = new ConfigNetworkTransmitSet(2, 1);
Log.e(TAG, "onMeshMessageReceived: 1" );
mMeshManagerApi.createMeshPdu(node.getUnicastAddress(), networkTransmitSet);
// }, 1500);
}, 0);
}
} else {
updateNode(node);
mMeshMessageLiveData.postValue(status);
}
} else if (meshMessage instanceof ConfigNetworkTransmitStatus) {
if (mSetupProvisionedNode) {
mSetupProvisionedNode = false;
mIsNetworkRetransmitSetCompleted = true;
mProvisioningStateLiveData.onMeshNodeStateUpdated(ProvisionerStates.NETWORK_TRANSMIT_STATUS_RECEIVED);
} else {
updateNode(node);
final ConfigNetworkTransmitStatus status = (ConfigNetworkTransmitStatus) meshMessage;
mMeshMessageLiveData.postValue(status);
}
} else if (meshMessage instanceof ConfigModelAppStatus) {
if (updateNode(node)) {
final ConfigModelAppStatus status = (ConfigModelAppStatus) meshMessage;
final Element element = node.getElements().get(status.getElementAddress());
if (node.getElements().containsKey(status.getElementAddress())) {
mSelectedElement.postValue(element);
final MeshModel model = element.getMeshModels().get(status.getModelIdentifier());
mSelectedModel.postValue(model);
}
}
} else if (meshMessage instanceof ConfigModelPublicationStatus) {
if (updateNode(node)) {
final ConfigModelPublicationStatus status = (ConfigModelPublicationStatus) meshMessage;
if (node.getElements().containsKey(status.getElementAddress())) {
final Element element = node.getElements().get(status.getElementAddress());
mSelectedElement.postValue(element);
final MeshModel model = element.getMeshModels().get(status.getModelIdentifier());
Log.e(TAG, "onMeshMessageReceived: ****************************" );
mSelectedModel.postValue(model);
}
}
} else if (meshMessage instanceof ConfigModelSubscriptionStatus) {
if (updateNode(node)) {
final ConfigModelSubscriptionStatus status = (ConfigModelSubscriptionStatus) meshMessage;
if (node.getElements().containsKey(status.getElementAddress())) {
final Element element = node.getElements().get(status.getElementAddress());
mSelectedElement.postValue(element);
final MeshModel model = element.getMeshModels().get(status.getModelIdentifier());
mSelectedModel.postValue(model);
}
}
} else if (meshMessage instanceof ConfigNodeResetStatus) {
mBleMeshManager.setClearCacheRequired();
final ConfigNodeResetStatus status = (ConfigNodeResetStatus) meshMessage;
mExtendedMeshNode.postValue(null);
Log.e(TAG, "onMeshMessageReceived: 2" );
loadNodes();
mMeshMessageLiveData.postValue(status);
} else if (meshMessage instanceof ConfigRelayStatus) {
if (updateNode(node)) {
final ConfigRelayStatus status = (ConfigRelayStatus) meshMessage;
mMeshMessageLiveData.postValue(status);
}
} else if (meshMessage instanceof ConfigProxyStatus) {
if (updateNode(node)) {
final ConfigProxyStatus status = (ConfigProxyStatus) meshMessage;
mMeshMessageLiveData.postValue(status);
}
} else if (meshMessage instanceof GenericOnOffStatus) {
if (updateNode(node)) {
final GenericOnOffStatus status = (GenericOnOffStatus) meshMessage;
if (node.getElements().containsKey(status.getSrcAddress())) {
final Element element = node.getElements().get(status.getSrcAddress());
mSelectedElement.postValue(element);
final MeshModel model = element.getMeshModels().get((int) SigModelParser.GENERIC_ON_OFF_SERVER);
mSelectedModel.postValue(model);
}
}
} else if (meshMessage instanceof GenericLevelStatus) {
if (updateNode(node)) {
final GenericLevelStatus status = (GenericLevelStatus) meshMessage;
if (node.getElements().containsKey(status.getSrcAddress())) {
final Element element = node.getElements().get(status.getSrcAddress());
mSelectedElement.postValue(element);
final MeshModel model = element.getMeshModels().get((int) SigModelParser.GENERIC_LEVEL_SERVER);
mSelectedModel.postValue(model);
}
}
} else if (meshMessage instanceof VendorModelMessageStatus) {
if (updateNode(node)) {
final VendorModelMessageStatus status = (VendorModelMessageStatus) meshMessage;
if (node.getElements().containsKey(status.getSrcAddress())) {
final Element element = node.getElements().get(status.getSrcAddress());
mSelectedElement.postValue(element);
final MeshModel model = element.getMeshModels().get(status.getModelIdentifier());
mSelectedModel.postValue(model);
}
}
}
if (mMeshMessageLiveData.hasActiveObservers()) {
mMeshMessageLiveData.postValue(meshMessage);
}
//Refresh mesh network live data
mMeshNetworkLiveData.refresh(mMeshManagerApi.getMeshNetwork());
}
2.9 字节数据的转换,8位二进制一个字节
public static String hexStringFormatNormal1(CmdNormal cmdNormal) {
if (cmdNormal == null) {
return "";
}
/**
* 功能码8位二进制组成功能byte
* Bit[0] : 0-不需要从机返回信息 / 1-需要从机返回信息
* Bit[1] : 0-发送 / 1-返回
* Bit[3] : 0-快捷指令 / 1-常规指令
* Bit[4:3] : 0-蓝牙灯具 / 1-2.4G灯具 / 2-DMX灯具
* Bit[6:5] : 未使用,保持0
* Bit[7] : 0-独立一帧 / 1-多帧数据
*/
StringBuffer stringBufferFunction = new StringBuffer();
CmdFunction cmdFunction = cmdNormal.getCmdFunction();
stringBufferFunction.append(cmdFunction.getIsMultiFrame());//7位(0-独立一帧 / 1-多帧数据)
stringBufferFunction.append("0");//6位保持0
stringBufferFunction.append(cmdFunction.getIsSetting());//5 0-查询 / 1-设置
//stringBufferFunction.append("0");//3位保持0
stringBufferFunction.append(OrderUtils.numToBinary(cmdFunction.getIsDeviceType(),2));//3,4位(0-蓝牙灯具 / 1-2.4G灯具 / 2-DMX灯具)
stringBufferFunction.append(cmdFunction.getIsFunctionNormal());//2位 (0-快捷指令 / 1-常规指令)
stringBufferFunction.append(cmdFunction.getIsFunctionBack());//1位(0-发送 / 1-返回)
stringBufferFunction.append(cmdFunction.getIsMachineBack());//0位(0-不需要从机返回信息 / 1-需要从机返回信息)
//功能码二进制转10进制
int functionTen = Integer.parseInt(stringBufferFunction.toString(), 2);
StringBuffer stringBuffer = new StringBuffer();
stringBuffer.append(bytesToHexString(cmdNormal.getRollCode()));//滚码
stringBuffer.append(bytesToHexString(functionTen));//功能码
//地址码
int address=cmdNormal.getAddress();
stringBuffer.append(bytesToHexString(address>> 8 & 0xff));
stringBuffer.append(bytesToHexString(address& 0xff));
//当前帧
stringBuffer.append(bytesToHexString(cmdNormal.getCurrentFrame()));
//总帧
stringBuffer.append(bytesToHexString(cmdNormal.getTotalFrame()));
//数据模式
stringBuffer.append(bytesToHexString(cmdNormal.getModeType()));
CmdNormal.AllDataMode allDataMode=cmdNormal.getAllDataMode();
for(CmdCode cmdCode:allDataMode.getCmdCodeList()){
if(cmdCode.getLenth()==1){
stringBuffer.append(bytesToHexString(cmdCode.getValue()));
}else if(cmdCode.getLenth()==2){
stringBuffer.append(bytesToHexString(cmdCode.getValue()>> 8 & 0xff));//高8位
stringBuffer.append(bytesToHexString(cmdCode.getValue() & 0xff));//低8位
}else if(cmdCode.getLenth()==4){
stringBuffer.append(bytesToHexString(cmdCode.getValue()>> 24 & 0xff));//高24位
stringBuffer.append(bytesToHexString(cmdCode.getValue()>> 16 & 0xff));//高16位
stringBuffer.append(bytesToHexString(cmdCode.getValue()>> 8 & 0xff));//高8位
stringBuffer.append(bytesToHexString(cmdCode.getValue() & 0xff));//低8位
}else if(cmdCode.getLenth()==6){
stringBuffer.append(bytesToHexString(cmdCode.getValue()>> 40 & 0xff));//高40位
stringBuffer.append(bytesToHexString(cmdCode.getValue()>> 32 & 0xff));//高32位
stringBuffer.append(bytesToHexString(cmdCode.getValue()>> 24 & 0xff));//高24位
stringBuffer.append(bytesToHexString(cmdCode.getValue()>> 16 & 0xff));//高16位
stringBuffer.append(bytesToHexString(cmdCode.getValue()>> 8 & 0xff));//高8位
stringBuffer.append(bytesToHexString(cmdCode.getValue() & 0xff));//低8位
}
}
return stringBuffer.toString();
}
三,总结:
3.1 整个Mesh通信拓扑的实现还是比较复杂的,所以开源的可能不好找,我也是基于收费厂家的一套Mesh方案实现的组网的步骤,有兴趣的可以了解下组网的概念和组网的流程。
3.2 组网步骤总结:
第一步肯定还是扫描设备,毕竟这是蓝牙最基本功能
Mesh节点在网络内发送数据不会像普通BLE广播需要等一个固定的广播间隔,而是延迟一小段随机时间后发送,所以为了数据不丢失,节点会启用100%占空比来扫描广播信道,扫描窗口时间=扫描间隔
第二步检测key连接设备
mesh对传输的数据进行分层次加密,网络层(Network Layer)数据通过网络密钥(Network Key)加密;应用密钥(App Key)用于加密接入层(Access Layer)数据;配置模型(Configuration Model)的数据则采用设备密钥(Device Key)进行加密
第三步选择节点
mesh里面还给每个节点有一些额外的四种可选的特性(Features)。分别是中继Relay,代理Proxy,朋友Friend和 低功耗Low Power features。节点可以在某个时间点选择不支持或者支持多个Feature。
中继(Relay)支持中继的节点,可以帮忙转发收到的消息。因为有了Relay,Mesh网络就可以实现多跳(Hops)。
低功耗和朋友(Low Power Nodes and Friend Nodes), 这是搭配来用的。我们先说Low power节点,类似于对功耗有要求的设备,例如温度传感器。这种类型的设备为了节约功耗,很大的时间都是在休眠的。也就是意味着他们收不到网络中发过来的消息。Friend节点能帮LP节点暂存消息。当LP节点需要的时候,可以发消息给Friend节点, 问问有没有“waiting message”。如果有,就会一条条的发给LP节点。简而言之,Friend节点就像是门卫的张大爷,你(Low power node)想起来的时候去门卫拿你要的信就好了。这种方式和zigbee里面的enddevice向父节点拿数据的方式类似
第四步配置入网
所谓配网就是将未配网的设备变为配网的节点,一般需要一个配网器与末配网设备进行配网交互、验证然后通过后将一些密钥交给对方的一个过程。
一般过程有5个阶段:
1. 信标(Beaconing)阶段
2.邀请 (Invitation)阶段
3. 交换公钥 (Exchange Public Keys)阶段
4. 身份认证 CAuthentication)阶段
5. 分发配网数据 (Distribution Of Provisioning Data)阶段
第五步分配地址
单播地址:分配给节点中的元素地址,地址范围0x0001~0x7FFF,
未分配地址:即无效地址,固定为0x0000,地址的初始值,常用于屏蔽一个设备
组播地址:用于表示一个或多个节点的多个元素,地址范围0xC000~0xFFFF,其中包含256个固定组播地址
虚拟地址:用于表示一个或多个节点的多个元素,每一个虚拟地址逻辑上对应一个128-bit的Label UUID,通过对该Label UUID作哈希运算得出虚拟地址的低14位数值,虚拟地址的范围为0x8000~0xBFFF
第六步选择模型
模型(Model)定义了节点基本功能的最小单位模型,包含实现这个功能所必需的状态和操作状态的消息及其他一些行为
在蓝牙mesh模型里,消息通信基于客户端-服务器的架构,对外提供状态访问接口的叫做服务器(Server),而访问服务器状态的叫做客户端,模型分为三种
服务器模型:服务器模型包含了一个或多个元素上的一种或多种状态,比如灯泡上包含有通用开关服务器模型(Generic OfOff Server)和灯泡亮度服务器模型(Light Lightneww Server)
客户端模型:客户端模型定义了一系列的消息,用于客户端去请求、设置服务端的状态,比如开关中含有通用开关客户端模型(Generic OnOff Client)以及灯亮度客户端模型(Light Lightness Client),客户端模型不含有状态
控制模型:控制模型可以包含一个或多个客户端模型,用来和其他节点的服务端模型通信;也可以包含一个或多个服务端模型,用于响应其他节点客户端模型发来的消息
蓝牙技术联盟定义的模型被称为标准模型(SIG Adopted Model),16bit标识,目前SIG定义好的模型包括Generic、Sensors、Time and Scenes、Lighting;由厂商定义的模型称为厂商模型(Vendor Model),32bit标识。
第七步发布和订阅
在蓝牙mesh里面发消息的动作我们叫做发布(Publish)。光从字面意思理解大家基本上就能看懂了。我想告诉别人什么事情发生或者做什么事情就叫做发布。谁对某些消息感兴趣就可以订阅这些内容。节点发布消息到单播地址,组播地址或者虚拟地址。节点有兴趣接收这些数据的可以订阅这些地址。
第八步发送消息
蓝牙Mesh采用了消息缓存队列和TTL的优化方案来避免消息的无限制转发。
消息缓存 Message cache:设备都会缓存收到消息的关键信息,以确定是否已经转发过此消息,如果是就忽略此消息。Message cache至少需要能缓存两条消息
Time to Live(TTL): 每个消息都会包含一个Time to Live(TTL)的值,来限制中继的次数,最大可以中继126次。消息每转发一次TTL的值就减1,TTL值为1就不再转发