Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

I updated from version 3.1.5 to version 4.0.1. I need to update many interfaces and the changes are huge. Is there any convenient way? #592

Open
liuhk8 opened this issue Nov 13, 2023 · 7 comments
Labels
question Further information is requested

Comments

@liuhk8
Copy link

liuhk8 commented Nov 13, 2023

Version

4.0.1 (Latest)

Ask the question

I updated from version 3.1.5 to version 4.0.1. I need to update many interfaces and the changes are huge. Is there any convenient way?

Relevant log output

No response

@liuhk8 liuhk8 added the question Further information is requested label Nov 13, 2023
@philips77
Copy link
Member

Hi,
Btw, there's 4.1 from today.
The migration guide is available in different PRs. You'll see a list of them in the release notes. Changes from 3.2.0 to 4 are indeed big. I was trying to make the migration somewhat not terrible. Most of the old API is just deprecated, but some had to be replaced for different reasons.

I will try to help you here. What do you need help with?

@philips77
Copy link
Member

The biggest breaking change is minimum iOS version set to 13 due to use of Task, actor, async sequences, async methods.

@philips77
Copy link
Member

I will try to help you here. What do you need help with?

How is the migration going?

@ahmadshoh-orc
Copy link

Hi @philips77,

I recently updated from version 3.2.0 to 4.2.0 and am encountering some issues with the provisioning process. Could you please assist me with the migration?

I am developing a Flutter plugin to interact with nRFMeshProvision from Flutter. The first problem I encountered is with the provisioning process. Below is a relevant excerpt from my QBleMesh class, where I have a provision method:

import Foundation
import NordicMesh
import CoreBluetooth

public class QBleMesh {
    var connection: NetworkConnection?
    var unprovisionedDevices: [DiscoveredPeripheral] = [DiscoveredPeripheral]()
    var discoveredDevices: [DiscoveredDevice] = [DiscoveredDevice]()
    var statusEntities: [StatusEntity] = [StatusEntity]()
    
    // callbacks
    var provisionCallback: (_ provisionedNode: ProvisionedNode) -> Void = {_ in}
    var statusCallback: (_ status: StatusEntity) -> Void = {_ in}
    var discoveredDevicesCallback: (_ discoveredDevices: [DiscoveredDevice]) -> Void = {_ in}
    
    var provisioningService: ProvisioningService?
    var _meshNetworkManager: MeshNetworkManager?
    var meshNetworkManager: MeshNetworkManager {
        get {
            if self._meshNetworkManager == nil {
                _meshNetworkManager = initializeMeshNetworkManager()
            }
            return self._meshNetworkManager!
        }
    }
    
    private func initializeMeshNetworkManager() -> MeshNetworkManager {
        let meshNetworkManager = MeshNetworkManager()
        meshNetworkManager.networkParameters = .advanced { parameters in
            parameters.sarAcknowledgmentRetransmissionsCount = 2
            parameters.acknowledgmentMessageInterval = 4.2
            parameters.acknowledgmentMessageTimeout = 40.0
        }
        meshNetworkManager.logger = self
        return meshNetworkManager
    }
    
    init() {
        var meshLoaded = false
        do {
            meshLoaded = try meshNetworkManager.load()
        } catch {
            print(error)
        }
        
        if (meshLoaded) {
            meshNetworkDidChange()
        } else {
            createNewMeshNetwork()
        }
    }

    func startScan() -> Bool {
        if let networkConnection = connection {
            return networkConnection.startScanForPeripherals()
        }
        return false
    }

    func discoveredDevices(_ callback: @escaping (_ discoveredDevices: [DiscoveredDevice]) -> Void) {
        discoveredDevicesCallback = callback
        if let networkConnection = connection {
            networkConnection.discoveredPeripherals() { (discoveredPeripheral: DiscoveredPeripheral) in
                if !self.unprovisionedDevices.contains(where: { $0.peripheral.identifier == discoveredPeripheral.peripheral.identifier }) {
                    self.unprovisionedDevices.append((discoveredPeripheral.device, discoveredPeripheral.peripheral, discoveredPeripheral.rssi))
                    let device = DiscoveredDevice(address: discoveredPeripheral.peripheral.identifier.uuidString,
                                                  name: discoveredPeripheral.peripheral.name ?? "",
                                                  rssi: discoveredPeripheral.rssi)
                    if let updateIndex = self.discoveredDevices.firstIndex(where: { $0.address == device.address }) {
                        self.discoveredDevices[updateIndex] = device
                    } else {
                        self.discoveredDevices.append(device)
                    }
                }
                self.discoveredDevicesCallback(self.discoveredDevices)
            }
        }
    }
    
