在app 项目的 build.gradle 中引入一下依赖

implementation 'no.nordicsemi.android:log:2.1.1'
implementation 'com.madgag.spongycastle:core:1.56.0.0'
implementation 'com.madgag.spongycastle:prov:1.56.0.0'
implementation 'com.google.code.gson:gson:2.8.5'
implementation 'no.nordicsemi.android.support.v18:scanner:1.1.0'
implementation "android.arch.lifecycle:extensions:1.1.1"
implementation 'android.arch.persistence.room:runtime:1.1.1'
annotationProcessor "android.arch.persistence.room:compiler:1.1.1"
androidTestImplementation 'android.arch.persistence.room:testing:1.1.1'

mesh sdk 初始化

MeshManagerApi mMeshManagerApi = new MeshManagerApi(context);
               mMeshManagerApi.setMeshManagerCallbacks(this);
               mMeshManagerApi.setProvisioningStatusCallbacks(this);
               mMeshManagerApi.setMeshStatusCallbacks(this);

BleMeshManager bleMeshManager = new BleMeshManager(context)

NetworkInformation networkInformation=new NetworkInformation(context)

NrfMeshRepository mMeshRepository=NrfMeshRepository(mMeshManagerApi, networkInformation, bleMeshManager);

以上是初始化流程

扫描未入网设备

ScannerRepository  mScannerRepository=new ScannerRepository(context,meshManagerApi)
mScannerRepository.getScannerState().startScanning();
//扫描前建议判断下蓝牙是否可用,定位是否打开(Android sdk>= 6.0)
mScannerRepository.startScan(BleMeshManager.MESH_PROVISIONING_UUID)
//扫描结果回调
mScannerRepository.getScannerState().observe(this, state -> {
        mDevices = scannerLiveData.getDevices();
       final Integer i = devices.getUpdatedDeviceIndex();
          if (i != null)
              notifyItemChanged(i);
          else
              notifyDataSetChanged();
      });

扫描已经入网设备

ScannerRepository  mScannerRepository=new ScannerRepository(context,meshManagerApi)
mScannerRepository.getScannerState().startScanning();
//扫描前建议判断下蓝牙是否可用,定位是否打开(Android sdk>= 6.0)
mScannerRepository.startScan(BleMeshManager.MESH_PROXY_UUID)
//扫描结果回调
mScannerRepository.getScannerState().observe(this, state -> {
        mDevices = scannerLiveData.getDevices();
       final Integer i = devices.getUpdatedDeviceIndex();
          if (i != null)
              notifyItemChanged(i);
          else
              notifyDataSetChanged();
      });

点击设备开始连接,入网开始

1 设备连接成功后,mesh SDK 进行发现服务,使能通知(接收硬件端发送的数据)

final UnprovisionedBeacon beacon = (UnprovisionedBeacon) device.getBeacon();
     //private static final int ATTENTION_TIMER = 5;
     if (beacon != null) {
         mMeshManagerApi.identifyNode(beacon.getUuid(), device.getName(), device.getAddress(), ATTENTION_TIMER);
     } else {
         final byte[] serviceData = getServiceData(device.getScanResult(), BleMeshManager.MESH_PROVISIONING_UUID);
         if (serviceData != null) {
             final UUID uuid = mMeshManagerApi.getDeviceUuid(serviceData);
             //第一步  发送invite  消息
             mMeshManagerApi.identifyNode(uuid, device.getName(), device.getAddress(), ATTENTION_TIMER);
         }
     }



// 入网回调
mNrfMeshRepository.getUnprovisionedMeshNode().observe(this, meshNode -> {
         if (meshNode != null) {
             if (meshNode.getDeviceAddress() == null) {
                 meshNode.setDeviceAddress(device.getAddress());
             }
               // 第二步  收到设备端回复Capabilities
             if (meshNode.getProvisioningCapabilities() != null) {
              //此处可以进行界面刷新
              // 第三步  发送provision start 消息  有三种方式
               final UnprovisionedMeshNode node = mMeshRepository.getUnProvisionedMeshNode().getValue();
              //mMeshRepository.getMeshManagerApi().startProvisioning(node);
             //mMeshRepository.getMeshManagerApi().startProvisioningWithStaticOOB(node);
              //mMeshRepository.getMeshManagerApi().startProvisioningWithOutputOOB(node, action);
            //调用 startProvision ,sdk会自动完成入网

             }
         }
     });
  // provision state callback
