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网络广播系统建设需求 随着中国经济的快速稳步发展,中国白酒行业也迎来了黄金时期,产品规模、销售业绩等…...

OpenHarmony JS Demo开发讲解
项目结构 打开entry→src→main→js,工程的开发目录如图所示 其中, i18n文件夹:用于存放配置不同语言场景的资源,比如应用文本词条,图片路径等资源。en-US.json文件定义了在英文模式下页面显示的变量内容,…...

CentOS系统安装Intel E810 25G网卡驱动
因特尔网卡驱动给的都是二进制包,需要编译环境。 首先去Intel下载最新的驱动 E810驱动下载:https://www.intel.com/content/www/us/en/download/19630/intel-network-adapter-driver-for-e810-series-devices-under-linux.html?wapkwe810 里面有三个驱…...

Java经典的String面试题
Java经典的Spring面试题 String是基本数据类型吗? String你是基本数据类型String是可变的话? String是final类型的,不可变怎么比较两个字符串的值一样,怎么比较两个字符串是否同一对象? 比较字符串的值是否相同用equa…...

c# 结构体与类区别
在 C# 中,结构体(struct)和类(class)都是用户自定义类型,它们具有一些共同的特性,比如可以定义字段、属性、方法等。但它们也有一些区别。 下面是一些结构体和类的区别: 定义方式不…...

使用 patch 命令打补丁
之前的这篇文章 git 导出差异 diff 文件 写了导出 diff 、patch 文件。 拿到 patch 文件,用 patch 命令可以快速的把修改内容合入,合入后在 git 上是已修改的状态,如需提交还要 add 、commit 。 patch 语法 patch --help 可以看到 Usage:…...

C++——类和对象[上]
目录 1.初识面向对象 2.类的引入 3.类的定义 4.成员变量的命名规则 5.类的实例化 6.类对象模型 7.this指针 1.初识面向对象 C语言是一门面向过程的语言,它关注的是完成任务所需要的过程;C是一门面向对象的语言,将一个任务分为多个对…...

MySQL日志
目录 一 关于mysql的设计和运行逻辑 二 MySQL的三类日志 三 对于日志的利用 插入查询 1 备份 2 删除重复数据 一 关于mysql的设计和运行逻辑 mysql在启动的时候非常占空间,需要申请很大的空间,但是有时候内存并没有那么多,所以OS会把my…...

TinyURL 的加密与解密、猜数字游戏、 Fizz Buzz、相对名次----2023/4/28
TinyURL 的加密与解密----2023/4/28 TinyURL 是一种 URL 简化服务, 比如:当你输入一个 URL https://leetcode.com/problems/design-tinyurl 时,它将返回一个简化的URL http://tinyurl.com/4e9iAk 。请你设计一个类来加密与解密 TinyURL 。 加…...

Spring boot结合SkyWalking-Trace工具类实现日志打印请求链路traceid
背景: 随着业务的复杂化、解耦化,运维人员和开发人员需要对请求链路跟踪来快速发现和定位问题,基于应用已经集成了SkyWalking的前提下,如何通过获取SkyWalking生成的统一traceId并加入打印日志中,方便开发人员能够根据…...

精通ES=ElasticSearch
Elasticsearch 是一个分布式、高扩展、高实时的搜索与 数据分析引擎。它能很方便的使大量数据具有搜索、分析和探索的能力。充分利用Elasticsearch的水平 伸缩性,能使数据在 生产环境变得更有价值。Elasticsearch 的实现原理主要分为以下几个步骤,首先用…...