@abandonware/noble源码解析

115 阅读5分钟

test.js

const noble = require('./index');
noble.startScanning(); // any service UUID, no duplicates
noble.startScanning([], true);
noble.startScanning([], true,()=>{});

lib/noble.js

function Noble (bindings) {

this.initialized = false;

  


this.address = 'unknown';

this._state = 'unknown';

this._bindings = bindings;

this._peripherals = {};

this._services = {};

this._characteristics = {};

this._descriptors = {};

this._discoveredPeripheralUUids = {};

  


this._bindings.on('stateChange', this.onStateChange.bind(this));

this._bindings.on('addressChange', this.onAddressChange.bind(this));

this._bindings.on('scanParametersSet', this.onScanParametersSet.bind(this));

this._bindings.on('scanStart', this.onScanStart.bind(this));
}

Noble.prototype.onScanStart = function (filterDuplicates) {

debug('scanStart');

this.emit('scanStart', filterDuplicates);

};

lib/noble.js

  • startScanning函数
const startScanning = function (serviceUuids, allowDuplicates, callback) {

if (typeof serviceUuids === 'function') {

this.emit('warning', 'calling startScanning(callback) is deprecated');

}

  


if (typeof allowDuplicates === 'function') {

this.emit('warning', 'calling startScanning(serviceUuids, callback) is deprecated');

}

  


const scan = function (state) {

if (state !== 'poweredOn') {

const error = new Error(`Could not start scanning, state is ${state} (not poweredOn)`);

  


if (typeof callback === 'function') {

callback(error);

} else {

throw error;

}

} else {

if (callback) {

this.once('scanStart', filterDuplicates => {

callback(null, filterDuplicates);

});

}

  


this._discoveredPeripheralUUids = {};

this._allowDuplicates = allowDuplicates;

  


this._bindings.startScanning(serviceUuids, allowDuplicates);

}

};

  


// if bindings still not init, do it now

if (!this.initialized) {

this.initialized = true;

  


this._bindings.init();

  


this.once('stateChange', scan.bind(this));

} else {

scan.call(this, this._state);

}

};

  


Noble.prototype.startScanning = startScanning;

Noble.prototype.startScanningAsync = function (serviceUUIDs, allowDuplicates) {

return util.promisify((callback) => this.startScanning(serviceUUIDs, allowDuplicates, callback))();

};
  • 其中 this._bindings.startScanning(serviceUuids, allowDuplicates);会执行lib/mac/src/noble_mac.mm文件中NobleMac::Scan方法,函数代码如下:
NobleMac::InstanceMethod("startScanning", &NobleMac::Scan);
Napi::Value NobleMac::Scan(const Napi::CallbackInfo& info) {。。。};
  • 其中,this._bindings.init();
NobleMac::InstanceMethod("init", &NobleMac::Init),
。。。
Napi::Object Init(Napi::Env env, Napi::Object exports) {

// 创建一个新的字符串对象,内容为"NobleMac"

Napi::String name = Napi::String::New(env, "NobleMac");

// 将NobleMac类创建的对象设置到exports对象中,键名为"NobleMac"

exports.Set(name, NobleMac::GetClass(env));

// 返回修改后的exports对象

return exports;

}

lib/distributed/bindings.js ???


const NobleBindings = function () {

this._wss = new WebSocketServer({

port: 0xB1e

});

this._startScanCommand = null;

this._peripherals = {};

this._wss.on('connection', this._onConnection.bind(this));

this.on('close', this._onClose.bind(this));

this.on('message', this._onMessage.bind(this)); 


process.nextTick(() => {

this.emit('stateChange', 'poweredOff');

});

};