mNrfMeshRepository.getProvisioningStatus().observe(this, provisioningStateLiveData -> {
         if (provisioningStateLiveData != null) {
             final ProvisionerProgress provisionerProgress = provisioningStateLiveData.getProvisionerProgress();

             if (provisionerProgress != null) {
                 final ProvisioningStatusLiveData.ProvisioningLiveDataState state = provisionerProgress.getState();
                 switch (state) {
                     case PROVISIONING_INVITE:
                         // step 1
                         break;
                     case PROVISIONING_CAPABILITIES:
                         // step 2

                         break;
                     case PROVISIONING_START:
                         //step 3
                          break;
                     case PROVISIONING_FAILED:
                     // 入网失败

                         break;
                     case PROVISIONING_AUTHENTICATION_STATIC_OOB_WAITING:
                      //step 4
                      break;
                     case PROVISIONING_AUTHENTICATION_OUTPUT_OOB_WAITING:
                     //step 4

                         break;
                     case PROVISIONING_AUTHENTICATION_INPUT_OOB_WAITING:
                     //step 4

                         break;
                     case PROVISIONING_AUTHENTICATION_INPUT_ENTERED:
                        //step 4
                         break;
                     case COMPOSITION_DATA_STATUS_RECEIVED:

                         break;
                     case PROVISIONING_PUBLIC_KEY_SENT:
                         //step 5
                         break;
                     case PROVISIONING_PUBLIC_KEY_RECEIVED:
                         //step 6
                         break;
                     case PROVISIONING_RANDOM_SENT:
                         //step 7
                         break;
                     case PROVISIONING_RANDOM_RECEIVED:
                         //step 8
                         break;
                     case PROVISIONING_DATA_SENT:
                         //step 9
                         break;
                     case PROVISIONING_COMPLETE:
                     // 入网完成
                        //received
                         break
                     case APP_KEY_STATUS_RECEIVED:

                         break;
                 }

             }

         }
     });

入网成功后手机数据库中会插入一条该设备数据 在MeshManagerApi.java中

private final InternalMeshManagerCallbacks internalMeshMgrCallbacks = new InternalMeshManagerCallbacks() {
    @Override
    public void onNodeProvisioned(final ProvisionedMeshNode meshNode) {
        updateProvisionedNodeList(meshNode);
        incrementUnicastAddress(meshNode.getUnicastAddress(), meshNode.getNumberOfElements());
        //Set the mesh network uuid to the node so we can identify nodes belonging to a network
        meshNode.setMeshUuid(mMeshNetwork.getMeshUUID());
        //插入节点数据
        mMeshNetworkDb.insertNode(mProvisionedNodeDao, meshNode);
        mMeshNetworkDb.updateProvisioner(mProvisionerDao,
                mMeshNetwork.getSelectedProvisioner());
        mTransportCallbacks.onNetworkUpdated(mMeshNetwork);
    }

    private void updateProvisionedNodeList(final ProvisionedMeshNode meshNode) {
        Log.d(TAG, "updateProvisionedNodeList: " + meshNode.getUuid());
        for (int i = 0; i < mMeshNetwork.nodes.size(); i++) {
            final ProvisionedMeshNode node = mMeshNetwork.nodes.get(i);
            Log.d(TAG, "updateProvisionedNodeList: " + node.getUuid());
            if (meshNode.getUuid().equals(node.getUuid())) {
                mMeshNetwork.nodes.remove(i);
                break;
            }
        }
        mMeshNetwork.nodes.add(meshNode);
    }
};

发送消息注意事项

ConfigMessage 地址必须是:unicastAddress(mMeshRepository.getSelectedMeshNode().value().getUnicastAddress()) GenericMessage 地址必须是:节点元素地址 elementAddress