    func stopScan() -> Bool {
        if let networkConnection = connection {
            return networkConnection.stopScan()
        }
        discoveredDevicesCallback = {_ in}
        return false
    }
    
    func identify(_ address: String, _ callback: @escaping (_ unprovisionedNode: UnprovisionedNode) -> Void) {
        provisioningService?.identifyingComplete = callback
        if let networkConnection = connection, let uuid = UUID(uuidString: address) {
            let peripheral = networkConnection.centralManager.retrievePeripherals(withIdentifiers: [uuid]).first
            let unprovisionedDevice = unprovisionedDevices.first(where: { $0.peripheral.identifier == peripheral?.identifier })
            let deviceToIdentify = unprovisionedDevice?.device
            deviceToIdentify?.name = unprovisionedDevice?.peripheral.name
            provisioningService?.identify(deviceToIdentify, peripheral: peripheral)
        }
    }

    func provision(_ node: UnprovisionedNode, _ callback: @escaping (_ provisionedNode: ProvisionedNode) -> Void) {
        provisionCallback = callback
        self.provisioningService?.provisioningComplete = { unicastAddress, unprovisionedDevice in
            self.startConfiguration(unicastAddress, deviceToIdentify: unprovisionedDevice)
        }
        self.provisioningService?.provision()
    }
}

In this setup, I set the provisioningComplete callback for the ProvisioningService, which starts the configuration. The full code for ProvisioningService and ConfigurationService is provided below.

After debugging, I found that the code execution halts at the following line in the ConfigurationService:

_ = try? manager.send(ConfigCompositionDataGet(), to: node)

No response is received after this point.

Full code for ProvisioningService and ConfigurationService can be found in another comments below/

Could you please help me identify what might be causing this issue and how I can resolve it?

Thank you!

@ahmadshoh-orc
Copy link

ahmadshoh-orc commented May 15, 2024

class ConfigurationService {
  
  let manager: MeshNetworkManager
  var compositionDataGetComplete: () -> Void = {}
  var configurationComplete: (_ provisionedNode: ProvisionedNode) -> Void = {_ in}
  
  init(manager: MeshNetworkManager) {
    self.manager = manager
    manager.delegate = self
  }
  
  func configure(unicastAddress: Address, name: String, address: String) {
    var provisionedNode: ProvisionedNode = ProvisionedNode(name: name, address: address)
    
    if let meshNetwork = self.manager.meshNetwork {
      if let node = meshNetwork.node(withAddress: unicastAddress) {
        if let appKey = meshNetwork.applicationKeys.first {
          _ = try? manager.send(ConfigAppKeyAdd(applicationKey: appKey), to: node)
          if node.isCompositionDataReceived {
            setupModels(&provisionedNode, unicastAddress: unicastAddress)
          } else {
            _ = try? manager.send(ConfigCompositionDataGet(), to: node)
            compositionDataGetComplete = {
              self.setupModels(&provisionedNode, unicastAddress: unicastAddress)
            }
          }
        }
      }
    }
  }
  
  func setupModels(_ provisionedNode: inout ProvisionedNode, unicastAddress: Address) {
    if let meshNetwork = self.manager.meshNetwork {
      if let node = meshNetwork.node(withAddress: unicastAddress) {
        if let appKey = meshNetwork.applicationKeys.first {
 
          if let primaryElement = node.primaryElement {
            provisionedNode.primaryElement = configurePrimaryElement(primaryElement, appKey: appKey)
          }
          
          for element: Element in node.elements {
            provisionedNode.segments.append(configureSegment(element, appKey: appKey))
          }
        }
      }
    }
    
    self.configurationComplete(provisionedNode)
  }
  
