Android硬件通信之 蓝牙Mesh通信
一,简介
蓝牙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 settingsfinal 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>() {@Overridepublic 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();}});//监听keyprovisioningViewModel.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 datamMeshNetworkLiveData.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位保持0stringBufferFunction.append(cmdFunction.getIsSetting());//5 0-查询 / 1-设置//stringBufferFunction.append("0");//3位保持0stringBufferFunction.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就不再转发
相关文章:

Android硬件通信之 蓝牙Mesh通信
一,简介 蓝牙4.0以下称为传统蓝牙,4.0以上是低功耗蓝牙,5.0开始主打物联网 5.0协议蓝牙最重要的技术就是Mesh组网,实现1对多,多对多的无线通信。即从点对点传输发展为网络拓扑结构,主要领域如灯光控制等&…...
PG数据库实现bool自动转smallint的方式
删除函数: 语法: DROP FUNCTION IF EXISTS your_schema_name.function_name(arg_type1, arg_type2) CASCADE RESTRICT; 实例: DROP FUNCTION IF EXISTS platformyw.boolean_to_smallint(bool) CASCADE RESTRICT; 查询是否存在函数 语法: SELE…...

易观千帆 | 2023年3月证券APP月活跃用户规模盘点
易观:2023年3月证券服务应用活跃人数14131.58万人,相较上月,环比增长0.61%,同比增长0.60%;2023年3月自营类证券服务应用Top10 活跃人数6221.44万人,环比增长0.08%;2023年3月第三方证券服务应用T…...

2023年江苏专转本成绩查询步骤
2023年江苏专转本成绩查询时间 2023年江苏专转本成绩查询时间预计在5月初,参加考试的考生,可以关注考试院发布的消息。江苏专转本考生可在规定时间内在省教育考试院网,在查询中心页面中输入准考证号和身份证号进行查询,或者拨…...
JavaScript中sort()函数
sort()函数是javascript中自带函数,这个函数的功能是排序。 使用sort()函数时,函数参数如果不设置的话,以默认方式进行排序,就是以字母顺序进行排序,准确的讲就是按照字符编码的顺序进行排序。 var arr [3,2,3,34,1…...

泰克Tektronix DPO5204B混合信号示波器
特征 带宽:2 GHz输入通道:4采样率:1 或 2 个通道上为 5 GS/s、10 GS/s记录长度:所有 4 个通道 25M,50M:1 或 2 个通道上升时间:175 皮秒MultiView zoom™ 记录长度高达 250 兆点>250,000 wf…...

突破传统监测模式:业务状态监控HM的新思路
作者:京东保险 管顺利 一、传统监控系统的盲区,如何打造业务状态监控。 在系统架构设计中非常重要的一环是要做数据监控和数据最终一致性,关于一致性的补偿,已经由算法部的大佬总结过就不在赘述。这里主要讲如何去补偿ÿ…...

0Ω电阻在PCB板中的5大常见作用
在PCB板中,时常见到一些阻值为0Ω的电阻。我们都知道,在电路中,电阻的作用是阻碍电流,而0Ω电阻显然失去了这个作用。那它存在于PCB板中的原因是什么呢?今天我们一探究竟。 1、充当跳线 在电路中,0Ω电阻…...

分布式消息队列Kafka(三)- 服务节点Broker
1.Kafka Broker 工作流程 (1)zookeeper中存储的kafka信息 1)启动 Zookeeper 客户端。 [zrclasshadoop102 zookeeper-3.5.7]$ bin/zkCli.sh 2)通过 ls 命令可以查看 kafka 相关信息。 [zk: localhost:2181(CONNECTED) 2]…...
蠕动泵说明书_RDB
RDB_2T-S蠕 动 泵 概述 蠕动灌装泵是一种高性能、高质量的泵。采用先进的微处理技术及通讯方式做成的控制器和步进电机驱动器,配以诚合最新研制出的泵头,使产品在稳定性、先进性和性价比上达到一个新的高度。适用饮料、保健品、制药、精细化工等诸流量…...
浅谈react如何自定义hooks
react 自定义 hooks 简介 一句话:使用自定义hooks可以将某些组件逻辑提取到可重用的函数中。 自定义hooks是一个从use开始的调用其他hooks的Javascript函数。 下面以一个案例: 新闻发布操作,来简单说一下react 自定义 hooks。 不使用自定义hooks时 …...

如何优雅的写个try catch的方式!
软件开发过程中,不可避免的是需要处理各种异常,就我自己来说,至少有一半以上的时间都是在处理各种异常情况,所以代码中就会出现大量的try {...} catch {...} finally {...} 代码块,不仅有大量的冗余代码,而…...

海尔智家:智慧场景掌握「主动」权,用户体验才有话语权
2023年1月,《福布斯》AI专栏作家Rob Toews发布了年度AI发展预测,指出人工智能的发展将带来涉及各行业、跨学科领域的深远影响。变革将至,全球已掀起生成式AI热,以自然语言处理为代表的人工智能技术在快速进化,积极拥抱…...

基于铜锁,在前端对登录密码进行加密,实现隐私数据保密性
本文将基于 铜锁(tongsuo)开源基础密码库实现前端对用户登录密码的加密,从而实现前端隐私数据的保密性。 首先,铜锁密码库是一个提供现代密码学算法和安全通信协议的开源基础密码库,在中国商用密码算法,例…...
LVS的小总结
LVS的工作模式及其工作过程: LVS 有三种负载均衡的模式,分别是VS/NAT(nat 模式)、VS/DR(路由模式)、VS/TUN(隧道模式)。 1、NAT模式(NAT模式) 原理&#x…...