ConfigMessage 属于配置信息主要目的是给相应的model 添加通信能力的字段信息 例如:ConfigAppkeyAdd 添加节点通信加解密appkey(16byte) 例如:ConfigModelAppBind 用于model通信加解密绑定 例如:ConfigModelSubscriptionAdd 订阅分组

public ConfigModelSubscriptionAdd(@NonNull final byte[] elementAddress,
                                  @NonNull final byte[] subscriptionAddress,
                                  final int modelIdentifier) throws IllegalArgumentException {
                                  //subscriptionAddress 分组地址 0xc000~0xFFEF
    this(AddressUtils.getUnicastAddressInt(elementAddress), AddressUtils.getUnicastAddressInt(subscriptionAddress), modelIdentifier);
}

发送消息

//example 入网完成后需要获取设备配置信息
 final ConfigCompositionDataGet compositionDataGet = new ConfigCompositionDataGet();
 ProvisionedMeshNode provisionedMeshNode =  mMeshRepository.getProvisionedMeshNode().getValue();
 int dst = provisionedMeshNode.getUnicastAddress();//代表目的地址(elementAddress,groupAddress) 手机发送消息用到的地址
 mMeshRepository.getMeshManagerApi().sendMeshMessage(dst, meshMessage);

 mMeshRepository.getNrfMeshRepository().getMeshMessageLiveData().observe(this, new Observer<MeshMessage>() {
               @Override
               public void onChanged(@Nullable MeshMessage meshMessage) {

                  if(meshMessage instanceof ConfigCompositionDataStatus){
                       //收到设备端回复的配置信息
                       //ConfigCompositionDataStatus 解析得到List :elements ,list:models
                        }else{
                  // more status need to catch
                  }

           });

移除节点

  final ConfigNodeReset configNodeReset = new ConfigNodeReset();
ProvisionedMeshNode provisionedMeshNode =  mMeshRepository.getSelectedMeshNode().getValue();//get之前先set  不然会报错
int dst = provisionedMeshNode.getUnicastAddress();
mMeshRepository.getMeshManagerApi().sendMeshMessage(dst, meshMessage);


mMeshRepository.getNrfMeshRepository().getMeshMessageLiveData().observe(this, new Observer<MeshMessage>() {
              @Override
              public void onChanged(@Nullable MeshMessage meshMessage) {
                 if(meshMessage instanceof ConfigNodeResetStatus){

                      //节点已经移除,该设备已经变成未入网设备,可以进行入网操作

                 }else{

                 // more status need to catch
                 }

          });

在手机数据库中添加分组信息

//地址范围 0xc000~0xFEFF
//(例如手机中现有两个节点,并且都订阅了改地址(以下代码中的地址是0xc000),那么现在可以向0xc000 发送一条分组消息,那么这两个节点都会处理该消息
mMeshRepository.getMeshNetworkLiveData().getMeshNetwork().addGroup(0xc000,"randomName")

从手机数据库中获取分组列表

mMeshRepository.getGroups().observe(this, groups -> {
         //refresh  UI
      });

导出手机入网数据

final File f = new File(EXPORT_PATH);

         if (!f.exists()) {

             f.mkdirs();
         }
         mMeshRepository.getMeshManagerApi().exportMeshNetwork(EXPORT_PATH);

导入数据到手机

final Intent intent;
     if (Utils.isKitkatOrAbove()) {
         intent = new Intent(Intent.ACTION_OPEN_DOCUMENT);
     } else {
         intent = new Intent(Intent.ACTION_GET_CONTENT);
     }
     intent.addCategory(Intent.CATEGORY_OPENABLE);
     intent.setType("*/*");
     startActivityForResult(intent, READ_FILE_REQUEST_CODE);


      public void onActivityResult(final int requestCode, final int resultCode, final Intent data) {
     super.onActivityResult(requestCode, resultCode, data);
     switch (requestCode) {
         case READ_FILE_REQUEST_CODE:
             if (resultCode == RESULT_OK) {
                 if (data != null) {
                     final Uri uri = data.getData();
                     mMeshRepository.importMeshNetwork(uri);
                 }
             } else {
                 Log.e(TAG, "Error while opening file browser");
             }
             break;
     }
 }