1. Plugin Integration Steps
1.1 Import the Plugin to the Project
1.1.1 Via CocoaPods
//In the project Podfile, add the plugin and specify the version.
pod 'QNSDK', '2.29.0' //The version number can be replaced with the latest version shown on GitHub.
Github address: sdk-ios-demo
1.1.2 Via Carthage
//In the project Cartfile, add the plugin dependency.
github "https://github.com/YolandaQingniu/sdk-ios-demo.git"
1.1.3 Manual Import
1. Place the .a file in the specified location in the project.
2. Add the SDK path in [TARGETS] -> [Build Setting] -> [Search Paths] -> [LibrarySearch Paths].
3. Configure the linker in [TARGETS] -> [Build Setting] -> [Linking] -> [Other Linker Flags], and add one of -ObjC, -all_load, or -force_load [SDK path].
1.2 Configure Bluetooth Permission Usage Description
In the project's Info.plist file, add the Privacy - Bluetooth Peripheral Usage Description and Privacy - Bluetooth Always Usage Description keys to describe Bluetooth usage.
1.3 Import the Plugin API Header File
#import <QNSDK/QNDeviceSDK.h>
1.4 Initialize the Plugin
//Get the authorization file path in the project. The parameters are the qn file name and file extension.
NSString *file = [[NSBundle mainBundle] pathForResource:@"123456789" ofType:@"qn"];
//Instantiate the plugin. QNBleApi is a singleton object.
QNBleApi *bleApi = [QNBleApi sharedBleApi];
//Get plugin authorization. The parameters are appid and the qn file path.
[bleApi initSdk:@"123456789" firstDataFile:file callback:^(NSError *error) {
if(error) {
//Exception callback
}
}];
1.5 Listen for System Bluetooth State Changes (QNBleStateListener)
//Set the listener for system Bluetooth state.
bleApi.bleStateListener = self;
/*
typedef NS_ENUM(NSUInteger, QNBLEState) {
QNBLEStateUnknown = 0, //Unknown state
QNBLEStateResetting = 1, //System Bluetooth is resetting
QNBLEStateUnsupported = 2, //Bluetooth is not supported by the system
QNBLEStateUnauthorized = 3, //Bluetooth usage is not authorized
QNBLEStatePoweredOff = 4, //System Bluetooth is powered off
QNBLEStatePoweredOn = 5, //System Bluetooth is powered on
};
*/
- (void)onBleSystemState:(QNBLEState)state {
}
1.6 Listen for SDK Logs (QNLogProtocol)
//Set the listener for SDK logs.
bleApi.logListener = self;
- (void)onLog:(nonnull NSString *)log {
//Logs output by the SDK are returned here.
}
2. Start Bluetooth Scanning and Obtain the Device Object
2.1 Start Bluetooth Scanning
[bleApi startBleDeviceDiscovery:^(NSError *error) {
}];
2.2 Listen for Scan State Changes (QNBleDeviceDiscoveryListener)
//Set the listener for device scanning.
bleApi.discoveryListener = self;
//This function is called when scanning starts.
- (void)onStartScan {
}
//This function is called when scanning stops.
- (void)onStopScan {
}
//This function is called when a device is discovered after scanning starts. Only supported devices are returned here.
- (void)onDeviceDiscover:(QNBleDevice *)device {
//When subsequently calling the SDK method to initiate device connection, pass in this device.
}
3. Connect the Device
3.1 Start Device Connection
//It is recommended to stop scanning first.
[_bleApi stopBleDeviceDiscorvery:^(NSError *error) { }];
//Set the scale unit.
QNConfig *sdkConfig = [QNConfig sharedConfig];
sdkConfig.unit = QNUnitKG;
[sdkConfig save];
//Start connecting to the scale device.
/*
device: The Bluetooth device to connect to, namely the (QNBleDevice *)device returned by the scan listener method onDeviceDiscover.
config: Configuration for connecting to the user scale device. See the example below.
*/
[bleApi connectUserScaleDevice:device config:config callback:^(NSError *error) {
}];
//config parameter example:
QNUserScaleConfig *config = [[QNUserScaleConfig alloc] init];
//User information for this measurement.
QNUser *user = [[QNUser alloc] init];
user.userId = "";//Unique user identifier in business logic, used to distinguish users in your business.
user.height = 170;//User height, unit cm.
user.gender = @"male";//User gender. "male" for male, "female" for female.
user.birthday = [NSDate dateWithTimeIntervalSince1970:631199317];//User birthday. The input parameter is a timestamp in seconds.
user.hmac = lastValidEightHmac; //Note: This must be the hmac of the user's most recent previous measurement data where body fat percentage is greater than 0 and the device is the same type of eight-electrode device. Whether the type is the same can be determined by the QNScaleData.newEightModel property.
/*
There are generally two measurement modes: user management mode and guest mode. Which mode to use depends on your app's business scenario.
*/
//1. Guest mode
/*
Guest mode can be understood as temporary scale use. The user information passed in only takes effect during this Bluetooth connection, and the scale will not save it.
Note: The config.isVisitor field and user.index are mutually exclusive. isVisitor has higher priority. In guest mode, user.index can keep its default value. user.index is designed for user management mode.
*/
config.isVisitor = YES; //Whether to use guest mode. YES - guest; NO - non-guest, namely user management mode.
config.curUser = user;
//2. User management mode
//2.1 Register user
/*
User to be registered on the scale.
Before registering a user, check whether the scale-side user list is full. The scale can store up to 8 users. You can determine this by QNBleDevice.registeredUserNum.
After scale-side user registration succeeds, the SDK obtains user.index returned by the scale. See the registerUserComplete callback function.
After scale-side user registration succeeds, real-time weight data and result data from the scale can be received.
*/
config.curUser = user;
//2.2 Access user [registration and access cannot be performed at the same time]
/*
To access a scale-side user, namely a user already existing on the scale, pass in user.index for scale-side verification. If it does not match the data stored on the scale, access will fail.
The user information passed in when accessing a user updates the corresponding information saved on the scale. For example, if the scale saved height 170 cm for this user and 171 cm is passed in this time, the scale will update it to 171 cm.
After scale-side user access succeeds, real-time weight data and result data from the scale can be received.
*/
user.index = 1; //User slot, namely the scale-side user index. Valid range: [1,8]. It indicates which position on the scale is being accessed. This value is returned by the scale when registering a user. See the registerUserComplete callback function.
config.curUser = user;
//2.3 Delete users [The SDK deletes users first, then registers or accesses users.]
//List of registered scale-side users. During this connection, the scale-side users not included in this array will be deleted from the scale.
NSMutableArray<QNUser *> *registeredUserList = [NSMutableArray array];
config.userlist = registeredUserList;
3.2 Listen for Device Connection State Changes (QNBleConnectionChangeListener)
//Set the listener for device connection state changes.
bleApi.connectionChangeListener = self;
//Callback when the device is connecting.
- (void)onConnecting:(QNBleDevice *)device {
}
//Callback when the device is connected successfully.
- (void)onConnected:(QNBleDevice *)device {
}
//The device communication service search is complete. No logic handling is usually required here.
- (void)onServiceSearchComplete:(QNBleDevice *)device {
}
//Callback when device connection fails, returning failure exception information.
- (void)onConnectError:(QNBleDevice *)device error:(NSError *)error {
}
//The device is ready for interaction, meaning the corresponding operation commands can be sent to the device.
- (void)onStartInteracting:(QNBleDevice *)device {
}
//The device is disconnecting from Bluetooth.
- (void)onDisconnecting:(QNBleDevice *)device {
}
//The device has disconnected.
- (void)onDisconnected:(QNBleDevice *)device {
}
4. Obtain Device Data
4.1 Listen for Device Data Interaction (QNScaleDataListener)
//Set the listener for device data.
bleApi.dataListener = self;
//Callback when scale-side user registration succeeds, returning the user slot assigned by the scale. This applies to user management mode; guest mode can ignore this method.
- (void)registerUserComplete:(QNBleDevice *)device user:(QNUser *)user {
//The slot assigned by the scale during user registration. The app should associate and save this slot with the user ID and device MAC. This index is required next time the user connects to this device to access the scale-side user for measurement.
int index = user.index;
}
/* Scale interaction process state changes
typedef NS_ENUM(NSInteger, QNScaleState) {
QNScaleStateDisconnected = 0, //Not connected
QNScaleStateLinkLoss = -1, //Connection lost
QNScaleStateConnected = 1, //Connected
QNScaleStateConnecting = 2, //Connecting
QNScaleStateDisconnecting = 3, //Disconnecting
QNScaleStateStartMeasure = 4, //Measuring
QNScaleStateRealTime = 5, //Measuring weight
QNScaleStateBodyFat = 7, //Measuring bioelectrical impedance
QNScaleStateMeasureCompleted = 9, //Measurement completed
}; Only these states need attention.
*/
- (void)onScaleStateChange:(QNBleDevice *)device scaleState:(QNScaleState)state {
}
/* Scale-side behavior state changes
typedef NS_ENUM(NSInteger, QNScaleEvent) {
QNScaleEventRegistUserSuccess = 4, //User registration succeeded
QNScaleEventRegistUserFail = 5, //User registration failed
QNScaleEventVisitUserSuccess = 6, //User access succeeded
QNScaleEventVisitUserFail = 7, //User access failed
QNScaleEventDeleteUserSuccess = 8, //User deletion succeeded
QNScaleEventDeleteUserFail = 9, //User deletion failed
QNScaleEventSyncUserInfoSuccess = 10, //User information sync succeeded
QNScaleEventSyncUserInfoFail = 11, //User information sync failed
QNScaleEventUpdateIdentifyWeightSuccess = 12, //User identification weight update succeeded
QNScaleEventUpdateIdentifyWeightFail = 13, //User identification weight update failed
}; Only these states need attention.
*/
- (void)onScaleEventChange:(QNBleDevice *)device scaleEvent:(QNScaleEvent)scaleEvent {
}
/*
Real-time weight callback during device measurement.
@param weight Real-time weight, unit kg.
*/
- (void)onGetUnsteadyWeight:(QNBleDevice *)device weight:(double)weight {
}
/*
Measurement data callback when device measurement is complete.
@param scaleData Measurement data.
*/
- (void)onGetScaleData:(QNBleDevice *)device data:(QNScaleData *)scaleData {
//Obtain the complete measurement data after measurement is complete. For new-solution eight-electrode devices, determine whether this measurement data is abnormal.
if (scaleData.newEightModel == 1) { //New-solution eight-electrode device.
//(New-solution eight-electrode only) Whether this measurement is abnormal. 0 - normal; 1 - abnormal.
if (scaleData.eightIsAbnormal == 1) {
//(New-solution eight-electrode only) Abnormal reason for this measurement. 0 - normal; 1 - hand abnormal; 2 - leg abnormal; 3 - both hand and foot abnormal.
NSInteger reasonMask = scaleData.eightReasonMask;
//According to the abnormal reason, you can prompt the user to remeasure.
return;
}
}
NSDate *measureData = scaleData.measureTime;//Measurement time
double weight = scaleData.weight;//Measured weight
NSArray <QNScaleItemData *> *allTarget = [scaleData getAllItem];
for (QNScaleItemData *item in allTarget) {
item.name //Indicator name
item.type //Indicator type, see QNScaleType
item.value //Indicator value. Confirm the precision of this value according to QNValueType.
}
/*
At the business layer, you can also perform secondary judgment on this measurement data, such as whether body fat was measured.
Alternatively, compare it with the user's most recent previous measurement data, such as weight/body fat. If the difference is outside a business threshold, you can also prompt the user to remeasure.
If the measurement data ultimately needs to be saved, the business logic must additionally save the hmac and newEightModel fields in the QNScaleData class for judgment and input parameters during the next connection and measurement.
*/
}
/*
Callback for stored data of the currently accessed user and unknown stored data.
@param storedDataList Stored data list.
*/
- (void)onGetStoredScale:(QNBleDevice *)device data:(NSArray <QNScaleStoreData *> *)storedDataList {
/*
Use the isDataComplete property in the QNScaleStoreData object to determine whether the data is unknown stored data. false means unknown stored data, and true means known stored data.
1. In guest mode, all data is unknown stored data.
2. In user management mode, there is known stored data, namely stored data belonging to the currently accessed user, and unknown stored data.
3. For unknown measurement data, you can notify relevant app users for claiming, meaning the app user chooses whether this data belongs to them.
4. Converting stored data to measurement data has two steps:
4.1 Set the owner of the stored data. This step is not required for known stored data. [storeData setUser:<#(QNUser *)#>];
4.2 Convert the stored data to measurement data. [storeData generateScaleDataWithLastEightHmac: lastValidEightHmac]
Note: lastValidEightHmac is the hmac of the data owner's most recent previous measurement data where body fat percentage is greater than 0 and the device is the same type of eight-electrode device. Whether the type is the same can be determined by the QNScaleData.newEightModel property.
*/
NSMutableArray<QNScaleStoreData *> *unknowStoreDataList = [NSMutableArray array];
for (QNScaleStoreData *storeData in storedDataList) {
if(!storeData.isDataComplete){
[unknowStoreDataList addObject:storeData];
} else {
NSString *lastValidEightHmac = @"xxxxxx";
QNScaleData *scaleData = [storeData generateScaleDataWithLastEightHmac: lastValidEightHmac];
NSDate *measureData = scaleData.measureTime;//Measurement time
double weight = scaleData.weight;//Measured weight
NSArray <QNScaleItemData *> *allTarget = [scaleData getAllItem];
for (QNScaleItemData *item in allTarget) {
item.name //Indicator name
item.type //Indicator type, see QNScaleType
item.value //Indicator value. Confirm the precision of this value according to QNValueType.
}
}
}
}
5. Actively Disconnect the Device
//If needed, you can actively call the method to disconnect the device.
[bleApi disconnectDevice:nil callback:^(NSError *error) {
}];
6. Data Calculation
6.1 Recalculate Indicators, Also Applicable to Generating Measurement Data from Stored Data
/// Recalculate data indicators.
/// @param user Target user for recalculating data indicators.
/// @param hmac hmac of the data to recalculate.
/// @param lastEightHmac hmac of the user's most recent previous measurement data, required for eight-electrode device fitting.
/// @param callback Callback.
- (QNScaleData *)calculateScaleDataByHmac:(QNUser *)user hmac:(NSString *)hmac lastEightHmac:(nullable NSString *)lastEightHmac callback:(QNResultCallback)callback;
Usage example:
//User information for the target user.
QNUser *user = [[QNUser alloc] init];
user.height = 170;//User height, unit cm.
user.gender = @"male";//User gender. "male" for male, "female" for female.
user.birthday = [NSDate dateWithTimeIntervalSince1970:631199317];//User birthday. Pass in a timestamp in seconds.
//hmac is the hmac of the data to recalculate. It can be the hmac of stored data, for the scenario of generating measurement data from stored data, or the hmac of measurement data, for the scenario of recalculating indicators for measurement data.
//lastValidEightHmac is the hmac of the target user's most recent previous measurement data where body fat percentage is greater than 0 and the device is the same type of eight-electrode device. Whether the type is the same can be determined by the QNScaleData.newEightModel property.
QNScaleData scaleData = [bleApi calculateScaleDataByHmac:user hmac:hmac lastEightHmac:lastValidEightHmac callback:^(NSError *error) {
//If input parameter validation fails, an error is reported here, and the corresponding SDK log is printed.
}];