  /// configure the primary element of the node
  func configurePrimaryElement(_ element: Element, appKey: ApplicationKey) -> PrimaryElement {
    let primaryElement: PrimaryElement = PrimaryElement(unicastAddress: element.unicastAddress)

    if let batteryModel = element.model(withSigModelId: .genericBatteryServer) {
      if let batteryConfig = ConfigModelAppBind(applicationKey: appKey, to: batteryModel) {
        _ = try? self.manager.send(batteryConfig, to: element.parentNode!)
      }

      let localAddress: MeshAddress = MeshAddress(self.manager.localElements.first!.unicastAddress)
      let publishMessage = Publish(to: localAddress, using: appKey, usingFriendshipMaterial: false, ttl: 8, period: Publish.Period(TimeInterval(15)), retransmit: Publish.Retransmit())
      if let publicationSet = ConfigModelPublicationSet(publishMessage, to: batteryModel) {
        _ = try? self.manager.send(publicationSet, to: element.parentNode!)
      }
    }

    if let vendorHealthModel = element.model(withModelId: .healthModelId, definedBy: .companyId) {
      if let healthConfig = ConfigModelAppBind(applicationKey: appKey, to: vendorHealthModel) {
        _ = try? self.manager.send(healthConfig, to: element.parentNode!)
      }
    }

    if let vendorCountdownModel = element.model(withModelId: .timerModelId, definedBy: .companyId) {
      if let config = ConfigModelAppBind(applicationKey: appKey, to: vendorCountdownModel) {
        _ = try? self.manager.send(config, to: element.parentNode!)
      }
    }

    return primaryElement
  }

  /// configure a segment
  func configureSegment(_ element: Element, appKey: ApplicationKey) -> Segment {
    let segment: Segment = Segment(unicastAddress: element.unicastAddress)

    if let onOffModel = element.model(withSigModelId: .genericOnOffServer) {
      if let onOffAppKeyConfig = ConfigModelAppBind(applicationKey: appKey, to: onOffModel) {
        _ = try? self.manager.send(onOffAppKeyConfig, to: element.parentNode!)
      }

      let localAddress: MeshAddress = MeshAddress(self.manager.localElements.first!.unicastAddress)
      let publishMessage = Publish(to: localAddress, using: appKey, usingFriendshipMaterial: false, ttl: 8, period: Publish.Period(TimeInterval(15)), retransmit: Publish.Retransmit())
      if let publicationSet = ConfigModelPublicationSet(publishMessage, to: onOffModel) {
        _ = try? self.manager.send(publicationSet, to: element.parentNode!)
      }
    }

    if let lightCtlModel = element.model(withSigModelId: .lightCTLServer) {
      if let lightCtlAppKeyConfig = ConfigModelAppBind(applicationKey: appKey, to: lightCtlModel) {
        _ = try? self.manager.send(lightCtlAppKeyConfig, to: element.parentNode!)
      }
      
      let localAddress: MeshAddress = MeshAddress(self.manager.localElements.first!.unicastAddress)
      let publishMessage = Publish(to: localAddress, using: appKey, usingFriendshipMaterial: false, ttl: 8, period: Publish.Period(TimeInterval(15)), retransmit: Publish.Retransmit())
      if let publicationSet = ConfigModelPublicationSet(publishMessage, to: lightCtlModel) {
        _ = try? self.manager.send(publicationSet, to: element.parentNode!)
      }
    }
    
    return segment
  }
}
extension ConfigurationService: MeshNetworkDelegate {
    func meshNetworkManager(_ manager: MeshNetworkManager, didReceiveMessage message: MeshMessage, sentFrom source: Address, to destination: MeshAddress) {
        if(message is ConfigCompositionDataStatus) {
            self.compositionDataGetComplete()
        }
    }
}
class ProvisioningService {
    var identifyingComplete: (_ unprovisionedNode:UnprovisionedNode) -> Void = {_ in}
    var provisioningComplete: (_ unicastAddress: Address?, _ provisionedDevice:UnprovisionedDevice?) -> Void = {_,_ in}
    
    var peripheral: CBPeripheral?
    var deviceToIdentify: UnprovisionedDevice?
    var capabilitiesReceived: Bool = false
    var bearer: ProvisioningBearer?
    var pbGattBearer: PBGattBearer?
    
    var manager : ProvisioningManager?
    let meshManager: MeshNetworkManager
    
    var authenticationMethod: AuthenticationMethod?
    var publicKey: PublicKey?
    
    init(meshNetworkManager meshManager: MeshNetworkManager) {
        self.meshManager = meshManager
    }
    