NobleBindings.prototype.startScanning = function (serviceUuids, allowDuplicates) {

this._startScanCommand = {

action: 'startScanning',

serviceUuids,

allowDuplicates

};

lib/mac/binding.gyp

-binding.gyp是交叉编译的打包配置

{

'targets': [

{

'target_name': 'binding',

'sources': [ 'src/noble_mac.mm', 'src/napi_objc.mm', 'src/ble_manager.mm', 'src/objc_cpp.mm', 'src/callbacks.cc' ],

'include_dirs': ["<!@(node -p \"require('node-addon-api').include\")"],

'dependencies': ["<!(node -p \"require('node-addon-api').gyp\")"],

'cflags!': [ '-fno-exceptions' ],

'cflags_cc!': [ '-fno-exceptions' ],

'cflags+': ['-fvisibility=hidden'],

'xcode_settings': {

'GCC_SYMBOLS_PRIVATE_EXTERN': 'YES', # -fvisibility=hidden

'GCC_ENABLE_CPP_EXCEPTIONS': 'YES',

'CLANG_CXX_LIBRARY': 'libc++',

'MACOSX_DEPLOYMENT_TARGET': '10.7',

'OTHER_CFLAGS': [

'-fobjc-arc',

],

},

'link_settings': {

'libraries': [

'$(SDKROOT)/System/Library/Frameworks/CoreBluetooth.framework',

]

},

'product_dir': '../lib/mac/native',

}

]

}
  • 打包完成后生成文件 lib/mac/native/binding.node

lib/mac/bindings.js

const { EventEmitter } = require('events'); // 引入Node.js的EventEmitter模块,用于创建事件发射器

const { inherits } = require('util'); // 引入Node.js的util模块中的inherits方法,用于实现基于原型的继承

const { resolve } = require('path'); // 引入Node.js的path模块中的resolve方法,用于解析文件路径

  


// 解析当前文件所在目录的上两级目录,赋值给变量dir

// 项目的根目录 ,对于本机 = /Users/caiyunsun/vs2024proj/abandonware_noble

const dir = resolve(__dirname, '..', '..');

console.log(dir);

  


// 引入node-gyp-build模块,传入dir作为参数,并将返回值赋值给变量binding

const binding = require('node-gyp-build')(dir);

console.log(binding); // {NobleMac:f}

  


// 从binding对象中获取NobleMac属性,并将其赋值给变量NobleMac

const { NobleMac } = binding;

  


// 使用inherits方法实现NobleMac基于EventEmitter的继承,使NobleMac具有EventEmitter的属性和方法

inherits(NobleMac, EventEmitter);
console.log(NobleMac); // 2
  


// 将NobleMac导出为模块的公开接口,以便在其他文件中通过require引入并使用

module.exports = NobleMac;
  • 打印binding如下: image.png

  • 打印console.log(NobleMac)如下:

image.png

lib/mac/src/noble_mac.h

  • bindings.js指出实际导出NobleMac,而这里是NobleMac的声明文件。
#include <napi.h>
#include "ble_manager.h"

class NobleMac : public Napi::ObjectWrap<NobleMac>

{

public:
NobleMac(const Napi::CallbackInfo&);
Napi::Value Init(const Napi::CallbackInfo&);
Napi::Value Scan(const Napi::CallbackInfo&);
Napi::Value StopScan(const Napi::CallbackInfo&);
static Napi::Function GetClass(Napi::Env); 


private:
BLEManager* manager;
};

lib/mac/src/noble_mac.mm

// NobleMac类的构造函数

NobleMac::NobleMac(const Napi::CallbackInfo& info) : ObjectWrap(info) {

}



// startScanning(serviceUuids, allowDuplicates)

Napi::Value NobleMac::Scan(const Napi::CallbackInfo& info) {

// 检查BLEManager对象是否存在

CHECK_MANAGER()

// 将传入的第一个参数转换为UUID数组

NSArray* array = getUuidArray(info[0]);

// 将传入的第二个参数转换为布尔值,如果没有传入,则使用默认值NO

// default value NO

auto duplicates = getBool(info[1], NO);

// 开始扫描,使用转换后的UUID数组和布尔值作为参数

[manager scan:array allowDuplicates:duplicates];

// 返回空值

return Napi::Value();

}

Napi::Function NobleMac::GetClass(Napi::Env env) {

// 定义一个名为"NobleMac"的类,并添加其方法
return DefineClass(env, "NobleMac", {

// 定义"init"实例方法,绑定到NobleMac::Init函数
NobleMac::InstanceMethod("init", &NobleMac::Init),

// 定义"startScanning"实例方法,绑定到NobleMac::Scan函数
NobleMac::InstanceMethod("startScanning", &NobleMac::Scan),

NobleMac::InstanceMethod("stopScanning", &NobleMac::StopScan),


});

}

关键是 :

// 开始扫描,使用转换后的UUID数组和布尔值作为参数
[manager scan:array allowDuplicates:duplicates];

执行BLEManager类的scan方法,传入参数1是UUID的数组array,参入参数2是allowDuplicates:duplicates。其中allowDuplicates是函数定义时的形参名称,duplicates是实参。

lib/mac/src/ble_manager.h

  • BLEManager类声明文件。
  • BLEManager类以组合方式使用Emit,用于回调函数。
// 导入Foundation和CoreBluetooth框架,它们是Apple提供的基础库和蓝牙通信库

#import <Foundation/Foundation.h>

#import <CoreBluetooth/CoreBluetooth.h>

  


// 导入dispatch库,用于多线程编程

#include <dispatch/dispatch.h>

  


// 导入自定义的callbacks.h头文件

#include "callbacks.h"

  


// 声明BLEManager类,并使其遵循CBCentralManagerDelegate和CBPeripheralDelegate协议

@interface BLEManager : NSObject <CBCentralManagerDelegate, CBPeripheralDelegate>

{

    // 定义Emit类型的成员变量emit,用于回调函数

    Emit emit;

    // 定义bool类型的成员变量pendingRead,表示是否有待处理的读取请求

    bool pendingRead;

}

  


// 定义CBCentralManager类型的属性centralManager,用于管理蓝牙连接

@property(strong) CBCentralManager *centralManager;

  


// 定义dispatch_queue_t类型的属性dispatchQueue,用于处理并发操作

@property dispatch_queue_t dispatchQueue;

  


// 定义NSMutableDictionary类型的属性peripherals,用于存储已发现的蓝牙外设信息

@property NSMutableDictionary *peripherals;

  


// 初始化方法,接收两个参数:receiver为Napi::Value类型,表示JavaScript的接收对象;callback为Napi::Function类型,表示JavaScript的回调函数

- (instancetype)init:(const Napi::Value &)receiver with:(const Napi::Function &)callback;

  


// 扫描蓝牙外设的方法,接收两个参数:serviceUUIDs为NSString数组,表示需要扫描的服务UUID;allowDuplicates为BOOL类型,表示是否允许重复扫描

- (void)scan:(NSArray<NSString *> *)serviceUUIDs allowDuplicates:(BOOL)allowDuplicates;

  


// 停止扫描蓝牙外设的方法

- (void)stopScan;

@end

lib/mac/src/ble_manager.mm

  • BLEManager类的scan方法具体实现,形参2个,第一个是NSArray<NSString*> *类型的,形参名是serviceUUIDs;第二个参数形参名是allowDuplicates,类型是BOOL。
  • 在方法最后执行emit.ScanState(true) 执行回调函数ScanState,参数是true;

- (void)scan: (NSArray<NSString*> *)serviceUUIDs allowDuplicates: (BOOL)allowDuplicates {

NSMutableArray* advServicesUuid = [NSMutableArray arrayWithCapacity:[serviceUUIDs count]];

[serviceUUIDs enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {

[advServicesUuid addObject:[CBUUID UUIDWithString:obj]];

}];

NSDictionary *options = @{CBCentralManagerScanOptionAllowDuplicatesKey:[NSNumber numberWithBool:allowDuplicates]};

[self.centralManager scanForPeripheralsWithServices:advServicesUuid options:options];

emit.ScanState(true);

}
- (void)stopScan {

[self.centralManager stopScan];

emit.ScanState(false);

}

lib/mac/src/callbacks.h

  • 调用noble.startScanning之后,会用回调事件ScanState的方式通知回调结果。
  • callbacks.h定义所有的回调事件。
class Emit

{

public:

// 定义一个Wrap函数,用于包装回调函数

void Wrap(const Napi::Value &receiver, const Napi::Function &callback);

  


// 定义一个RadioState函数,用于处理无线电状态

void RadioState(const std::string &status);

  


// 定义一个ScanState函数,用于控制扫描的开始和结束

void ScanState(bool start);

// 定义一个Scan函数,用于扫描到周边设备

void Scan(const std::string &uuid, int rssi, const Peripheral &peripheral);



protected:

// 成员变量为受保护的访问修饰符,只能在类内部和派生类中访问

std::shared_ptr<ThreadSafeCallback> mCallback;

// 指向线程安全回调对象的智能指针,用于处理回调事件

};

lib/mac/src/callbacks.cc

void Emit::ScanState(bool start) {

mCallback->call([start](Napi::Env env, std::vector<napi_value>& args) {

// emit('scanStart') emit('scanStop')

args = { _s(start ? "scanStart" : "scanStop") };

});

}

void Emit::Scan(const std::string& uuid, int rssi, const Peripheral& peripheral) {

。。。。



// emit('discover', deviceUuid, address, addressType, connectable, advertisement, rssi);

args = { _s("discover"), _u(uuid), _s(address), toAddressType(env, addressType), _b(connectable), advertisment, _n(rssi) };

});

}

