MeshNetworkManager
is the main object, which manages the mesh network. The manager is transport independent, that means it does not handle the Bluetooth Mesh communication on its own. Instead, there is a pair of methods that should be used for this purpose. To pass incoming data to the manager, call the manager.bearerDidDeliverData(_,ofType)
method. The manager will call trasmitter.send(_,ofType)
whenever a packet is needed to be sent to the mesh network. When using a Proxy protocol, the transport implementation must segment and reassembly packets.
let meshNetworkManager = MeshNetworkManager()
or
let meshNetworkManager = MeshNetworkManager(using: MyStorage(), queue: DispatchQueue.someQueue, delegateQueue: DispatchQueue.main)
After instantiating the MeshNetworkManager
object, set up its properties.
The default values are set according to Bluetooth Mesh specification, but you may find modifying the values for your need. For example, when using a proxy with a long connection interval, there is no need to wait for a response for a shorted time than the interval. A library would try to repeat sending a packet assuming it hasn't been received. Have a look at AppDelegate
in the Sample App for an example.
// Read properties documentation for details.
meshNetworkManager.acknowledgmentTimerInterval = 0.600
meshNetworkManager.transmissionTimerInteral = 0.600
meshNetworkManager.retransmissionLimit = 2
meshNetworkManager.acknowledgmentMessageInterval = 5.0
meshNetworkManager.acknowledgmentMessageTimeout = 40.0
meshNetworkManager.logger = self
Upon creation, the manager is not ready. One needs to create, load or import the mesh network. Creating a new network will generate an empty network with one Provisioner
and a primary NetworkKey
. Call save()
to store the configuration in Storage
provided. Calling load()
will load the last used configuration from the Storage
. import(from)
will import a configuration from JSON file that is compatible among different platforms.
var loaded = false
do {
loaded = try meshNetworkManager.load()
} catch {
print(error)
}
// If load failed, create a new MeshNetwork.
if !loaded {
createNewMeshNetwork() // See AppDelegate in Sample App
} else {
meshNetworkDidChange() // See AppDelegate in Sample App
}
After importing a mesh network, make sure to set a unique Provisioner as a local one. Each device (also a phone that has a provisioner role) must have a unique Unicast Address. If you have 3 users in your network and one of them has additionally an iPad, you need to have 4 Provisioners: one per each device.
To make the most common use case (working with GATT Proxy Bearer) easier, the library contains a default implementation for the GATT Proxy and GATT Provisioning bearers, which handle the Proxy protocol. Use GattBearer
and PBGattBearer
respectively. Set the manager as bearer's dataDelegate
and the bearer object as manager's transmitter
.
let bearer = GattBearer(target: scannedPeripheral)
bearer.dataDelegate = meshNetworkManager
meshNetworkManager.transmitter = bearer
bearer.logger = self
bearer.open()
The Sample App provides additional logic on top of the bearer, which allows to connect to multiple proxy nodes at the same time, so that if one proxy disconnects, another one can be used immediately. This logic needs to be used with caution, as proxies can often be used only by a single connected client. By default the limit of concurrent connection is set to 1.
By default, the local Node (that is the Provisioner's Node) will have one Element with 5 Models:
- Configuration Server
- Configuration Client
- Health Server
- Health Client
- Scene Client
If you want to send and receive any other messages beside Configuration Messages, you need to define a Model. For example, if you want to allow user to control switching ON and OFF lights in their homes using Generic OnOff Set messages, you need to have Generic OnOff Client model.
To set local elements, call manager.localElements = [...]
. The 5 Models mentioned above will be added automatically to the first Element. Each local model must have a ModelDelegate
that will handle incoming messages and provide mapping for opcodes of messages that such Model supports (receives).
For example, the Generic OnOff Client mentioned above may send Generic OnOff Get, Generic OnOff Set, Generic OnOff Set Unacknowledged and will receive Generic OnOff Status. The last, status message must be added to messageTypes
map of the Model's delegate, so that when a message with its opcode is received, the library knows which message type to instantiate. If you support any Server model, your ModelDelegate
must also reply to all acknowledged messages it can receive.
Without setting the local Model and specifying the message types, each message would be reported to the manager's delegate as UnknownMessage
.
Important: Received messages are delivered to the app in 2 ways: using the MeshNetworkManager.delegate
and to the ModelDelegate
. However, in order to receive the messages in the model, it needs to bound to the Application Key, and the message needs either target the Element's Unicast Address, a All Nodes (0xFFFF) address, or a Group Address to which that model is subscribed to.
let nordicCompanyId: UInt16 = 0x0059
let element = Element(name: "Primary Element", location: .unknown, models: [
Model(sigModelId: 0x1001, delegate: GenericOnOffClientDelegate()),
Model(vendorModelId: 0x0001, companyId: nordicCompanyId, delegate: SimpleOnOffDelegate())
])
meshNetworkManager.localElements = [element]
Important: Even if you don't have any Models (for example only want to allow Configuration Messages in the app) you have to set the localElements
property. In that case the Configuration Server and Client Models will not be properly initialized and you will be getting UnknownMessage
instead of proper Status message.
// Configuration when there are no additional models.
// This will create a single Element with 5 models mentioned above.
meshNetworkManager.localElements = []
Provisioning is a process of sending a Network Key to a new Unprovisioned Device in a more or less secure way. During provisioning, a Provisioner and the new provisioned Node will generate a common secret that will work as Device Key. Using this key the Provisioner may send and data to the Node privately, so that only this device will be able to decode the message. The first data sent to Node are its unique Unicast Address, and the Network Key.
To provision a device, scan for nearby Unprovisioned Devices (devices advertising with Service UUID: 0x1827). When a device is found and selected by the user, create UnprovisionedDevice(advertisingData:)
object and a ProvisioningBearer
(currently only PB GATT bearer is supported by the library). For default bearer you may use PBGattBearer
which implements ProvisioningBearer
protocol. To start provisioning, call manager.provision(unprovisionedDevice:over)
. This method returns a ProvisioningManager
, an object that will help you provision the device.
First, call identify(andAttractFor)
on the new manager to make the device blink or make noise. This will also request device capabilities. Set the Network Key and Unicast Address (the next available address is selected automatically) and, when the right device has been chosen, call provision(usingAlgorithm:publicKey:authenticationMethod)
to start the provisioning process. A delegate will be informed when provisioning is complete or has failed.
guard let bearer = PBGattBearer(target: peripheral),
let unprovisionedDevice = UnprovisionedDevice(advertisementData: advertisementData),
let provisioningManager = try? manager.provision(unprovisionedDevice: unprovisionedDevice, over: bearer) else {
return
}
provisioningManager.delegate = self
provisioningManager.networkKey = primaryNetworkKey
bearer.delegate = self
bearer.open()
Identification:
do {
try provisioningManager.identify(andAttractFor: 5) // Blink for 5 seconds
} catch {
bearer.close()
}
Provisioning:
do {
try provisioningManager.provision(usingAlgorithm: .fipsP256EllipticCurve,
publicKey: publicKey,
authenticationMethod: authenticationMethod)
} catch {
bearer.close()
}
See ScannerTableViewController
and ProvisioniongViewController
in the Sample App.
The manager's API contains number of methods for sending mesh messages, which can be divided into 3 groups. First group allows to send ConfigMessages
to a Configuration Server on a remote or local Node. Configuration messages are always signed using the device key and must be sent to a Unicast Address of the Primary Element of a Node. Second group allows to send other messages from any local Element to any remote or local Element, or a group or virtual address. These messages are signed using Application Key and will be delivered to all models on the Element with target Unicast Address or subscribed to the target group or virtual address, that have this Application Key bound. You may not send ConfigMessages
using this set of methods. The third group, which contains just a single method publish(_:fromModel:withTtl)
can be used to send publication from the local model.
Remember, that only the Nodes that know the Network Key with which a message is sent may relay the message. It is for example not possible to send a message over a Proxy connection signed with a Network Key not known to this Proxy Node.
meshNetworkManager.delegate = self
meshNetworkManager.send(message, to: targetModel)