    func identify(_ deviceToIdentify: UnprovisionedDevice?, peripheral: CBPeripheral?) {
        self.deviceToIdentify = deviceToIdentify
        self.peripheral = peripheral
        if let item = self.peripheral {
            pbGattBearer = PBGattBearer(target: item)
            pbGattBearer?.logger = meshManager.logger
            pbGattBearer?.delegate = self
            pbGattBearer?.open()
        }
    }
    
    func startIdentifyingProcess() {
        if let unprovisionedDevice = self.deviceToIdentify, let bearer = self.bearer {
            manager = try! meshManager.provision(unprovisionedDevice: unprovisionedDevice, over: bearer)
            manager?.delegate = self
            
            do {
                let attentionTimer: UInt8 = 30
                
                try self.manager?.identify(andAttractFor: attentionTimer)
            } catch {
                self.abortProvisioning()
                return
            }
        }
    }
    
    func identifyOrOpenBearer() {
        if(pbGattBearer?.isOpen ?? false) {
            startIdentifyingProcess()
        } else {
            self.identify(self.deviceToIdentify, peripheral: peripheral)
        }
    }
    
    func provision() {
        guard (manager?.provisioningCapabilities) != nil else {
            print("startProvisioning guard\n -> provisioningManager.provisioningCapabilities = \(String(describing:manager?.provisioningCapabilities))")
            return
        }
        
        publicKey = publicKey ?? .noOobPublicKey
        authenticationMethod = authenticationMethod ?? .noOob
        
        if manager?.networkKey == nil {
            let network = self.meshManager.meshNetwork!
            let networkKey = try! network.add(networkKey: Data.random128BitKey(), name: "Primary Network Key")
            manager?.networkKey = networkKey
        }
        
        // Start provisioning.
        
        do {
            print("start provisioning")
            try self.manager?.provision(usingAlgorithm:       .BTM_ECDH_P256_CMAC_AES128_AES_CCM,
                                        publicKey:            self.publicKey!,
                                        authenticationMethod: self.authenticationMethod!)
        } catch {
            self.abortProvisioning()
            print("startProvisioning Error \n \(error.localizedDescription)")
            return
        }
    }
    
    func abortProvisioning() {
        do {
            try self.bearer?.close()
        } catch {
            print("after update i added this here. bearer?.close has some error")
        }
    }
}
extension ProvisioningService : BearerDelegate {
    func bearerDidOpen(_ bearer: Bearer) {
        self.bearer = bearer as? ProvisioningBearer
        self.startIdentifyingProcess()
    }
    
    public func bearer(_ bearer: Bearer, didClose error: Error?) {
        // Stop here when we are not finished provisioning
        print("provisioning state \(String(describing: self.manager?.state))")
        guard case .complete = self.manager?.state else {
            return
        }
        
        // provisioning is completed
        if meshManager.save() {
            print("Mesh configuration saved.")
        } else {
            print("Mesh configuration could not be saved.")
        }
        
        self.pbGattBearer?.delegate = nil
        self.pbGattBearer = nil
        self.meshManager.delegate = nil
        
        self.provisioningComplete(self.manager?.unicastAddress, self.deviceToIdentify)
        self.deviceToIdentify = nil
    }
}
extension ProvisioningService : ProvisioningDelegate {
    func authenticationActionRequired(_ action: AuthAction) {
        // not needed
    }
    
    func inputComplete() {
        // not needed
    }
    
    public func provisioningState(of unprovisionedDevice: UnprovisionedDevice, didChangeTo state: ProvisioningState) {
        print("state changed to -> \(state)")
        switch(state) {
        case .ready:
            print("ready to provision")
            //check if we have a open gatt
            self.identifyOrOpenBearer()
        case .requestingCapabilities:
            print("requesting capabilities for \(unprovisionedDevice.name ?? "unknown device")")
        case .capabilitiesReceived(_):
            let addressValid = self.manager?.isUnicastAddressValid ?? false
            let deviceSupported = self.manager?.isDeviceSupported == true
            let capabilitiesWereAlreadyReceived = self.capabilitiesReceived
            self.capabilitiesReceived = true
            
            if(deviceSupported && addressValid) {
                if(capabilitiesWereAlreadyReceived) {
                    let isToran:Bool = unprovisionedDevice.name?.lowercased().starts(with: "toran") ?? false
                    let unprovisionedNode:UnprovisionedNode = UnprovisionedNode(
                        address: self.peripheral?.identifier.uuidString ?? "",
                        isToran: isToran,
                        name: unprovisionedDevice.name ?? ""
                    )
                    self.identifyingComplete(unprovisionedNode)
                } else {
                    self.identifyOrOpenBearer();
                }
            }
        case .complete:
            // provisioning is done so we can close the connection to the gatt.
            do {
                try self.bearer?.close()
            } catch {
                print("bearer.close error after update to 4.2.0")
            }
            break
        case .failed(_):
            // provisioning failed so we should inform the user about the failure.
            self.abortProvisioning()
            break
        default:
            break
        }
    }
}

