signalk-mareas-ihm 1.2.0 → 1.2.1
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/README.md
CHANGED
|
@@ -74,6 +74,14 @@ El calado del barco se lee automáticamente de Signal K (`design.draft`) o se pu
|
|
|
74
74
|
|
|
75
75
|
### Changelog
|
|
76
76
|
|
|
77
|
+
#### v1.2.1
|
|
78
|
+
- **UI Fondeo renovada**: cajas resultado con títulos uniformes 14px, Cadena y Profundidad en naranja, Estado Marea muestra resumen, Status Profundidad con estado varada, Publicar SK en barra verde.
|
|
79
|
+
- **Config overlay**: panel configuración se superpone sin scroll, con botón Calcular naranja.
|
|
80
|
+
- **Botones 170px**: todos los botones de la barra principal ampliados.
|
|
81
|
+
- **Fix alarma**: notificaciones OpenPlotter se limpian correctamente (state:normal).
|
|
82
|
+
- **Fix hBow**: corregido borrado de altura de roldana.
|
|
83
|
+
- **Fix Publicar SK**: estado persistente entre reinicios.
|
|
84
|
+
|
|
77
85
|
#### v1.2.0
|
|
78
86
|
- ⚓ **Calculadora de fondeo**: nueva pestaña FONDEO con modelo de scope náutico. Cadena recomendada, radio de borneo, publicación opcional en Signal K (`environment.anchor.mareasIhm.*`).
|
|
79
87
|
- 🔇 **Fix notificaciones**: la alarma de varada suena una sola vez (antes se repetía cada 60s).
|
|
@@ -174,6 +182,14 @@ Vessel draft is read automatically from Signal K (`design.draft`) or can be conf
|
|
|
174
182
|
|
|
175
183
|
### Changelog
|
|
176
184
|
|
|
185
|
+
#### v1.2.1
|
|
186
|
+
- **UI Fondeo renovada**: cajas resultado con títulos uniformes 14px, Cadena y Profundidad en naranja, Estado Marea muestra resumen, Status Profundidad con estado varada, Publicar SK en barra verde.
|
|
187
|
+
- **Config overlay**: panel configuración se superpone sin scroll, con botón Calcular naranja.
|
|
188
|
+
- **Botones 170px**: todos los botones de la barra principal ampliados.
|
|
189
|
+
- **Fix alarma**: notificaciones OpenPlotter se limpian correctamente (state:normal).
|
|
190
|
+
- **Fix hBow**: corregido borrado de altura de roldana.
|
|
191
|
+
- **Fix Publicar SK**: estado persistente entre reinicios.
|
|
192
|
+
|
|
177
193
|
#### v1.2.0
|
|
178
194
|
- ⚓ **Anchoring calculator**: new ANCHOR tab with nautical scope model. Recommended chain, swing radius, optional Signal K publication (`environment.anchor.mareasIhm.*`).
|
|
179
195
|
- 🔇 **Notification fix**: grounding alarm sounds once (previously repeated every 60s).
|
package/dist/index.js
CHANGED
|
@@ -35,7 +35,7 @@ function isPositionValue(v) {
|
|
|
35
35
|
}
|
|
36
36
|
// Plugin version — must match package.json "version" field.
|
|
37
37
|
// Shown in the WebApp instructions modal and published as environment.tide.pluginVersion.
|
|
38
|
-
const PLUGIN_VERSION = "1.2.
|
|
38
|
+
const PLUGIN_VERSION = "1.2.1";
|
|
39
39
|
const DEFAULT_LANG = "es";
|
|
40
40
|
const NEARBY_THRESHOLD_METERS = 30000;
|
|
41
41
|
// v1.1.0: Default fallback station when no GPS and no lastStation (first boot).
|
|
@@ -1050,20 +1050,19 @@ export default function (app) {
|
|
|
1050
1050
|
// Previously, the clear only happened on the next evaluation cycle (~60s), causing phantom alarms.
|
|
1051
1051
|
function emitNotificationClear() {
|
|
1052
1052
|
try {
|
|
1053
|
-
// v1.2.
|
|
1054
|
-
// This prevents apps like avnav from showing stale "normal" as an active alarm.
|
|
1053
|
+
// v1.2.1 FIX: Use state:"normal" instead of null. Null didn't clear OpenPlotter notifications.
|
|
1055
1054
|
const clearDelta = {
|
|
1056
1055
|
context: ("vessels." + app.selfId),
|
|
1057
1056
|
updates: [{
|
|
1058
1057
|
timestamp: new Date().toISOString(),
|
|
1059
1058
|
values: [{
|
|
1060
1059
|
path: "notifications.signalk-mareas-ihm.grounding",
|
|
1061
|
-
value:
|
|
1060
|
+
value: { state: "normal", method: [], message: "" }
|
|
1062
1061
|
}]
|
|
1063
1062
|
}]
|
|
1064
1063
|
};
|
|
1065
1064
|
app.handleMessage(plugin.id, clearDelta);
|
|
1066
|
-
lastGroundingNotifState = "cleared"
|
|
1065
|
+
lastGroundingNotifState = "normal"; // v1.2.1: "normal" not "cleared" — prevents re-emit on next cycle
|
|
1067
1066
|
}
|
|
1068
1067
|
catch { /* never break user action */ }
|
|
1069
1068
|
}
|
|
@@ -1105,6 +1104,8 @@ export default function (app) {
|
|
|
1105
1104
|
expressApp.get("/signalk-mareas-ihm/api/anchor/config", async (_req, res) => {
|
|
1106
1105
|
try {
|
|
1107
1106
|
const saved = (await ihmCache.get("anchorConfig"));
|
|
1107
|
+
if (saved && typeof saved.autoPublish === "boolean")
|
|
1108
|
+
anchorAutoPublish = saved.autoPublish;
|
|
1108
1109
|
res.json({ ...ANCHOR_DEFAULTS, ...saved, autoPublish: anchorAutoPublish });
|
|
1109
1110
|
}
|
|
1110
1111
|
catch (e) {
|
|
@@ -1123,8 +1124,10 @@ export default function (app) {
|
|
|
1123
1124
|
roundingStep: [1, 5].includes(Number(b.roundingStep)) ? Number(b.roundingStep) : ANCHOR_DEFAULTS.roundingStep,
|
|
1124
1125
|
};
|
|
1125
1126
|
await ihmCache.set("anchorConfig", cfg);
|
|
1126
|
-
if (typeof b.autoPublish === "boolean")
|
|
1127
|
+
if (typeof b.autoPublish === "boolean") {
|
|
1127
1128
|
anchorAutoPublish = b.autoPublish;
|
|
1129
|
+
await ihmCache.set("anchorConfig", { ...cfg, autoPublish: anchorAutoPublish });
|
|
1130
|
+
}
|
|
1128
1131
|
res.json({ ok: true, ...cfg, autoPublish: anchorAutoPublish });
|
|
1129
1132
|
}
|
|
1130
1133
|
catch (e) {
|
|
@@ -1141,28 +1144,57 @@ export default function (app) {
|
|
|
1141
1144
|
anchorAutoPublish = b.autoPublish;
|
|
1142
1145
|
const saved = (await ihmCache.get("anchorConfig"));
|
|
1143
1146
|
const cfg = { ...ANCHOR_DEFAULTS, ...saved };
|
|
1144
|
-
// Read SK values
|
|
1145
|
-
const
|
|
1146
|
-
const
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1147
|
+
// Read SK values — unified depth reader with diagnostics
|
|
1148
|
+
const toNum = (v) => { if (v == null || v === "")
|
|
1149
|
+
return null; const n = Number(v); return (typeof n === "number" && isFinite(n) && n > 0) ? n : null; };
|
|
1150
|
+
const readSkNumber = (path) => {
|
|
1151
|
+
try {
|
|
1152
|
+
// Try path.value (SK v2 unwrapped)
|
|
1153
|
+
let d = toNum(app.getSelfPath(path + ".value"));
|
|
1154
|
+
if (d)
|
|
1155
|
+
return d;
|
|
1156
|
+
// Try raw path (SK v1 may return {value: X} or plain number)
|
|
1157
|
+
const raw = app.getSelfPath(path);
|
|
1158
|
+
if (typeof raw === "number" && isFinite(raw) && raw > 0)
|
|
1159
|
+
return raw;
|
|
1160
|
+
if (raw && typeof raw === "object") {
|
|
1161
|
+
d = toNum(raw.value ?? raw.maximum);
|
|
1162
|
+
if (d)
|
|
1163
|
+
return d;
|
|
1164
|
+
}
|
|
1165
|
+
return null;
|
|
1166
|
+
}
|
|
1167
|
+
catch {
|
|
1168
|
+
return null;
|
|
1169
|
+
}
|
|
1170
|
+
};
|
|
1171
|
+
// Try ALL standard depth paths: belowTransducer > belowSurface > belowKeel
|
|
1172
|
+
let skDepth = null;
|
|
1173
|
+
let depthPathUsed = "none";
|
|
1174
|
+
const depthPaths = [
|
|
1175
|
+
"environment.depth.belowTransducer",
|
|
1176
|
+
"environment.depth.belowSurface",
|
|
1177
|
+
"environment.depth.belowKeel",
|
|
1178
|
+
];
|
|
1179
|
+
const depthDiag = {};
|
|
1180
|
+
for (const dp of depthPaths) {
|
|
1181
|
+
try {
|
|
1182
|
+
const rawDot = app.getSelfPath(dp + ".value");
|
|
1183
|
+
const rawObj = app.getSelfPath(dp);
|
|
1184
|
+
depthDiag[dp] = { dotValue: rawDot ?? null, raw: (typeof rawObj === "object" ? JSON.stringify(rawObj)?.slice(0, 200) : rawObj) ?? null };
|
|
1185
|
+
}
|
|
1186
|
+
catch (e) {
|
|
1187
|
+
depthDiag[dp] = { error: e?.message };
|
|
1188
|
+
}
|
|
1189
|
+
const v = readSkNumber(dp);
|
|
1190
|
+
if (v) {
|
|
1191
|
+
skDepth = v;
|
|
1192
|
+
depthPathUsed = dp;
|
|
1193
|
+
break;
|
|
1194
|
+
}
|
|
1162
1195
|
}
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
} })();
|
|
1196
|
+
const skTideNow = readSkNumber("environment.tide.heightNow");
|
|
1197
|
+
const skNextHighH = readSkNumber("environment.tide.heightNextHigh");
|
|
1166
1198
|
const skNextHighT = (() => { try {
|
|
1167
1199
|
const v = app.getSelfPath("environment.tide.timeNextHigh.value");
|
|
1168
1200
|
return typeof v === "string" ? Date.parse(v) : null;
|
|
@@ -1170,13 +1202,7 @@ export default function (app) {
|
|
|
1170
1202
|
catch {
|
|
1171
1203
|
return null;
|
|
1172
1204
|
} })();
|
|
1173
|
-
const skNextLowH = (
|
|
1174
|
-
const v = app.getSelfPath("environment.tide.heightNextLow.value");
|
|
1175
|
-
return typeof v === "number" ? v : null;
|
|
1176
|
-
}
|
|
1177
|
-
catch {
|
|
1178
|
-
return null;
|
|
1179
|
-
} })();
|
|
1205
|
+
const skNextLowH = readSkNumber("environment.tide.heightNextLow");
|
|
1180
1206
|
const skNextLowT = (() => { try {
|
|
1181
1207
|
const v = app.getSelfPath("environment.tide.timeNextLow.value");
|
|
1182
1208
|
return typeof v === "string" ? Date.parse(v) : null;
|
|
@@ -1184,13 +1210,7 @@ export default function (app) {
|
|
|
1184
1210
|
catch {
|
|
1185
1211
|
return null;
|
|
1186
1212
|
} })();
|
|
1187
|
-
const skLoa = (
|
|
1188
|
-
const v = app.getSelfPath("design.length.overall.value");
|
|
1189
|
-
return typeof v === "number" ? v : null;
|
|
1190
|
-
}
|
|
1191
|
-
catch {
|
|
1192
|
-
return null;
|
|
1193
|
-
} })();
|
|
1213
|
+
const skLoa = readSkNumber("design.length.overall");
|
|
1194
1214
|
const skTendency = (() => { try {
|
|
1195
1215
|
const v = app.getSelfPath("environment.tide.tendencypercentage.value");
|
|
1196
1216
|
return typeof v === "string" ? v : null;
|
|
@@ -1221,7 +1241,39 @@ export default function (app) {
|
|
|
1221
1241
|
if (anchorAutoPublish && Number.isFinite(result.chainL) && !result.partial) {
|
|
1222
1242
|
publishAnchoringDeltas(result);
|
|
1223
1243
|
}
|
|
1224
|
-
res.json({ ...result, depthMax, depthMin, draftEffective, tendencyPercentage: skTendency });
|
|
1244
|
+
res.json({ ...result, depthMax, depthMin, draftEffective, tendencyPercentage: skTendency, depthPathUsed, skDepthFound: skDepth, depthDiag });
|
|
1245
|
+
}
|
|
1246
|
+
catch (e) {
|
|
1247
|
+
res.status(500).json({ error: e?.message ?? String(e) });
|
|
1248
|
+
}
|
|
1249
|
+
});
|
|
1250
|
+
// v1.2.1: Diagnostic endpoint — shows exactly what Signal K returns for depth paths
|
|
1251
|
+
// Access: http://localhost:3000/signalk-mareas-ihm/api/anchor/depth-debug
|
|
1252
|
+
expressApp.get("/signalk-mareas-ihm/api/anchor/depth-debug", async (_req, res) => {
|
|
1253
|
+
try {
|
|
1254
|
+
const paths = [
|
|
1255
|
+
"environment.depth.belowTransducer",
|
|
1256
|
+
"environment.depth.belowSurface",
|
|
1257
|
+
"environment.depth.belowKeel",
|
|
1258
|
+
"environment.depth",
|
|
1259
|
+
];
|
|
1260
|
+
const results = {};
|
|
1261
|
+
for (const p of paths) {
|
|
1262
|
+
try {
|
|
1263
|
+
const raw = app.getSelfPath(p);
|
|
1264
|
+
const dotValue = (() => { try {
|
|
1265
|
+
return app.getSelfPath(p + ".value");
|
|
1266
|
+
}
|
|
1267
|
+
catch {
|
|
1268
|
+
return "ERROR";
|
|
1269
|
+
} })();
|
|
1270
|
+
results[p] = { raw: raw ?? "UNDEFINED", dotValue: dotValue ?? "UNDEFINED", rawType: typeof raw, dotValueType: typeof dotValue };
|
|
1271
|
+
}
|
|
1272
|
+
catch (e) {
|
|
1273
|
+
results[p] = { error: e?.message ?? String(e) };
|
|
1274
|
+
}
|
|
1275
|
+
}
|
|
1276
|
+
res.json({ timestamp: new Date().toISOString(), depthPaths: results });
|
|
1225
1277
|
}
|
|
1226
1278
|
catch (e) {
|
|
1227
1279
|
res.status(500).json({ error: e?.message ?? String(e) });
|
|
@@ -1560,9 +1612,24 @@ export default function (app) {
|
|
|
1560
1612
|
return (n && n > 0) ? n : null;
|
|
1561
1613
|
};
|
|
1562
1614
|
const readDepthBelowTransducer = () => {
|
|
1563
|
-
|
|
1564
|
-
const
|
|
1565
|
-
|
|
1615
|
+
// Try ALL standard depth paths: belowTransducer > belowSurface > belowKeel
|
|
1616
|
+
for (const p of ["environment.depth.belowTransducer", "environment.depth.belowSurface", "environment.depth.belowKeel"]) {
|
|
1617
|
+
try {
|
|
1618
|
+
let d = toNum(app.getSelfPath(p + ".value"));
|
|
1619
|
+
if (d && d > 0)
|
|
1620
|
+
return d;
|
|
1621
|
+
const raw = app.getSelfPath(p);
|
|
1622
|
+
if (typeof raw === "number" && isFinite(raw) && raw > 0)
|
|
1623
|
+
return raw;
|
|
1624
|
+
if (raw && typeof raw === "object") {
|
|
1625
|
+
d = toNum(raw.value ?? raw.maximum);
|
|
1626
|
+
if (d && d > 0)
|
|
1627
|
+
return d;
|
|
1628
|
+
}
|
|
1629
|
+
}
|
|
1630
|
+
catch { /* try next */ }
|
|
1631
|
+
}
|
|
1632
|
+
return null;
|
|
1566
1633
|
};
|
|
1567
1634
|
// --- Draft resolution (manual vs Signal K) ---
|
|
1568
1635
|
const cachedDraft = toNum(caladoCfg?.draft);
|
|
@@ -1746,9 +1813,10 @@ export default function (app) {
|
|
|
1746
1813
|
};
|
|
1747
1814
|
app.handleMessage(plugin.id, draftDelta);
|
|
1748
1815
|
}
|
|
1749
|
-
// v1.2.
|
|
1750
|
-
//
|
|
1751
|
-
//
|
|
1816
|
+
// v1.2.1: Notification state machine — emit on transitions, clear stale notifications
|
|
1817
|
+
// BUG FIX: After plugin restart, lastGroundingNotifState="cleared" but old alarm notification
|
|
1818
|
+
// may still be active in SK data model. By checking !== "normal" instead of === "alarm",
|
|
1819
|
+
// we emit ONE clear on startup to kill stale zombie notifications.
|
|
1752
1820
|
const shouldNotify = notifyRisk;
|
|
1753
1821
|
const soundMuted = alarmaCfg?.soundMuted === true;
|
|
1754
1822
|
const notifMethod = soundMuted ? ["visual", "push"] : ["visual", "sound", "push"];
|
|
@@ -1767,22 +1835,20 @@ export default function (app) {
|
|
|
1767
1835
|
app.handleMessage(plugin.id, notifDelta);
|
|
1768
1836
|
lastGroundingNotifState = "alarm";
|
|
1769
1837
|
}
|
|
1770
|
-
else if (!shouldNotify && lastGroundingNotifState
|
|
1771
|
-
// TRANSITION
|
|
1772
|
-
// Using null removes the path from the SK data model, so apps like avnav
|
|
1773
|
-
// won't see a stale "normal" notification as an active alarm.
|
|
1838
|
+
else if (!shouldNotify && lastGroundingNotifState !== "normal") {
|
|
1839
|
+
// TRANSITION → clear: covers both "alarm"→"normal" AND "cleared"→"normal" (startup)
|
|
1774
1840
|
const clearDelta = {
|
|
1775
1841
|
context: ("vessels." + app.selfId),
|
|
1776
1842
|
updates: [{
|
|
1777
1843
|
timestamp: now.toISOString(),
|
|
1778
1844
|
values: [{
|
|
1779
1845
|
path: "notifications.signalk-mareas-ihm.grounding",
|
|
1780
|
-
value:
|
|
1846
|
+
value: { state: "normal", method: [], message: tr("Sin riesgo de varada", "No grounding risk") }
|
|
1781
1847
|
}]
|
|
1782
1848
|
}]
|
|
1783
1849
|
};
|
|
1784
1850
|
app.handleMessage(plugin.id, clearDelta);
|
|
1785
|
-
lastGroundingNotifState = "
|
|
1851
|
+
lastGroundingNotifState = "normal";
|
|
1786
1852
|
}
|
|
1787
1853
|
// If already "cleared" or "normal" and no risk → do nothing (no spam).
|
|
1788
1854
|
}
|