@signalk/tracks-plugin 3.0.0-beta.3 → 3.0.0-beta.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +97 -12
- package/dist/index.js.map +1 -1
- package/dist/sourceWatch.d.ts +17 -28
- package/package.json +2 -3
package/dist/index.js
CHANGED
|
@@ -12483,26 +12483,108 @@ function reconcileByExtent(history, stored) {
|
|
|
12483
12483
|
* Detection is therefore empirical rather than reading the priority config:
|
|
12484
12484
|
* seeing several sources here *is* the evidence that no rule is matching,
|
|
12485
12485
|
* which a config that merely exists would not tell us.
|
|
12486
|
+
*
|
|
12487
|
+
* With one qualification. A priority rule has a failover window — when the
|
|
12488
|
+
* top-ranked source goes quiet the engine promotes the next one — so over a
|
|
12489
|
+
* long run several sources legitimately reach this bus even with a rule in
|
|
12490
|
+
* place, one at a time. Counting every source ever seen would therefore
|
|
12491
|
+
* report a conflict on a correctly configured boat whose GPS drops out
|
|
12492
|
+
* occasionally. What distinguishes competition is *alternation*: unfiltered
|
|
12493
|
+
* sources hand back and forth repeatedly within seconds, while a failover
|
|
12494
|
+
* switches once and stays.
|
|
12495
|
+
*/
|
|
12496
|
+
/**
|
|
12497
|
+
* How quickly the source must change again for it to count as alternation.
|
|
12498
|
+
*
|
|
12499
|
+
* Shorter than the server's 15s default failover, so a promotion — one
|
|
12500
|
+
* switch after a silence — never chains into a sequence. Positions are a
|
|
12501
|
+
* rapid update, often 10Hz, so genuinely competing sources alternate far
|
|
12502
|
+
* faster than this.
|
|
12486
12503
|
*/
|
|
12504
|
+
var ALTERNATION_WINDOW_MS = 1e4;
|
|
12505
|
+
/**
|
|
12506
|
+
* How long a conflict is remembered after it was last observed.
|
|
12507
|
+
*
|
|
12508
|
+
* Longer than the 30s status tick, so a conflict seen just after one tick is
|
|
12509
|
+
* still reported at the next and does not flicker. Finite, so fixing the
|
|
12510
|
+
* priorities clears the warning on its own rather than needing a restart —
|
|
12511
|
+
* which is the whole complaint about a watcher that only ever accumulates.
|
|
12512
|
+
*/
|
|
12513
|
+
var CONFLICT_RETENTION_MS = 9e4;
|
|
12514
|
+
/**
|
|
12515
|
+
* How many source changes in a row before it counts as competition.
|
|
12516
|
+
*
|
|
12517
|
+
* One change is a failover, two is a failback — the preferred source
|
|
12518
|
+
* returning and winning immediately, which the server does on purpose. Three
|
|
12519
|
+
* means the sources are trading the path back and forth, which no priority
|
|
12520
|
+
* rule does.
|
|
12521
|
+
*/
|
|
12522
|
+
var CHANGES_BEFORE_WARNING = 3;
|
|
12487
12523
|
var SourceWatch = class {
|
|
12524
|
+
/** Last time each source produced a position, per context. */
|
|
12488
12525
|
seen = /* @__PURE__ */ new Map();
|
|
12526
|
+
/** Sources found competing, per context, with when each was last seen so. */
|
|
12527
|
+
competing = /* @__PURE__ */ new Map();
|
|
12528
|
+
/** The current alternation per context: who is in it, and how long it is. */
|
|
12529
|
+
lastSeen = /* @__PURE__ */ new Map();
|
|
12489
12530
|
/** Record that `context` produced a position from `$source`. */
|
|
12490
|
-
add(context, source) {
|
|
12531
|
+
add(context, source, now = Date.now()) {
|
|
12491
12532
|
if (!source) return;
|
|
12492
12533
|
let sources = this.seen.get(context);
|
|
12493
12534
|
if (!sources) {
|
|
12494
|
-
sources = /* @__PURE__ */ new
|
|
12535
|
+
sources = /* @__PURE__ */ new Map();
|
|
12495
12536
|
this.seen.set(context, sources);
|
|
12496
12537
|
}
|
|
12497
|
-
|
|
12538
|
+
const previous = this.lastSeen.get(context);
|
|
12539
|
+
sources.set(source, now);
|
|
12540
|
+
const stale = !previous || now - previous.changedAt > ALTERNATION_WINDOW_MS;
|
|
12541
|
+
if (stale || previous.source === source) this.lastSeen.set(context, {
|
|
12542
|
+
source,
|
|
12543
|
+
at: now,
|
|
12544
|
+
changedAt: stale ? now : previous.changedAt,
|
|
12545
|
+
changes: stale ? 0 : previous.changes,
|
|
12546
|
+
participants: stale ? /* @__PURE__ */ new Set([source]) : previous.participants
|
|
12547
|
+
});
|
|
12548
|
+
else {
|
|
12549
|
+
const changes = previous.changes + 1;
|
|
12550
|
+
const participants = previous.participants.add(source);
|
|
12551
|
+
this.lastSeen.set(context, {
|
|
12552
|
+
source,
|
|
12553
|
+
at: now,
|
|
12554
|
+
changedAt: now,
|
|
12555
|
+
changes,
|
|
12556
|
+
participants
|
|
12557
|
+
});
|
|
12558
|
+
if (changes >= CHANGES_BEFORE_WARNING) {
|
|
12559
|
+
const found = this.competing.get(context) ?? /* @__PURE__ */ new Map();
|
|
12560
|
+
for (const participant of participants) found.set(participant, sources.get(participant) ?? now);
|
|
12561
|
+
this.competing.set(context, found);
|
|
12562
|
+
}
|
|
12563
|
+
}
|
|
12498
12564
|
}
|
|
12499
|
-
/**
|
|
12565
|
+
/** Every source seen for a context, in the order they first appeared. */
|
|
12500
12566
|
sourcesFor(context) {
|
|
12501
|
-
return [...this.seen.get(context) ??
|
|
12567
|
+
return [...(this.seen.get(context) ?? /* @__PURE__ */ new Map()).keys()];
|
|
12568
|
+
}
|
|
12569
|
+
/**
|
|
12570
|
+
* The sources found live at the same time, which is what a warning names.
|
|
12571
|
+
*
|
|
12572
|
+
* Expired entries are dropped rather than merely hidden, so a conflict that
|
|
12573
|
+
* has stopped recurring leaves nothing behind.
|
|
12574
|
+
*/
|
|
12575
|
+
competingFor(context, now = Date.now()) {
|
|
12576
|
+
const found = this.competing.get(context);
|
|
12577
|
+
if (!found) return [];
|
|
12578
|
+
for (const [source, at] of found) if (now - at > CONFLICT_RETENTION_MS) found.delete(source);
|
|
12579
|
+
if (found.size < 2) {
|
|
12580
|
+
this.competing.delete(context);
|
|
12581
|
+
return [];
|
|
12582
|
+
}
|
|
12583
|
+
return [...found.keys()];
|
|
12502
12584
|
}
|
|
12503
|
-
/** Contexts
|
|
12504
|
-
conflicted() {
|
|
12505
|
-
return [...this.
|
|
12585
|
+
/** Contexts that have received positions from two sources at once. */
|
|
12586
|
+
conflicted(now = Date.now()) {
|
|
12587
|
+
return [...this.competing.keys()].filter((context) => this.competingFor(context, now).length > 1);
|
|
12506
12588
|
}
|
|
12507
12589
|
/**
|
|
12508
12590
|
* A status line naming the problem, or undefined when nothing is wrong.
|
|
@@ -12512,21 +12594,24 @@ var SourceWatch = class {
|
|
|
12512
12594
|
* vessels are usually AIS, where multiple receivers are expected and less
|
|
12513
12595
|
* worth nagging about — they are counted, not named.
|
|
12514
12596
|
*/
|
|
12515
|
-
warning(selfContext) {
|
|
12516
|
-
const conflicted = this.conflicted();
|
|
12597
|
+
warning(selfContext, now = Date.now()) {
|
|
12598
|
+
const conflicted = this.conflicted(now);
|
|
12517
12599
|
if (conflicted.length === 0) return;
|
|
12518
12600
|
const selfConflicted = selfContext !== void 0 && conflicted.includes(selfContext);
|
|
12519
12601
|
const others = conflicted.length - (selfConflicted ? 1 : 0);
|
|
12520
12602
|
if (selfConflicted) {
|
|
12521
|
-
const
|
|
12603
|
+
const recent = this.competingFor(selfContext, now);
|
|
12604
|
+
const sources = recent.join(", ");
|
|
12522
12605
|
const tail = others > 0 ? ` (and ${others} other ${others === 1 ? "vessel" : "vessels"})` : "";
|
|
12523
|
-
return `navigation.position for the own vessel is arriving from ${
|
|
12606
|
+
return `navigation.position for the own vessel is arriving from ${recent.length} sources (${sources})${tail}. Set source priority for navigation.position, or the track will zigzag between them.`;
|
|
12524
12607
|
}
|
|
12525
12608
|
return `navigation.position is arriving from multiple sources for ${others} ${others === 1 ? "vessel" : "vessels"}.`;
|
|
12526
12609
|
}
|
|
12527
12610
|
/** Forget everything seen; used when the plugin restarts. */
|
|
12528
12611
|
clear() {
|
|
12529
12612
|
this.seen.clear();
|
|
12613
|
+
this.competing.clear();
|
|
12614
|
+
this.lastSeen.clear();
|
|
12530
12615
|
}
|
|
12531
12616
|
};
|
|
12532
12617
|
//#endregion
|