@philips77
Copy link
Member

Hello,
When the provisioning is completed, you call self.bearer?.close(). This will close the GATT connection with the device. At that point the device is switching to normal mode. There are 2 ways you can deal with it:

  1. Reconnect, discover services and proceed with configuration
  2. Handle https://developer.apple.com/documentation/corebluetooth/cbperipheraldelegate/peripheral(_:didmodifyservices:) method and rediscover services.

Anyway, you need to be connected to any provisioned Node before starting to send messages. I can't find reconnection in your code.

In case of nRF Mesh app, there's NetworkConnection class, which constantly scans for the device if it is not connected and connectes to first found matching device (when Auto Connect is enabled).

In the latest version of nRF Mesh app it is also possible to reconnect immediately to the device (even if one was connected) to be able to configure Low Power Nodes (LPN), which don't have any Friend yet. By directly connecting to them using GATT the client can send what's needed without relying on Friends.
Have a look at:

if connection.isConnected && bearer is PBGattBearer {
self.presentAlert(title: "Success",
message: "Provisioning complete.\n\nDo you want to connect to the new Node over GATT bearer?",
options: [reconnectAction, continueAction])
} else {
self.presentAlert(title: "Success",
message: "Provisioning complete.") { _ in
done(reconnect: true)
}
}

@ahmadshoh-orc
Copy link

Thank you for your answer.

I have an extension for the QBleMesh class that facilitates the creation of a new MeshNetworkManager instance. The NetworkConnection class, derived from the nRFMesh app example with some small additional features, should ensure control over the GATT connection, ensuring it's opened or closed appropriately. Or am i missing something?

extension QBleMesh {
  func createNewMeshNetwork() {
    let provisioner = Provisioner.init(name: UIDevice.current.name)
    _ = meshNetworkManager.createNewMeshNetwork(withName: "bt-control", by: provisioner)
    _ = meshNetworkManager.save()

    meshNetworkDidChange()
  }
  
  func meshNetworkDidChange() {
    let meshNetwork = meshNetworkManager.meshNetwork!
    let primary: Element = Element(name: "PrimaryElement", location: .first, models: [
      Model(sigModelId: .genericOnOffClient, delegate: GenericOnOffClientDelegate(meshNetworkManager, statusManager: self)),
      Model(sigModelId: .lightCTLClient, delegate: LightCTLClientDelegate(meshNetworkManager, statusManager: self)),
      Model(sigModelId: .genericBatteryClient, delegate: GenericBatteryClientDelegate(meshNetwork, statusManager: self)),
//      Model(vendorModelId: .healthModelId, companyId: .companyId, delegate: VendorHealthClientDelegate(meshNetworkManager))
      Model(vendorModelId: .timerModelId, companyId: .companyId, delegate: VendorCountdownClientDelegate(meshNetworkManager))
    ])
    
    meshNetworkManager.localElements = [primary]
    
    setApplicationKey()
    setupModelApplicationKeysForLocalNode(meshNetwork.applicationKeys.first!)

    print("meshNetwork.nodes \(meshNetwork.nodes)")
    print("meshNetwork.groups \(meshNetwork.groups)")
    
    connection = NetworkConnection(to: meshNetwork)
    connection?.isConnectionModeAutomatic = true
    connection?.dataDelegate = meshNetworkManager
    connection?.logger = self
    meshNetworkManager.transmitter = connection
    connection?.open()
    
    provisioningService = ProvisioningService(meshNetworkManager: meshNetworkManager)
    meshNetworkManager.delegate = self
  }
  
