bluetooth-buzzer/src/lib/ntp.ts

108 lines
3 KiB
TypeScript

export class Ntp {
__currentOffset: bigint | undefined;
__currentFluctuation: bigint | undefined;
// between 0 and 100
__currentAcceptanceRate: bigint | undefined;
__acceptedFluctuationFactor: bigint = 4n;
__latestOffsets: bigint[] = [];
__latestFluctuations: bigint[] = [];
__latestAcceptanceResults: bigint[] = [];
constructor() {
this.__currentOffset = undefined;
this.__currentFluctuation = undefined;
this.__latestOffsets = [];
this.__latestFluctuations = [];
this.__latestAcceptanceResults = [];
}
currentOffset() {
return this.__currentOffset;
}
currentAcceptanceRate() {
return this.__currentAcceptanceRate;
}
handleTimeSync(sentAt: bigint, receivedAt: bigint, time: bigint) {
let roundTripTime = receivedAt - sentAt;
let newOffset = time + roundTripTime / 2n - receivedAt;
this.__handleNewOffset(newOffset);
}
__handleNewOffset(newOffset: bigint) {
const offsetInMargin = this.__isOffsetInMargin(newOffset);
this.__handleAcceptanceResult(offsetInMargin);
if (!offsetInMargin) {
return;
}
this.__currentOffset = this.__pushValueAndCaluclateAverage(this.__latestOffsets, newOffset);
}
__handleAcceptanceResult(result: boolean) {
this.__currentAcceptanceRate = this.__pushValueAndCaluclateAverage(
this.__latestAcceptanceResults,
result ? 100n : 0n
);
if (this.__currentAcceptanceRate > 90n) {
this.__acceptedFluctuationFactor = 4n;
}
else if (this.__currentAcceptanceRate > 80n) {
this.__acceptedFluctuationFactor = 6n;
}
else if (this.__currentAcceptanceRate > 50n) {
this.__acceptedFluctuationFactor = 10n;
} else {
this.__acceptedFluctuationFactor = 100n;
}
}
__isOffsetInMargin(newOffset: bigint) {
if (!this.__currentOffset) {
return true;
}
let fluctuation = this.__currentOffset - newOffset;
if (fluctuation < 0) {
fluctuation = -fluctuation;
}
if (
this.__currentFluctuation &&
this.__latestFluctuations.length > 5 &&
fluctuation > this.__currentFluctuation * this.__acceptedFluctuationFactor
) {
return false;
}
this.__currentFluctuation = this.__pushValueAndCaluclateAverage(this.__latestFluctuations, fluctuation);
if (this.__latestFluctuations.length < 10) {
return true;
}
return fluctuation < this.__currentFluctuation * 2n;
};
__pushValueAndCaluclateAverage(values: bigint[], newValue: bigint): bigint {
values.push(newValue);
if (values.length > 10) {
values.shift();
}
let sum = 0n;
for (let i = 0; i < values.length; i++) {
sum += values[i];
}
return sum / BigInt(values.length);
};
}