Skip to content

Commit

Permalink
feat: Add methods for all BLE interactions
Browse files Browse the repository at this point in the history
  • Loading branch information
richardhopton committed Mar 29, 2023
1 parent 97d7018 commit 89f0aa2
Show file tree
Hide file tree
Showing 3 changed files with 187 additions and 21 deletions.
166 changes: 166 additions & 0 deletions lib/connection.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,52 @@ const isBase64 = (payload) => new RegExp(base64Regex, 'gi').test(payload);
const base64Decode = (message) =>
isBase64(message) ? Buffer.from(message, 'base64').toString('ascii') : message;

const uuidRegex = new RegExp(
/([a-f0-9]{8})([a-f0-9]{4})([a-f0-9]{4})([a-f0-9]{4})([a-f0-9]{12})/
);
const uuidDecode = (segments) =>
segments
.map((segment) => BigInt(segment).toString(16).padStart(16, '0'))
.join('')
.replace(uuidRegex, '$1-$2-$3-$4-$5');

const mapMessageByType = (type, obj) => {
switch (type) {
case 'SubscribeLogsResponse': {
// decode message to string
const message = base64Decode(obj.message);
return { ...obj, message };
}
case 'BluetoothLEAdvertisementResponse': {
// decode name to string
const name = base64Decode(obj.name);
return { ...obj, name };
}
case 'BluetoothGATTGetServicesResponse': {
// decode uuidList to uuid string
const { servicesList, ...rest } = obj;
return {
...rest,
servicesList: servicesList.map(
({ uuidList, characteristicsList, ...rest }) => ({
uuid: uuidDecode(uuidList),
...rest,
characteristicsList: characteristicsList.map(
({ uuidList, descriptorsList, ...rest }) => ({
uuid: uuidDecode(uuidList),
...rest,
descriptorsList: descriptorsList.map(
({ uuidList, ...rest }) => ({
uuid: uuidDecode(uuidList),
...rest,
})
),
})
),
})
),
};
}
default:
return obj;
}
Expand Down Expand Up @@ -329,6 +368,133 @@ class EsphomeNativeApiConnection extends EventEmitter {
switchCommandService(data) {
Entities.Switch.commandService(this, data);
}
subscribeBluetoothAdvertisementService() {
if (!this.connected) throw new Error(`Not connected`);
if (!this.authorized) throw new Error(`Not authorized`);
this.sendMessage(new pb.SubscribeBluetoothLEAdvertisementsRequest());
}
unsubscribeBluetoothAdvertisementService() {
if (!this.connected) throw new Error(`Not connected`);
if (!this.authorized) throw new Error(`Not authorized`);
this.sendMessage(new pb.UnsubscribeBluetoothLEAdvertisementsRequest());
}
async connectBluetoothDeviceService(address) {
if (!this.connected) throw new Error(`Not connected`);
if (!this.authorized) throw new Error(`Not authorized`);
return await this.sendMessageAwaitResponse(
new pb.BluetoothDeviceRequest([address]),
'BluetoothDeviceConnectionResponse',
10
);
}
async disconnectBluetoothDeviceService(address) {
if (!this.connected) throw new Error(`Not connected`);
if (!this.authorized) throw new Error(`Not authorized`);
return await this.sendMessageAwaitResponse(
new pb.BluetoothDeviceRequest([
address,
pb.BluetoothDeviceRequestType
.BLUETOOTH_DEVICE_REQUEST_TYPE_DISCONNECT,
]),
'BluetoothDeviceConnectionResponse'
);
}
async listBluetoothGattServicesService(address) {
if (!this.connected) throw new Error(`Not connected`);
if (!this.authorized) throw new Error(`Not authorized`);
const message = new pb.BluetoothGATTGetServicesRequest([address]);

const servicesList = [];
const onMessage = (message) => {
if (message.address === address)
servicesList.push(...message.servicesList);
};
this.on('message.BluetoothGATTGetServicesResponse', onMessage);
await this.sendMessageAwaitResponse(
message,
'BluetoothGATTGetServicesDoneResponse'
).then(
() => {
this.off('message.BluetoothGATTGetServicesResponse', onMessage);
},
(e) => {
this.off('message.BluetoothGATTGetServicesResponse', onMessage);
throw e;
}
);
return { address, servicesList };
}

async readBluetoothGATTCharacteristicService(address, handle) {
if (!this.connected) throw new Error(`Not connected`);
if (!this.authorized) throw new Error(`Not authorized`);
return await this.sendMessageAwaitResponse(
new pb.BluetoothGATTReadRequest([address, handle]),
'BluetoothGATTReadResponse'
);
}

async writeBluetoothGATTCharacteristicService(
address,
handle,
value,
response = false
) {
if (!this.connected) throw new Error(`Not connected`);
if (!this.authorized) throw new Error(`Not authorized`);

const message = new pb.BluetoothGATTWriteRequest([
address,
handle,
response,
value,
]);
if (!response) return this.sendMessage(message);

return await this.sendMessageAwaitResponse(
message,
'BluetoothGATTWriteResponse'
);
}

async notifyBluetoothGATTCharacteristicService(address, handle) {
if (!this.connected) throw new Error(`Not connected`);
if (!this.authorized) throw new Error(`Not authorized`);
return await this.sendMessageAwaitResponse(
new pb.BluetoothGATTNotifyRequest([address, handle, true]),
'BluetoothGATTNotifyResponse'
);
}

async readBluetoothGATTDescriptorService(address, handle) {
if (!this.connected) throw new Error(`Not connected`);
if (!this.authorized) throw new Error(`Not authorized`);
return await this.sendMessageAwaitResponse(
new pb.BluetoothGATTReadDescriptorRequest([address, handle]),
'BluetoothGATTReadResponse'
);
}

async writeBluetoothGATTDescriptorService(
address,
handle,
value,
waitForResponse = false
) {
if (!this.connected) throw new Error(`Not connected`);
if (!this.authorized) throw new Error(`Not authorized`);
const message = new pb.BluetoothGATTWriteDescriptorRequest([
address,
handle,
value,
]);
if (!waitForResponse) return this.sendMessage(message);

return await this.sendMessageAwaitResponse(
message,
'BluetoothGATTWriteResponse'
);
}
}

module.exports = EsphomeNativeApiConnection;
6 changes: 3 additions & 3 deletions lib/protoc/api.proto
Original file line number Diff line number Diff line change
Expand Up @@ -1218,19 +1218,19 @@ message BluetoothGATTGetServicesRequest {
}

message BluetoothGATTDescriptor {
repeated uint64 uuid = 1;
repeated uint64 uuid = 1 [jstype=JS_STRING];
uint32 handle = 2;
}

message BluetoothGATTCharacteristic {
repeated uint64 uuid = 1;
repeated uint64 uuid = 1 [jstype=JS_STRING];
uint32 handle = 2;
uint32 properties = 3;
repeated BluetoothGATTDescriptor descriptors = 4;
}

message BluetoothGATTService {
repeated uint64 uuid = 1;
repeated uint64 uuid = 1 [jstype=JS_STRING];
uint32 handle = 2;
repeated BluetoothGATTCharacteristic characteristics = 3;
}
Expand Down
36 changes: 18 additions & 18 deletions lib/protoc/api_pb.js
Original file line number Diff line number Diff line change
Expand Up @@ -22207,7 +22207,7 @@ proto.BluetoothGATTDescriptor.deserializeBinaryFromReader = function(msg, reader
var field = reader.getFieldNumber();
switch (field) {
case 1:
var values = /** @type {!Array<number>} */ (reader.isDelimited() ? reader.readPackedUint64() : [reader.readUint64()]);
var values = /** @type {!Array<string>} */ (reader.isDelimited() ? reader.readPackedUint64String() : [reader.readUint64String()]);
for (var i = 0; i < values.length; i++) {
msg.addUuid(values[i]);
}
Expand Down Expand Up @@ -22247,7 +22247,7 @@ proto.BluetoothGATTDescriptor.serializeBinaryToWriter = function(message, writer
var f = undefined;
f = message.getUuidList();
if (f.length > 0) {
writer.writePackedUint64(
writer.writePackedUint64String(
1,
f
);
Expand All @@ -22264,15 +22264,15 @@ proto.BluetoothGATTDescriptor.serializeBinaryToWriter = function(message, writer

/**
* repeated uint64 uuid = 1;
* @return {!Array<number>}
* @return {!Array<string>}
*/
proto.BluetoothGATTDescriptor.prototype.getUuidList = function() {
return /** @type {!Array<number>} */ (jspb.Message.getRepeatedField(this, 1));
return /** @type {!Array<string>} */ (jspb.Message.getRepeatedField(this, 1));
};


/**
* @param {!Array<number>} value
* @param {!Array<string>} value
* @return {!proto.BluetoothGATTDescriptor} returns this
*/
proto.BluetoothGATTDescriptor.prototype.setUuidList = function(value) {
Expand All @@ -22281,7 +22281,7 @@ proto.BluetoothGATTDescriptor.prototype.setUuidList = function(value) {


/**
* @param {number} value
* @param {string} value
* @param {number=} opt_index
* @return {!proto.BluetoothGATTDescriptor} returns this
*/
Expand Down Expand Up @@ -22398,7 +22398,7 @@ proto.BluetoothGATTCharacteristic.deserializeBinaryFromReader = function(msg, re
var field = reader.getFieldNumber();
switch (field) {
case 1:
var values = /** @type {!Array<number>} */ (reader.isDelimited() ? reader.readPackedUint64() : [reader.readUint64()]);
var values = /** @type {!Array<string>} */ (reader.isDelimited() ? reader.readPackedUint64String() : [reader.readUint64String()]);
for (var i = 0; i < values.length; i++) {
msg.addUuid(values[i]);
}
Expand Down Expand Up @@ -22447,7 +22447,7 @@ proto.BluetoothGATTCharacteristic.serializeBinaryToWriter = function(message, wr
var f = undefined;
f = message.getUuidList();
if (f.length > 0) {
writer.writePackedUint64(
writer.writePackedUint64String(
1,
f
);
Expand Down Expand Up @@ -22479,15 +22479,15 @@ proto.BluetoothGATTCharacteristic.serializeBinaryToWriter = function(message, wr

/**
* repeated uint64 uuid = 1;
* @return {!Array<number>}
* @return {!Array<string>}
*/
proto.BluetoothGATTCharacteristic.prototype.getUuidList = function() {
return /** @type {!Array<number>} */ (jspb.Message.getRepeatedField(this, 1));
return /** @type {!Array<string>} */ (jspb.Message.getRepeatedField(this, 1));
};


/**
* @param {!Array<number>} value
* @param {!Array<string>} value
* @return {!proto.BluetoothGATTCharacteristic} returns this
*/
proto.BluetoothGATTCharacteristic.prototype.setUuidList = function(value) {
Expand All @@ -22496,7 +22496,7 @@ proto.BluetoothGATTCharacteristic.prototype.setUuidList = function(value) {


/**
* @param {number} value
* @param {string} value
* @param {number=} opt_index
* @return {!proto.BluetoothGATTCharacteristic} returns this
*/
Expand Down Expand Up @@ -22668,7 +22668,7 @@ proto.BluetoothGATTService.deserializeBinaryFromReader = function(msg, reader) {
var field = reader.getFieldNumber();
switch (field) {
case 1:
var values = /** @type {!Array<number>} */ (reader.isDelimited() ? reader.readPackedUint64() : [reader.readUint64()]);
var values = /** @type {!Array<string>} */ (reader.isDelimited() ? reader.readPackedUint64String() : [reader.readUint64String()]);
for (var i = 0; i < values.length; i++) {
msg.addUuid(values[i]);
}
Expand Down Expand Up @@ -22713,7 +22713,7 @@ proto.BluetoothGATTService.serializeBinaryToWriter = function(message, writer) {
var f = undefined;
f = message.getUuidList();
if (f.length > 0) {
writer.writePackedUint64(
writer.writePackedUint64String(
1,
f
);
Expand All @@ -22738,15 +22738,15 @@ proto.BluetoothGATTService.serializeBinaryToWriter = function(message, writer) {

/**
* repeated uint64 uuid = 1;
* @return {!Array<number>}
* @return {!Array<string>}
*/
proto.BluetoothGATTService.prototype.getUuidList = function() {
return /** @type {!Array<number>} */ (jspb.Message.getRepeatedField(this, 1));
return /** @type {!Array<string>} */ (jspb.Message.getRepeatedField(this, 1));
};


/**
* @param {!Array<number>} value
* @param {!Array<string>} value
* @return {!proto.BluetoothGATTService} returns this
*/
proto.BluetoothGATTService.prototype.setUuidList = function(value) {
Expand All @@ -22755,7 +22755,7 @@ proto.BluetoothGATTService.prototype.setUuidList = function(value) {


/**
* @param {number} value
* @param {string} value
* @param {number=} opt_index
* @return {!proto.BluetoothGATTService} returns this
*/
Expand Down

0 comments on commit 89f0aa2

Please sign in to comment.