  func setApplicationKey() {
    let meshNetwork = meshNetworkManager.meshNetwork!
    if(meshNetwork.applicationKeys.isEmpty) {
      let defaults = UserDefaults.standard
      
      var applicationKey:Data? = nil
      
      if let storedKey = defaults.object(forKey: "ApplicationKey") as? Data {
        applicationKey = storedKey
      }
      
      if applicationKey == nil {
        applicationKey = Data.random128BitKey()
        defaults.set(applicationKey!, forKey: "ApplicationKey")
      }
      
      do {
        _ = try meshNetwork.add(applicationKey: applicationKey!, name: "\(UIDevice.current.name)")
      } catch {
        log(message: "Application Key could not be added", ofCategory: .access, withLevel: .error)
      }
    }
  }
  
  func setupModelApplicationKeysForLocalNode(_ applicationKey: ApplicationKey) {
    // first setup battery client
    if let localElements = meshNetworkManager.meshNetwork?.localProvisioner?.node?.elements {
      for element: Element in localElements {
        if let batteryModel = element.model(withSigModelId: .genericBatteryClient) {
          if !batteryModel.isBoundTo(applicationKey) {
            if let configModelAppBindMessage = ConfigModelAppBind(applicationKey: applicationKey, to: batteryModel) {
              _ = try? meshNetworkManager.sendToLocalNode(configModelAppBindMessage)
            }
          }
        }
        if let healthModel = element.model(withModelId: .healthModelId, definedBy: .companyId) {
          if !healthModel.isBoundTo(applicationKey) {
            if let configModelAppBindMessage = ConfigModelAppBind(applicationKey: applicationKey, to: healthModel) {
              _ = try? meshNetworkManager.sendToLocalNode(configModelAppBindMessage)
            }
          }
        }
        if let genericOnOffModel = element.model(withSigModelId: .genericOnOffClient) {
          if !genericOnOffModel.isBoundTo(applicationKey) {
            if let configModelAppBindMessage = ConfigModelAppBind(applicationKey: applicationKey, to: genericOnOffModel) {
              _ = try? meshNetworkManager.sendToLocalNode(configModelAppBindMessage)
            }
          }
        }
        if let lightCtlModel = element.model(withSigModelId: .lightCTLClient) {
          if !lightCtlModel.isBoundTo(applicationKey) {
            if let configModelAppBindMessage = ConfigModelAppBind(applicationKey: applicationKey, to: lightCtlModel) {
              _ = try? meshNetworkManager.sendToLocalNode(configModelAppBindMessage)
            }
          }
        }
      }
    }
  }
  
  func startConfiguration(_ unicastAddress: Address?, deviceToIdentify: UnprovisionedDevice?) {
    let discoveredPeripheral: DiscoveredPeripheral? = unprovisionedDevices.first { (device: UnprovisionedDevice, peripheral: CBPeripheral, rssi: Int32) in
      device.uuid == deviceToIdentify?.uuid
    }
    
    let name = discoveredPeripheral?.peripheral.name ?? ""
    let address = discoveredPeripheral?.peripheral.identifier.uuidString ?? ""
    
    //clean up devices
    unprovisionedDevices.removeAll { (device: UnprovisionedDevice, peripheral: CBPeripheral, rssi: Int32) in
      device.uuid == deviceToIdentify?.uuid
    }
    
    if let unicastAddress: Address = unicastAddress {
      let configService: ConfigurationService = ConfigurationService(manager: self.meshNetworkManager)
      configService.configurationComplete = self.provisionCallback
      configService.configure(unicastAddress: unicastAddress, name: name, address: address)
    }
  }
}

extension QBleMesh : MeshNetworkDelegate {
    public func meshNetworkManager(_ manager: NordicMesh.MeshNetworkManager,
                                   didReceiveMessage message: any NordicMesh.MeshMessage,
                                   sentFrom source: NordicMesh.Address,
                                   to destination: NordicMesh.MeshAddress) {
        print("received a message -> \(message)")
        print("received a message with opcode -> \(message.opCode)")
    }

