first Working FTMS
This commit is contained in:
+15
-8
@@ -1,21 +1,28 @@
|
|||||||
import { BaseInterface } from "./base";
|
import { BaseInterface } from "./base";
|
||||||
import dbus from "dbus-next";
|
import dbus, { interface as iface } from "dbus-next";
|
||||||
|
const { property, method } = iface;
|
||||||
|
|
||||||
export class Advertisement extends BaseInterface {
|
export class Advertisement extends BaseInterface {
|
||||||
@dbus.interface.property({ signature: "s" })
|
@property({ signature: "s" })
|
||||||
Type = "peripheral";
|
Type = "peripheral";
|
||||||
|
|
||||||
@dbus.interface.property({ signature: "s" })
|
@property({ signature: "s" })
|
||||||
LocalName = "TestDevice";
|
LocalName = "TestDevice";
|
||||||
|
|
||||||
@dbus.interface.property({ signature: "as" })
|
@property({ signature: "as" })
|
||||||
ServiceUUIDs = ["139fc001-a4ed-11ed-b9df-0242ac120003"];
|
ServiceUUIDs = [];
|
||||||
//ServiceUUIDs = ["1816", "1818", "1826"];
|
|
||||||
|
|
||||||
@dbus.interface.method({ inSignature: "", outSignature: "" })
|
@property({ signature: "a{sv}" })
|
||||||
|
ServiceData = { 1826: new dbus.Variant("ay", [0x01, 0x20, 0x00]) };
|
||||||
|
|
||||||
|
@property({ signature: "as" })
|
||||||
|
Includes = ["tx-power"];
|
||||||
|
|
||||||
|
@method({ inSignature: "", outSignature: "" })
|
||||||
Release() {}
|
Release() {}
|
||||||
|
|
||||||
constructor(bus, path) {
|
constructor(bus, path, uuids) {
|
||||||
super(bus, path, "org.bluez.LEAdvertisement1");
|
super(bus, path, "org.bluez.LEAdvertisement1");
|
||||||
|
this.ServiceUUIDs = uuids;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+46
-1
@@ -1,6 +1,35 @@
|
|||||||
import { BaseInterface } from "./base";
|
import { BaseInterface } from "./base";
|
||||||
import { Variant, interface as iface } from "dbus-next";
|
import { Variant, interface as iface } from "dbus-next";
|
||||||
const { method, property } = iface;
|
const { method, property, Interface } = iface;
|
||||||
|
|
||||||
|
export class BaseApplication extends BaseInterface {
|
||||||
|
services = [];
|
||||||
|
|
||||||
|
constructor(bus, path) {
|
||||||
|
super(bus, path, "org.freedesktop.DBus.ObjectManager");
|
||||||
|
}
|
||||||
|
|
||||||
|
addService(service) {
|
||||||
|
this.services.push(service);
|
||||||
|
}
|
||||||
|
|
||||||
|
@method({ outSignature: "a{oa{sa{sv}}}" })
|
||||||
|
GetManagedObjects(val) {
|
||||||
|
const reply = {};
|
||||||
|
for (const service of this.services) {
|
||||||
|
reply[service.path] = {
|
||||||
|
[service.$name]: service.getProperties(),
|
||||||
|
};
|
||||||
|
|
||||||
|
for (const char of service.characteristics) {
|
||||||
|
reply[char.path] = {
|
||||||
|
[char.$name]: char.getProperties(),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return reply;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export class BaseService extends BaseInterface {
|
export class BaseService extends BaseInterface {
|
||||||
@property({ signature: "s" })
|
@property({ signature: "s" })
|
||||||
@@ -46,6 +75,9 @@ export class BaseCharacteritic extends BaseInterface {
|
|||||||
@property({ signature: "b" })
|
@property({ signature: "b" })
|
||||||
Notifying = false;
|
Notifying = false;
|
||||||
|
|
||||||
|
@property({ signature: "ay" })
|
||||||
|
Value = [];
|
||||||
|
|
||||||
constructor(bus, path, service, uuid, flags) {
|
constructor(bus, path, service, uuid, flags) {
|
||||||
super(bus, path, "org.bluez.GattCharacteristic1");
|
super(bus, path, "org.bluez.GattCharacteristic1");
|
||||||
this.UUID = uuid;
|
this.UUID = uuid;
|
||||||
@@ -73,13 +105,26 @@ export class BaseCharacteritic extends BaseInterface {
|
|||||||
|
|
||||||
@method({})
|
@method({})
|
||||||
StartNotify() {
|
StartNotify() {
|
||||||
|
if (this.startNotify) {
|
||||||
|
this.startNotify();
|
||||||
|
} else {
|
||||||
console.log("not definied");
|
console.log("not definied");
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@method({})
|
@method({})
|
||||||
StopNotify() {
|
StopNotify() {
|
||||||
|
if (this.stopNotify) {
|
||||||
|
this.stopNotify();
|
||||||
|
} else {
|
||||||
console.log("not definied");
|
console.log("not definied");
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
notify(value) {
|
||||||
|
this.Value = value;
|
||||||
|
Interface.emitPropertiesChanged(this, { Value: value });
|
||||||
|
}
|
||||||
|
|
||||||
getProperties() {
|
getProperties() {
|
||||||
return {
|
return {
|
||||||
|
|||||||
@@ -0,0 +1,137 @@
|
|||||||
|
import { BaseInterface } from "./base";
|
||||||
|
import { BaseService, BaseCharacteritic, BaseApplication } from "./bluez-gatt";
|
||||||
|
|
||||||
|
export class FTMSService extends BaseService {
|
||||||
|
UUID = "00001826-0000-1000-8000-00805f9b34fb";
|
||||||
|
Primary = true;
|
||||||
|
|
||||||
|
constructor(bus, path) {
|
||||||
|
super(bus, path);
|
||||||
|
this.addCharacteristic(new FTMSFeature(bus, this.path + "/char0", this));
|
||||||
|
this.addCharacteristic(new IndoorBikeData(bus, this.path + "/char1", this));
|
||||||
|
this.addCharacteristic(
|
||||||
|
new FTMSControlPoint(bus, this.path + "/char2", this)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class FTMSFeature extends BaseCharacteritic {
|
||||||
|
constructor(bus, path, service) {
|
||||||
|
super(bus, path, service, "00002acc-0000-1000-8000-00805f9b34fb", ["read"]);
|
||||||
|
}
|
||||||
|
|
||||||
|
read() {
|
||||||
|
console.log("read FTMS Feature");
|
||||||
|
// 32bits features + 32bits target supported
|
||||||
|
// 0x0a 0x44 = feature: cadence (1), inclinaison (3), hr (10), power (14)
|
||||||
|
// 0x0a 0x20 = target: inclinaison (1), power (3), simulation parameters (13)
|
||||||
|
return [0x0a, 0x44, 0x00, 0x00, 0x0a, 0x20, 0x00, 0x00];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class FTMSControlPoint extends BaseCharacteritic {
|
||||||
|
wind = 0;
|
||||||
|
grade = 0;
|
||||||
|
crr = 0;
|
||||||
|
cw = 0;
|
||||||
|
|
||||||
|
constructor(bus, path, service) {
|
||||||
|
super(bus, path, service, "00002ad9-0000-1000-8000-00805f9b34fb", [
|
||||||
|
"write",
|
||||||
|
"indicate",
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
write(value, options) {
|
||||||
|
console.log("FTMS Write Control Point");
|
||||||
|
console.log(value);
|
||||||
|
const arraybuf = value.buffer.slice(
|
||||||
|
value.byteOffset,
|
||||||
|
value.byteOffset + value.byteLength
|
||||||
|
);
|
||||||
|
const view = new DataView(arraybuf);
|
||||||
|
|
||||||
|
if (value[0] === 0x00) {
|
||||||
|
// take control
|
||||||
|
this.notify([0x80, 0x00, 0x01]);
|
||||||
|
} else if (value[0] === 0x01) {
|
||||||
|
// reset machine
|
||||||
|
this.notify([0x80, 0x01, 0x01]);
|
||||||
|
} else if (value[0] === 0x07) {
|
||||||
|
// start/resume
|
||||||
|
this.notify([0x80, 0x07, 0x01]);
|
||||||
|
} else if (value[0] === 0x11) {
|
||||||
|
// set simulations param (wind, grade, etc)
|
||||||
|
const wind = view.getInt16(1, true);
|
||||||
|
const grade = view.getInt16(3, true);
|
||||||
|
const crr = view.getUint8(5);
|
||||||
|
const cw = view.getUint8(6);
|
||||||
|
|
||||||
|
this.wind = wind * 0.001;
|
||||||
|
this.grade = grade * 0.01;
|
||||||
|
this.crr = crr * 0.001;
|
||||||
|
this.cw = cw * 0.01;
|
||||||
|
|
||||||
|
console.log(wind * 0.001);
|
||||||
|
console.log(grade * 0.01);
|
||||||
|
console.log(crr * 0.0001);
|
||||||
|
console.log(cw * 0.01);
|
||||||
|
|
||||||
|
this.notify([0x80, 0x11, 0x01]);
|
||||||
|
} else {
|
||||||
|
console.log("FTMS Control point op code " + value[0] + "not supported");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
startNotify() {
|
||||||
|
console.log("FTMS Start Notify CP");
|
||||||
|
}
|
||||||
|
|
||||||
|
stopNotify() {
|
||||||
|
console.log("FTMS Stop Notify CP");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class IndoorBikeData extends BaseCharacteritic {
|
||||||
|
constructor(bus, path, service) {
|
||||||
|
super(bus, path, service, "00002ad2-0000-1000-8000-00805f9b34fb", [
|
||||||
|
"notify",
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
startNotify() {
|
||||||
|
if (this.Notifying) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.Notifying = true;
|
||||||
|
|
||||||
|
this.interval = setInterval(() => {
|
||||||
|
console.log("FTMS Notif Indoor Bike");
|
||||||
|
// 0x44 0x02 = fields included: cadence (2), power (6), hr (9)
|
||||||
|
// 0x00 0x00 = average speed, always included
|
||||||
|
// cadence is *2 (resolution 0.5 s^-1)
|
||||||
|
this.notify([
|
||||||
|
0x44,
|
||||||
|
0x02,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
Math.round(160 + Math.random() * 10),
|
||||||
|
0x00,
|
||||||
|
Math.round(160 + Math.random() * 40),
|
||||||
|
0x00,
|
||||||
|
Math.round(120 + Math.random() * 10),
|
||||||
|
]);
|
||||||
|
}, 1000);
|
||||||
|
}
|
||||||
|
|
||||||
|
stopNotify() {
|
||||||
|
if (!this.Notifying) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.Notifying = false;
|
||||||
|
|
||||||
|
clearInterval(this.interval);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,45 @@
|
|||||||
|
import { BaseInterface } from "./base";
|
||||||
|
import { BaseService, BaseCharacteritic, BaseApplication } from "./bluez-gatt";
|
||||||
|
|
||||||
|
export class HRService extends BaseService {
|
||||||
|
UUID = "0000180d-0000-1000-8000-00805f9b34fb";
|
||||||
|
Primary = true;
|
||||||
|
|
||||||
|
constructor(bus, path) {
|
||||||
|
super(bus, path);
|
||||||
|
this.addCharacteristic(new HRMeasure(bus, this.path + "/char0", this));
|
||||||
|
//this.addCharacteristic(new WriteChar(bus, "/eu/atoy/hrservice/char1", this));
|
||||||
|
//this.addCharacteristic(new NotifChar(bus, "/eu/atoy/hrservice/char2", this));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class HRMeasure extends BaseCharacteritic {
|
||||||
|
constructor(bus, path, service) {
|
||||||
|
super(bus, path, service, "00002a37-0000-1000-8000-00805f9b34fb", [
|
||||||
|
"notify",
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
startNotify() {
|
||||||
|
if (this.Notifying) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.Notifying = true;
|
||||||
|
|
||||||
|
this.interval = setInterval(() => {
|
||||||
|
console.log("HR Notify");
|
||||||
|
this.notify([0x06, Math.round(120 + Math.random() * 10)]);
|
||||||
|
}, 1000);
|
||||||
|
}
|
||||||
|
|
||||||
|
stopNotify() {
|
||||||
|
if (!this.Notifying) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.Notifying = false;
|
||||||
|
|
||||||
|
clearInterval(this.interval);
|
||||||
|
}
|
||||||
|
}
|
||||||
+27
-75
@@ -1,84 +1,29 @@
|
|||||||
import { systemBus, Variant, interface as iface } from "dbus-next";
|
import { systemBus } from "dbus-next";
|
||||||
|
import { BaseApplication } from "./bluez-gatt";
|
||||||
const { Interface, method, property } = iface;
|
|
||||||
|
|
||||||
import { BaseService, BaseCharacteritic } from "./bluez-gatt";
|
|
||||||
import { Advertisement } from "./adv";
|
import { Advertisement } from "./adv";
|
||||||
import { BaseInterface } from "./base";
|
|
||||||
import { Bluez } from "./bluez";
|
import { Bluez } from "./bluez";
|
||||||
|
import { HRService } from "./hr-service";
|
||||||
|
import { PWRService } from "./power-service";
|
||||||
|
import { FTMSService } from "./ftms-service";
|
||||||
|
|
||||||
class Application extends BaseInterface {
|
class BikeApplication extends BaseApplication {
|
||||||
constructor(bus, path) {
|
|
||||||
super(bus, path, "org.freedesktop.DBus.ObjectManager");
|
|
||||||
|
|
||||||
const gattService = new MyService(bus, "/eu/atoy/service0");
|
|
||||||
|
|
||||||
this.services = [gattService];
|
|
||||||
}
|
|
||||||
|
|
||||||
@method({ outSignature: "a{oa{sa{sv}}}" })
|
|
||||||
GetManagedObjects(val) {
|
|
||||||
const reply = {};
|
|
||||||
for (const service of this.services) {
|
|
||||||
reply[service.path] = {
|
|
||||||
[service.$name]: service.getProperties(),
|
|
||||||
};
|
|
||||||
|
|
||||||
for (const char of service.characteristics) {
|
|
||||||
reply[char.path] = {
|
|
||||||
[char.$name]: char.getProperties(),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return reply;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class MyService extends BaseService {
|
|
||||||
UUID = "139fc001-a4ed-11ed-b9df-0242ac120003";
|
|
||||||
Primary = true;
|
|
||||||
|
|
||||||
@property({ signature: "ao" })
|
|
||||||
get Characteristics() {
|
|
||||||
return this.characteristics.map((c) => c.path);
|
|
||||||
}
|
|
||||||
|
|
||||||
characteristics = [];
|
|
||||||
|
|
||||||
constructor(bus, path) {
|
constructor(bus, path) {
|
||||||
super(bus, path);
|
super(bus, path);
|
||||||
this.addCharacteristic(new PowerChar(bus, "/eu/atoy/service0/char0", this));
|
//this.addService(new HRService(bus, this.path + "/hrservice"));
|
||||||
this.addCharacteristic(new WriteChar(bus, "/eu/atoy/service0/char1", this));
|
//this.addService(new PWRService(bus, this.path + "/pwrservice"));
|
||||||
}
|
this.addService(new FTMSService(bus, this.path + "/ftmsservice"));
|
||||||
}
|
|
||||||
|
|
||||||
class PowerChar extends BaseCharacteritic {
|
|
||||||
constructor(bus, path, service) {
|
|
||||||
super(bus, path, service, "139fc002-a4ed-11ed-b9df-0242ac120003", ["read"]);
|
|
||||||
}
|
|
||||||
|
|
||||||
read(options) {
|
|
||||||
return [0xaa, 0xcc];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class WriteChar extends BaseCharacteritic {
|
|
||||||
constructor(bus, path, service) {
|
|
||||||
super(bus, path, service, "139fc003-a4ed-11ed-b9df-0242ac120003", [
|
|
||||||
"write",
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
write(value, options) {
|
|
||||||
console.log(value);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function main() {
|
async function main() {
|
||||||
const bus = systemBus();
|
const bus = systemBus();
|
||||||
|
|
||||||
const app = new Application(bus, "/eu/atoy");
|
const app = new BikeApplication(bus, "/eu/atoy");
|
||||||
const adv = new Advertisement(bus, "/eu/atoy/advertising");
|
const adv = new Advertisement(bus, "/eu/atoy/advertising", [
|
||||||
|
//"1818",
|
||||||
|
//"180d",
|
||||||
|
"1826",
|
||||||
|
]);
|
||||||
|
|
||||||
await bus.requestName("eu.atoy");
|
await bus.requestName("eu.atoy");
|
||||||
|
|
||||||
@@ -86,10 +31,17 @@ async function main() {
|
|||||||
const leamgr = obj.getInterface("org.bluez.LEAdvertisingManager1");
|
const leamgr = obj.getInterface("org.bluez.LEAdvertisingManager1");
|
||||||
const gattmgr = obj.getInterface("org.bluez.GattManager1");
|
const gattmgr = obj.getInterface("org.bluez.GattManager1");
|
||||||
|
|
||||||
/*leamgr.RegisterAdvertisement("/eu/atoy/advertising", {});
|
try {
|
||||||
|
await leamgr.RegisterAdvertisement("/eu/atoy/advertising", {});
|
||||||
console.log("BLE Advertisement registered");
|
console.log("BLE Advertisement registered");
|
||||||
gattmgr.RegisterApplication("/eu/atoy", {});
|
} catch (e) {
|
||||||
console.log("BLE GATT application registered");*/
|
console.log("Unable to register BLE Advertisement");
|
||||||
|
}
|
||||||
|
|
||||||
|
await gattmgr.RegisterApplication("/eu/atoy", {});
|
||||||
|
console.log("BLE GATT application registered");
|
||||||
|
|
||||||
|
return;
|
||||||
|
|
||||||
const bz = new Bluez();
|
const bz = new Bluez();
|
||||||
const pafers = await bz.getDevice("PAFERS_53B9A3");
|
const pafers = await bz.getDevice("PAFERS_53B9A3");
|
||||||
@@ -101,9 +53,9 @@ async function main() {
|
|||||||
const iscon = await pafers.isConnected();
|
const iscon = await pafers.isConnected();
|
||||||
console.log("connected: " + iscon);
|
console.log("connected: " + iscon);
|
||||||
|
|
||||||
bz.on("interfacesAdded", () => {
|
/*bz.on("interfacesAdded", () => {
|
||||||
|
|
||||||
});
|
});*/
|
||||||
|
|
||||||
if (!iscon) {
|
if (!iscon) {
|
||||||
await pafers.connect();
|
await pafers.connect();
|
||||||
|
|||||||
@@ -0,0 +1,45 @@
|
|||||||
|
import { BaseInterface } from "./base";
|
||||||
|
import { BaseService, BaseCharacteritic, BaseApplication } from "./bluez-gatt";
|
||||||
|
|
||||||
|
export class PWRService extends BaseService {
|
||||||
|
UUID = "00001818-0000-1000-8000-00805f9b34fb";
|
||||||
|
Primary = true;
|
||||||
|
|
||||||
|
constructor(bus, path) {
|
||||||
|
super(bus, path);
|
||||||
|
this.addCharacteristic(new PWRMeasure(bus, this.path + "/char0", this));
|
||||||
|
//this.addCharacteristic(new WriteChar(bus, "/eu/atoy/hrservice/char1", this));
|
||||||
|
//this.addCharacteristic(new NotifChar(bus, "/eu/atoy/hrservice/char2", this));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class PWRMeasure extends BaseCharacteritic {
|
||||||
|
constructor(bus, path, service) {
|
||||||
|
super(bus, path, service, "00002a63-0000-1000-8000-00805f9b34fb", [
|
||||||
|
"notify",
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
startNotify() {
|
||||||
|
if (this.Notifying) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.Notifying = true;
|
||||||
|
|
||||||
|
this.interval = setInterval(() => {
|
||||||
|
console.log("Power Notify");
|
||||||
|
this.notify([0x00, 0x00, Math.round(170 + Math.random() * 30), 0x00]);
|
||||||
|
}, 1000);
|
||||||
|
}
|
||||||
|
|
||||||
|
stopNotify() {
|
||||||
|
if (!this.Notifying) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.Notifying = false;
|
||||||
|
|
||||||
|
clearInterval(this.interval);
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user