日志打印

const noble = require('../index');
// noble
console.log(noble);
// 以下是日志内容
执行node ./examples/advertisement-discovery.js脚本,
控制台打印日志为:

Noble {
  initialized: false,
  address: 'unknown',
  _state: 'unknown',
  _bindings: NobleMac {
    _events: [Object: null prototype] {
      stateChange: [Function: bound ],
      addressChange: [Function: bound ],
      scanParametersSet: [Function: bound ],
      scanStart: [Function: bound ],
      scanStop: [Function: bound ],
      discover: [Function: bound ],
      connect: [Function: bound ],
      disconnect: [Function: bound ],
      rssiUpdate: [Function: bound ],
      servicesDiscover: [Function: bound ],
      servicesDiscovered: [Function: bound ],
      includedServicesDiscover: [Function: bound ],
      characteristicsDiscover: [Function: bound ],
      characteristicsDiscovered: [Function: bound ],
      read: [Function: bound ],
      write: [Function: bound ],
      broadcast: [Function: bound ],
      notify: [Function: bound ],
      descriptorsDiscover: [Function: bound ],
      valueRead: [Function: bound ],
      valueWrite: [Function: bound ],
      handleRead: [Function: bound ],
      handleWrite: [Function: bound ],
      handleNotify: [Function: bound ],
      onMtu: [Function: bound ]
    },
    _eventsCount: 25
  },
  _peripherals: {},
  _services: {},
  _characteristics: {},
  _descriptors: {},
  _discoveredPeripheralUUids: {},
  _events: [Object: null prototype] {
    warning: [Function (anonymous)],
    newListener: [Function (anonymous)]
  },
  _eventsCount: 2
}

  • 日志中Noble._bindings._events共25个函数,对应lib/noble.js中绑定的25个方法。
  • 当lib/noble.js执行this._bindings.init();函数后,会在Node下增加6个函数。

image.png

build/gyp-mac-tool交叉编译配置

image.png

更改代码后执行编译指令

// 代码检查

npm run lint

// 代码检查

npm run lint-fix

// 构建清理

npm run clean

// 代码预构建

npm run prebuild

// 代码预构建 - mac系统

npm run prebuild-darwin

// 代码编译

npm run rebuild

// 测试用例代码测试 ,执行test/test.js

npm run test