  public func meshNetworkManager(_ manager: MeshNetworkManager,
                          failedToSendMessage message: MeshMessage,
                          from localElement: Element, to destination: MeshAddress,
                          error: Error) {
    print("received error: \(error)")
    switch error {
    case let accessError as AccessError:
        switch accessError {
        case .timeout:
            self.update(unicastAddress: destination.address, onOff: nil, luminosity: nil, temperature: nil, powerStatus: nil, onlineStatus: false)
        default:
          print("default case for AccessError")
        }
    default:
        print("Unknown error occurred.")
    }
  }
}

and here is NetworkConnection

typealias DiscoveredPeripheral = (
    device: UnprovisionedDevice,
    peripheral: CBPeripheral,
    rssi: Int32
)

class NetworkConnection: NSObject, Bearer {
  private let connectionModeKey = "connectionMode"
  
  /// Maximum number of connections that `NetworkConnection` can
  /// handle.
  static let maxConnections = 1
  /// The Bluetooth Central Manager instance that will scan and
  /// connect to proxies.
  let centralManager: CBCentralManager
  /// The Mesh Network for this connection.
  let meshNetwork: MeshNetwork
  /// The list of connected GATT Proxies.
  var proxies: [GattBearer] = []
  /// A flag set to `true` when any of the underlying bearers is open.
  var isOpen: Bool = false
  
  weak var delegate: BearerDelegate?
  weak var dataDelegate: BearerDataDelegate?
  weak var logger: LoggerDelegate? {
    didSet {
      proxies.forEach {
        $0.logger = logger
      }
    }
  }
  
  var scanCallback: (_ discoveredPeripheral: DiscoveredPeripheral) -> Void = {_ in }
  
  public var supportedPduTypes: PduTypes {
    return [.networkPdu, .meshBeacon, .proxyConfiguration]
  }
  
  /// A flag indicating whether the network connection is open.
  /// When open, it will scan for mesh nodes in range and connect to
  /// them if found.
  private var isStarted: Bool = false
  
  /// Returns `true` if at least one Proxy is connected, `false` otherwise.
  var isConnected: Bool {
    return proxies.contains { $0.isOpen }
  }
  /// Returns the name of the connected Proxy.
  var name: String? {
    return proxies.first(where: { $0.isOpen })?.name
  }
  /// Whether the connection to mesh network should be managed automatically,
  /// or manually.
  var isConnectionModeAutomatic: Bool {
    get {
      return UserDefaults.standard.bool(forKey: connectionModeKey)
    }
    set {
      UserDefaults.standard.set(newValue, forKey: connectionModeKey)
      if newValue && isStarted && centralManager.state == .poweredOn {
        centralManager.scanForPeripherals(withServices: [MeshProxyService.uuid], options: nil)
      }
    }
  }
  
  init(to meshNetwork: MeshNetwork) {
    centralManager = CBCentralManager()
    self.meshNetwork = meshNetwork
    super.init()
    centralManager.delegate = self
    
    // By default, the connection mode is automatic.
    UserDefaults.standard.register(defaults: [connectionModeKey : true])
    
    showPermissionAlert()
  }
  
  func open() {
    if !isStarted && isConnectionModeAutomatic &&
        centralManager.state == .poweredOn {
      if #available(iOS 13.0, *) {
          if centralManager.authorization != .denied {
            _ = startScanForPeripherals()
          }else{
              showPermissionAlert()
          }
      }
    }
    isStarted = true
  }
  
  func close() {
    centralManager.stopScan()
    proxies.forEach { $0.close() }
    proxies.removeAll()
    isStarted = false
  }
  
  func disconnectCurrent() {
    proxies.first?.close()
  }
  
  func send(_ data: Data, ofType type: PduType) throws {
    
    showPermissionAlert()
    
    guard supports(type) else {
      throw BearerError.pduTypeNotSupported
    }
    // Find the first connected proxy. This may be modified to find
    // the closes one, or, if needed, the message can be sent to all
    // connected nodes.
    guard let proxy = proxies.first(where: { $0.isOpen }) else {
      throw BearerError.bearerClosed
    }
    try proxy.send(data, ofType: type)
  }
  
  /// If manual connection mode is enabled, this method may set the
  /// proxy that will be used by the mesh network.
  ///
  /// - parameter bearer: The GATT Bearer proxy to use.
  func use(proxy bearer: GattBearer) {
    guard !isConnectionModeAutomatic else {
      return
    }
    
    bearer.delegate = self
    bearer.dataDelegate = self
    bearer.logger = logger
    
    proxies.filter { bearer.identifier != $0.identifier }.forEach { $0.close() }
    proxies.append(bearer)
    if bearer.isOpen {
      bearerDidOpen(self)
    }
  }
  
  func startScanForPeripherals() -> Bool {
    showPermissionAlert()
    
    centralManager.scanForPeripherals(withServices: [MeshProxyService.uuid, MeshProvisioningService.uuid],
                                      options: [CBCentralManagerScanOptionAllowDuplicatesKey: false])
    return true
  }
  
  func discoveredPeripherals(_ callback: @escaping (_ discoveredPeripheral: DiscoveredPeripheral) -> Void) {
    scanCallback = callback
  }
  
  func stopScan() -> Bool {
    centralManager.stopScan()
    return true
  }
  
  func showPermissionAlert() {
    if #available(iOS 13.0, *) {
      if centralManager.authorization != .allowedAlways {
      }
    }
  }
  
}

