Hi there, I got a forward of this email and so I'm adding my personal thoughts as responsible for the SoftDevice API for several years.
> -------- Forwarded Message -------- > Subject: > Re: Proposed nimble host API changes > Date: > Thu, 23 Jun 2016 14:35:49 -0700 > From: > chris collins mailto:[email protected] > Reply-To: > mailto:[email protected] > To: > mailto:[email protected] > > Hi all, > > In reviewing the nimble host API, I thought it would be a good idea to > compare it with another BLE host that is in wide use: the Nordic S130. > I used the S130 API reference > (https://developer.nordicsemi.com/nRF51_SDK/nRF51_SDK_v8.x.x/doc/8.1.0/s > 130/html/modules.html) > to compile the following list of differences. This list is just based > on my reading of the API reference and looking at some sample Nordic > applications; I may have gotten some details wrong. All corrections and > other feedback is greatly appreciated! I think this is a great idea, but you should probably use the latest SoftDevice API version 2.0.0 instead: http://infocenter.nordicsemi.com/index.jsp?topic=%2Fcom.nordic.infocenter.s132.api.v2.0.0%2Findex.html&cp=2_3_0_1_0 > > Thanks, > Chris > > **** Stack-to-application communication: > * S130: > * The stack pushes events onto a queue; the application pulls them > off. > > * Nimble: > * The application configures the stack with callback functions; the > stack executes the callbacks to pass information to the > application. > > * Thoughts: > * I like both approaches. Either approach can be wrapped to behave > like > the other if necessary. > The reason we went for pulled events and not callbacks is 2-fold: 1) The memory is provided by the application when calling sd_ble_evt_get(), so we don't need to worry about the app accessing SoftDevice memory which might be protected 2) Callbacks impose a threading model of some sort (i.e. you have a separate thread calling into your app). This was a problem for us since we wanted the SoftDevice to be compatible both with bare-metal apps and full OSs running on top of it. That said, your case is different. Have you thought about a mailbox/queue mechanism of some sort to isolate tasks perhaps? Since, in contrast with the SD, you control the userspace, this would be acceptable. > **** GATT service registration: > * S130: > * Service registration is done procedurally via sequential calls to > sd_ble_gatts_service_add(), sd_ble_gatts_characteristic_add(), and > similar. > > * Nimble: > * Data-driven table-based approach. The set of supported attributes > are expressed as a series of tables that resides in the C code. > > * Thoughts: > * I personally prefer nimble's approach, as I like to keep data > separate from code. However, this is just personal preference, > and I don't think there is a "right" answer. I am pretty sure > S130's procedural design would be easier to script against, which > makes me think nimble should support something similar. > Internally, nimble uses something similar to the S130's public > API; these functions could be exposed. Reasoning behind procedural design: we wanted to have all metadata in SD memory to provide memory protection. And since we're doing that and we already provide the VLOCK_STACK / VLOC_USER for the actual attribute values, we opted for the data to be procedurally added. It also has the advantage of being better suited for dynamic attribute tables that allow for attribute removal (though we never implemented this so far). > > **** GATT server reads and writes: > * S130: > * Requires each characteristic to be associated with a data buffer > specified by the application during registration. When a peer > performs a GATT operation on one of your characteristics, the > stack manipulates the contents of the buffer accordingly. > > * Nimble: > * The application associates a callback with each characteristic. > Whenever a GATT operation is performed on a characteristic, the > callback gets called. There is no buffer explicitly tied to a > characteristic; it is up to the callback to do the right thing > corresponding to the GATT operation being performed. > > * Thoughts: > * I prefer Nimble's approach, as it provides a more straightforward > means of supporting "dynamic" characteristics. By this, I mean it > is easy for the application to generate a characteristic's value > on demand when read. We sort of support both: if you enable authorization you get essentially the nimble approach and, if you have a constant attribute that is not gonna change then the app doesn't need to worry about incoming reads at all, which can be handy. That said, I think the nimble approach is more than reasonable. > **** GATT notifications and indications: > * S130: > * When a notification or indication is sent, the stack automatically > fills the message with the value of the associated characteristic. > It does this by reading the buffer associated with it. > > * Notifications and indications can only be sent if the peer has > subscribed to them. > > * Nimble: > * The application can either manually specify the contents of a > notification (ble_gattc_notify_custom()), or have the stack read > the contents of the associated characteristic > (ble_gattc_notify()). > > * Notifications can be sent to any peer at any time. Subscription > is not necessary. > > * Thoughts: > * I think the ability to send free-form notifications is important. > Notifications are the only way for a server to send unsolicited > data to a client, and giving the application more freedom in this > area opens up the possibility of using bluetooth as a generic > transport layer for application protocols. We have both approaches when it comes to data: you can either pass a NULL pointer to sd_ble_gatts_hvx() and the data that is currently present in the buffer will be sent, or you can pass a valid pointer and the stack will atomically set the data in the buffer and send the notification. Allowing notifications without the CCCD being set goes against the BLE spec and in general Nordic has always tried to enforce conformance. > > **** GATT client procedures: > * S130: > * Procedures which involve multiple sends (e.g., discovery > procedures, long reads, etc.) require repeated function calls by > the application. The stack informs the application of progress > via an event on the queue; the application continues the procedure > by calling the original function again with an updated set of > arguments. > > * Nimble: > * The stack manages procedure state machines internally. The > application is informed of progress via invocations of callback > functions. The application indicates whether the stack should > proceed with the procedure via the callback return code. > We did this so that applications would have full control of when and which packets go over the air. This allows apps to finely schedule traffic if there's a need. That said, this is changing with the addition of long ATT MTUs (>23) in SoftDevice 3.0.0. > **** CCCD persistence: > * S130: > * The application implements the persistence mechanism. > > * It is up to the application to populate the stack with CCCD state > after it has read it from flash. When a connection is > established, the application reads CCCD data from flash and passes > it to the stack via a call to sd_ble_gatts_sys_attr_set(). > > * The application decides when to persist CCCD state. The > application > requests CCCD state from the stack via a call to > sd_ble_gatts_sys_attr_get(). This is generally done immediately > after a device is disconnected. > > * Nimble: > * The application implements the persistence mechanism. > > * The stack tells the application to persist data via calls to the > store_write_cb callback. > > * The stack requests stored data from the application via calls to > the store_read_cb callback. CCCDs are tricky and we tried hard to make it easy for the app to keep track of the values. We also defer reads to CCCDs until they have been initialized by the app, since we have no way of knowing what they should contain before that. > > **** Security persistence: > * S130: > * The stack has no knowledge of persistence of security material. > The application specifies security data in stack function calls; > this data has presumably been restored from flash. > > * Nimble: > * The stack uses the same persistence mechanism as with CCCD state: > calls to the store_read_cb and store_write_cb callbacks. The > stack decides when data should be persisted and restored. > The reasoning behind that is that the specification in 4.0 didn't clearly state what "bonded" really meant, and we had a design decision not to access flash from within the SoftDevice. Instead, we leave all key-related decisions to the app to support multiple approaches. > **** Timeouts > * S130: > * Timeouts are 16-bit values expresssed in seconds. > > * Nimble: > * Timeouts are 32-bit values expressed in milliseconds. > > **** Privacy > * S130: > * Application specifies a single own-address-type for all > connections. > > * Nimble: > * Application specifies an own-address-type per connection. > > * Thoughts: > * It isn't clear to me how the S130 generates a resolvable private > address if the application never specifies an identity address to > use. > This is going to see a major overhaul in s13x 3.0.0, where we will introduce controller-based privacy. In 2.0.0 there are no identity addresses, and instead we base everything on a single local IRK. > **** Advertising and response data > * S130: > * Application specifies the raw advertising data and response data > in buffers. > > * Nimble: > * Application specifies values of fields (e.g., name, service > UUIDs); the stack converts these into the raw advertising data. > > * Thoughts: > * Nimble's approach is more user-friendly but also more restrictive. > I think it is good to allow the application to fill the data > buffer itself, and nimble should provide this capability. This has proven like a good decision on our side over the years. People tend to use advertising data very loosely and we have relaxed more and more the checks and requirements over time. A layered approach is better I believe: a function to set the raw data, and another one (in our case in the SDK) to help format it. > > **** Advertising > * S130: > * Accepts the HCI Advertising_Type as a parameter. > > * Nimble: > * Slightly higher level; accepts connectable-mode and > discoverable-mode parameters and converts these into the HCI > Advertising_Type. > Again, advertising is so fundamental that we wanted to give apps low-level control over it. The SDK above it then decides how to implement the modes. > **** Querying stack for connection properties > * S130: > * Several functions. Each function retrieves a different set of > information about the specified connection (e.g., > sd_ble_gap_conn_sec_get() and sd_ble_gap_rssi_get()). > > * Nimble: > * A single function--ble_gap_find_conn()--retrieves all information > about the specified connection. > > **** Connection termination > * S130: > * The termination function (sd_ble_gap_disconnect()) accepts an HCI > reason code as a parameter. > > * Nimble: > * The termination function (ble_gap_terminate()) always temrinates > * with the same reason: remote user terminated connection. > > * Thoughts: > * The S130 gets this right. The application should be able to > specify the reason for the disconnect. Yes, this again has proven to be the right decision over time. Some people use disconnection codes as indicators to the peer. Carles