Spring依赖注入(DI配置)
Spring依赖注入 1. 依赖注入方式【重点】1.1 依赖注入的两种方式1.2 setter方式注入问题导入引用类型简单类型 1.3 构造方式注入问题导入引用类型简单类型参数适配【了解】 1.4 依赖注入方式选择 2. 依赖自动装配【理解】问题导入2.1 自动装配概念2.2 自动装配类型依赖自动装配…...

绘声绘影2023简体中文版新功能介绍
会声会影是一款专业的数字音频工作站软件,它提供强大的音频编辑和制作功能,被广泛应用于音乐创作、录音棚录制以及现场演出等领域。会声会影的最新版本会声会影2023将于2022年底发布,主要功能和新功能详述如下: 会声会影2023主要功能: 1. 直观易用的界面:会声会影采用简洁而不…...
一个好的前端开发人员必须掌握的前端代码整洁与开发技巧
前端代码整洁与开发技巧 为保证前端人员在团队项目开发过程中的规范化、统一化,特建立《前端代码整洁与开发技巧》文档,通过代码简洁推荐、开发技巧推荐等章节来帮助我们统一代码规范和编码风格,从而提升项目的可读性和可维护性。 目录 …...

【别再困扰于LeetCode接雨水问题了 | 从暴力法=>动态规划=>单调栈】
🚀 算法题 🚀 🌲 算法刷题专栏 | 面试必备算法 | 面试高频算法 🍀 🌲 越难的东西,越要努力坚持,因为它具有很高的价值,算法就是这样✨ 🌲 作者简介:硕风和炜,…...

酒厂酒业IP网络广播系统建设方案-基于局域网的新一代交互智慧酒厂酒业IP广播设计指南
酒厂酒业IP网络广播系统建设方案-基于局域网的新一代交互智酒业酒厂IP广播系统设计指南 由北京海特伟业任洪卓发布于2023年4月25日 一、酒厂酒业IP网络广播系统建设需求 随着中国经济的快速稳步发展,中国白酒行业也迎来了黄金时期,产品规模、销售业绩等…...

UE5 学习系列(二)用户操作界面及介绍
这篇博客是 UE5 学习系列博客的第二篇,在第一篇的基础上展开这篇内容。博客参考的 B 站视频资料和第一篇的链接如下: 【Note】:如果你已经完成安装等操作,可以只执行第一篇博客中 2. 新建一个空白游戏项目 章节操作,重…...

docker详细操作--未完待续
docker介绍 docker官网: Docker:加速容器应用程序开发 harbor官网:Harbor - Harbor 中文 使用docker加速器: Docker镜像极速下载服务 - 毫秒镜像 是什么 Docker 是一种开源的容器化平台,用于将应用程序及其依赖项(如库、运行时环…...
C++:std::is_convertible
C++标志库中提供is_convertible,可以测试一种类型是否可以转换为另一只类型: template <class From, class To> struct is_convertible; 使用举例: #include <iostream> #include <string>using namespace std;struct A { }; struct B : A { };int main…...

使用分级同态加密防御梯度泄漏
抽象 联邦学习 (FL) 支持跨分布式客户端进行协作模型训练,而无需共享原始数据,这使其成为在互联和自动驾驶汽车 (CAV) 等领域保护隐私的机器学习的一种很有前途的方法。然而,最近的研究表明&…...

Linux-07 ubuntu 的 chrome 启动不了
文章目录 问题原因解决步骤一、卸载旧版chrome二、重新安装chorme三、启动不了,报错如下四、启动不了,解决如下 总结 问题原因 在应用中可以看到chrome,但是打不开(说明:原来的ubuntu系统出问题了,这个是备用的硬盘&a…...
爬虫基础学习day2
# 爬虫设计领域 工商:企查查、天眼查短视频:抖音、快手、西瓜 ---> 飞瓜电商:京东、淘宝、聚美优品、亚马逊 ---> 分析店铺经营决策标题、排名航空:抓取所有航空公司价格 ---> 去哪儿自媒体:采集自媒体数据进…...

Maven 概述、安装、配置、仓库、私服详解
目录 1、Maven 概述 1.1 Maven 的定义 1.2 Maven 解决的问题 1.3 Maven 的核心特性与优势 2、Maven 安装 2.1 下载 Maven 2.2 安装配置 Maven 2.3 测试安装 2.4 修改 Maven 本地仓库的默认路径 3、Maven 配置 3.1 配置本地仓库 3.2 配置 JDK 3.3 IDEA 配置本地 Ma…...
JS设计模式(4):观察者模式
JS设计模式(4):观察者模式 一、引入 在开发中,我们经常会遇到这样的场景:一个对象的状态变化需要自动通知其他对象,比如: 电商平台中,商品库存变化时需要通知所有订阅该商品的用户;新闻网站中࿰…...

Selenium常用函数介绍
目录 一,元素定位 1.1 cssSeector 1.2 xpath 二,操作测试对象 三,窗口 3.1 案例 3.2 窗口切换 3.3 窗口大小 3.4 屏幕截图 3.5 关闭窗口 四,弹窗 五,等待 六,导航 七,文件上传 …...
OD 算法题 B卷【正整数到Excel编号之间的转换】
文章目录 正整数到Excel编号之间的转换 正整数到Excel编号之间的转换 excel的列编号是这样的:a b c … z aa ab ac… az ba bb bc…yz za zb zc …zz aaa aab aac…; 分别代表以下的编号1 2 3 … 26 27 28 29… 52 53 54 55… 676 677 678 679 … 702 703 704 705;…...