extension NetworkConnection: CBCentralManagerDelegate {
  
  func centralManagerDidUpdateState(_ central: CBCentralManager) {
    switch central.state {
    case .poweredOn:
      if isStarted && isConnectionModeAutomatic &&
          proxies.count < NetworkConnection.maxConnections {
        central.scanForPeripherals(withServices: [MeshProxyService.uuid], options: nil)
      }
    case .poweredOff, .resetting:
      proxies.forEach { $0.close() }
      proxies.removeAll()
    default:
      break
    }
  }
  
  func centralManager(_ central: CBCentralManager, didDiscover peripheral: CBPeripheral,
                      advertisementData: [String : Any], rssi RSSI: NSNumber) {
    
    if let unprovisionedDevice = UnprovisionedDevice(advertisementData: advertisementData) {
      self.scanCallback((unprovisionedDevice, peripheral, RSSI.int32Value))
    }
    
    // Is it a Network ID or Private Network Identity beacon?
      if let networkIdentity = advertisementData.networkIdentity {
      guard meshNetwork.matches(networkIdentity: networkIdentity) else {
        // A Node from another mesh network.
        return
      }
    } else {
      // Is it a Node Identity or Private Node Identity beacon?
      guard let nodeIdentity = advertisementData.nodeIdentity,
            meshNetwork.matches(nodeIdentity: nodeIdentity) else {
        // A Node from another mesh network.
        return
      }
    }
    
    guard !proxies.contains(where: { $0.identifier == peripheral.identifier }) else {
      return
    }
    let bearer = GattBearer(target: peripheral)
    proxies.append(bearer)
    bearer.delegate = self
    bearer.dataDelegate = self
    bearer.logger = logger
    // Is the limit reached?
    if proxies.count >= NetworkConnection.maxConnections {
//      central.stopScan()
    }
    bearer.open()
  }
}

extension NetworkConnection: GattBearerDelegate, BearerDataDelegate {
  
  func bearerDidOpen(_ bearer: Bearer) {
    guard !isOpen else {
      return
    }
    isOpen = true
    delegate?.bearerDidOpen(self)
  }
  
  func bearer(_ bearer: Bearer, didClose error: Error?) {
    if let index = proxies.firstIndex(of: bearer as! GattBearer) {
      proxies.remove(at: index)
    }
    if isStarted && isConnectionModeAutomatic &&
        proxies.count < NetworkConnection.maxConnections {
      centralManager.scanForPeripherals(withServices: [MeshProxyService.uuid], options: nil)
    }
    if proxies.isEmpty {
      isOpen = false
      delegate?.bearer(self, didClose: nil)
    }
  }
  
  func bearerDidConnect(_ bearer: Bearer) {
    if !isOpen, let delegate = delegate as? GattBearerDelegate {
      delegate.bearerDidConnect(bearer)
    }
  }
  
  func bearerDidDiscoverServices(_ bearer: Bearer) {
    if !isOpen, let delegate = delegate as? GattBearerDelegate {
      delegate.bearerDidDiscoverServices(bearer)
    }
  }
  
  func bearer(_ bearer: Bearer, didDeliverData data: Data, ofType type: PduType) {
    dataDelegate?.bearer(self, didDeliverData: data, ofType: type)
  }
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
question Further information is requested
Projects
None yet
Development

No branches or pull requests

3 participants