Browse Source

first Working FTMS

master
Anthony Hinsinger 4 days ago
parent
commit
74efaa024a
  1. 23
      src/adv.js
  2. 51
      src/bluez-gatt.js
  3. 137
      src/ftms-service.js
  4. 45
      src/hr-service.js
  5. 104
      src/index.js
  6. 45
      src/power-service.js

23
src/adv.js

@ -1,21 +1,28 @@ @@ -1,21 +1,28 @@
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 {
@dbus.interface.property({ signature: "s" })
@property({ signature: "s" })
Type = "peripheral";
@dbus.interface.property({ signature: "s" })
@property({ signature: "s" })
LocalName = "TestDevice";
@dbus.interface.property({ signature: "as" })
ServiceUUIDs = ["139fc001-a4ed-11ed-b9df-0242ac120003"];
//ServiceUUIDs = ["1816", "1818", "1826"];
@property({ signature: "as" })
ServiceUUIDs = [];
@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() {}
constructor(bus, path) {
constructor(bus, path, uuids) {
super(bus, path, "org.bluez.LEAdvertisement1");
this.ServiceUUIDs = uuids;
}
}

51
src/bluez-gatt.js

@ -1,6 +1,35 @@ @@ -1,6 +1,35 @@
import { BaseInterface } from "./base";
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 {
@property({ signature: "s" })
@ -46,6 +75,9 @@ export class BaseCharacteritic extends BaseInterface { @@ -46,6 +75,9 @@ export class BaseCharacteritic extends BaseInterface {
@property({ signature: "b" })
Notifying = false;
@property({ signature: "ay" })
Value = [];
constructor(bus, path, service, uuid, flags) {
super(bus, path, "org.bluez.GattCharacteristic1");
this.UUID = uuid;
@ -73,12 +105,25 @@ export class BaseCharacteritic extends BaseInterface { @@ -73,12 +105,25 @@ export class BaseCharacteritic extends BaseInterface {
@method({})
StartNotify() {
console.log("not definied");
if (this.startNotify) {
this.startNotify();
} else {
console.log("not definied");
}
}
@method({})
StopNotify() {
console.log("not definied");
if (this.stopNotify) {
this.stopNotify();
} else {
console.log("not definied");
}
}
notify(value) {
this.Value = value;
Interface.emitPropertiesChanged(this, { Value: value });
}
getProperties() {

137
src/ftms-service.js

@ -0,0 +1,137 @@ @@ -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);
}
}

45
src/hr-service.js

@ -0,0 +1,45 @@ @@ -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);
}
}

104
src/index.js

@ -1,84 +1,29 @@ @@ -1,84 +1,29 @@
import { systemBus, Variant, interface as iface } from "dbus-next";
const { Interface, method, property } = iface;
import { BaseService, BaseCharacteritic } from "./bluez-gatt";
import { systemBus } from "dbus-next";
import { BaseApplication } from "./bluez-gatt";
import { Advertisement } from "./adv";
import { BaseInterface } from "./base";
import { Bluez } from "./bluez";
import { HRService } from "./hr-service";
import { PWRService } from "./power-service";
import { FTMSService } from "./ftms-service";
class Application extends BaseInterface {
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 = [];
class BikeApplication extends BaseApplication {
constructor(bus, path) {
super(bus, path);
this.addCharacteristic(new PowerChar(bus, "/eu/atoy/service0/char0", this));
this.addCharacteristic(new WriteChar(bus, "/eu/atoy/service0/char1", this));
}
}
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);
//this.addService(new HRService(bus, this.path + "/hrservice"));
//this.addService(new PWRService(bus, this.path + "/pwrservice"));
this.addService(new FTMSService(bus, this.path + "/ftmsservice"));
}
}
async function main() {
const bus = systemBus();
const app = new Application(bus, "/eu/atoy");
const adv = new Advertisement(bus, "/eu/atoy/advertising");
const app = new BikeApplication(bus, "/eu/atoy");
const adv = new Advertisement(bus, "/eu/atoy/advertising", [
//"1818",
//"180d",
"1826",
]);
await bus.requestName("eu.atoy");
@ -86,10 +31,17 @@ async function main() { @@ -86,10 +31,17 @@ async function main() {
const leamgr = obj.getInterface("org.bluez.LEAdvertisingManager1");
const gattmgr = obj.getInterface("org.bluez.GattManager1");
/*leamgr.RegisterAdvertisement("/eu/atoy/advertising", {});
console.log("BLE Advertisement registered");
gattmgr.RegisterApplication("/eu/atoy", {});
console.log("BLE GATT application registered");*/
try {
await leamgr.RegisterAdvertisement("/eu/atoy/advertising", {});
console.log("BLE Advertisement registered");
} catch (e) {
console.log("Unable to register BLE Advertisement");
}
await gattmgr.RegisterApplication("/eu/atoy", {});
console.log("BLE GATT application registered");
return;
const bz = new Bluez();
const pafers = await bz.getDevice("PAFERS_53B9A3");
@ -101,9 +53,9 @@ async function main() { @@ -101,9 +53,9 @@ async function main() {
const iscon = await pafers.isConnected();
console.log("connected: " + iscon);
bz.on("interfacesAdded", () => {
/*bz.on("interfacesAdded", () => {
});
});*/
if (!iscon) {
await pafers.connect();

45
src/power-service.js

@ -0,0 +1,45 @@ @@ -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);
}
}
Loading…
Cancel
Save